From: Eric Blake Date: Wed, 7 Oct 2009 22:05:34 +0000 (-0600) Subject: utimens: add UTIME_NOW and UTIME_OMIT support X-Git-Tag: v0.1~5327 X-Git-Url: http://erislabs.org.uk/gitweb/?a=commitdiff_plain;h=080cbfcab5a4ddf2405c018aa7c7f95ac336a465;p=gnulib.git utimens: add UTIME_NOW and UTIME_OMIT support These flags make it possible to implement futimens and utimensat; they also make touch(1) more efficient, by avoiding stat or gettime if native utimensat works. * lib/utimens.c (validate_timespec, update_timespec): New helper functions. (gl_futimens, lutimens): Use them. * modules/utimens (Depends-on): Add gettime, lstat, stat-time, stdbool, sys_stat. (Link): Mention resulting library dependency. * modules/utimecmp (Link): Likewise. * modules/utimens-tests (Depends-on): Drop stat-time, stdbool. (Makefile.am): Pick up library dependency. * lib/sys_stat.in.h (UTIME_NOW, UTIME_OMIT): Guarantee a definition. * tests/test-sys_stat.c: Test the definitions. * doc/posix-headers/sys_stat.texi (sys/stat.h): Document this. * NEWS: Document library dependency. Signed-off-by: Eric Blake --- diff --git a/ChangeLog b/ChangeLog index 9da56c21a..924050874 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,21 @@ 2009-10-10 Eric Blake + utimens: add UTIME_NOW and UTIME_OMIT support + * lib/utimens.c (validate_timespec, update_timespec): New helper + functions. + (gl_futimens, lutimens): Use them. + * modules/utimens (Depends-on): Add gettime, lstat, stat-time, + stdbool, sys_stat. + (Link): Mention resulting library dependency. + * modules/utimecmp (Link): Likewise. + * modules/utimens-tests (Depends-on): Drop stat-time, stdbool. + (Makefile.am): Pick up library dependency. + * lib/sys_stat.in.h (UTIME_NOW, UTIME_OMIT): Guarantee a + definition. + * tests/test-sys_stat.c: Test the definitions. + * doc/posix-headers/sys_stat.texi (sys/stat.h): Document this. + * NEWS: Document library dependency. + utimecmp: support symlink timestamps * lib/utimecmp.c (utimecmp): Use new interface. Skip effort of hashing when possible. Use pathconf when available. diff --git a/NEWS b/NEWS index 62c631f1e..5ae0d78c2 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,9 @@ User visible incompatible changes Date Modules Changes +2009-10-10 utimens The use of this module now requires linking with + $(LIB_CLOCK_GETTIME). + 2009-09-16 canonicalize-lgpl The include file is changed from "canonicalize.h" to . diff --git a/doc/posix-headers/sys_stat.texi b/doc/posix-headers/sys_stat.texi index 8fbfc069d..33c8683d2 100644 --- a/doc/posix-headers/sys_stat.texi +++ b/doc/posix-headers/sys_stat.texi @@ -17,6 +17,9 @@ Some platforms define macros, such as @code{S_ISDOOR}, that are not defined on other platforms. @item The functions @code{lstat} and @code{mkdir} are not declared on mingw. +@item +The macros @code{UTIME_NOW} and @code{UTIME_OMIT} are missing on some +platforms. @end itemize Portability problems not fixed by Gnulib: diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h index cc438229e..7051d53bb 100644 --- a/lib/sys_stat.in.h +++ b/lib/sys_stat.in.h @@ -278,6 +278,12 @@ # define S_IRWXUGO (S_IRWXU | S_IRWXG | S_IRWXO) #endif +/* Macros for futimens and utimensat. */ +#ifndef UTIME_NOW +# define UTIME_NOW (-1) +# define UTIME_OMIT (-2) +#endif + #ifdef __cplusplus extern "C" { diff --git a/lib/utimens.c b/lib/utimens.c index eb5102ee8..b2b25d13d 100644 --- a/lib/utimens.c +++ b/lib/utimens.c @@ -24,12 +24,17 @@ #include "utimens.h" +#include #include #include +#include #include #include #include +#include "stat-time.h" +#include "timespec.h" + #if HAVE_UTIME_H # include #endif @@ -44,6 +49,78 @@ struct utimbuf }; #endif +/* Validate the requested timestamps. Return 0 if the resulting + timespec can be used for utimensat (after possibly modifying it to + work around bugs in utimensat). Return 1 if the timespec needs + further adjustment based on stat results for utimes or other less + powerful interfaces. Return -1, with errno set to EINVAL, if + timespec is out of range. */ +static int +validate_timespec (struct timespec timespec[2]) +{ + int result = 0; + assert (timespec); + if ((timespec[0].tv_nsec != UTIME_NOW + && timespec[0].tv_nsec != UTIME_OMIT + && (timespec[0].tv_nsec < 0 || 1000000000 <= timespec[0].tv_nsec)) + || (timespec[1].tv_nsec != UTIME_NOW + && timespec[1].tv_nsec != UTIME_OMIT + && (timespec[1].tv_nsec < 0 || 1000000000 <= timespec[1].tv_nsec))) + { + errno = EINVAL; + return -1; + } + /* Work around Linux kernel 2.6.25 bug, where utimensat fails with + EINVAL if tv_sec is not 0 when using the flag values of + tv_nsec. */ + if (timespec[0].tv_nsec == UTIME_NOW + || timespec[0].tv_nsec == UTIME_OMIT) + { + timespec[0].tv_sec = 0; + result = 1; + } + if (timespec[1].tv_nsec == UTIME_NOW + || timespec[1].tv_nsec == UTIME_OMIT) + { + timespec[1].tv_sec = 0; + result = 1; + } + return result; +} + +/* Normalize any UTIME_NOW or UTIME_OMIT values in *TS, using stat + buffer STATBUF to obtain the current timestamps of the file. If + both times are UTIME_NOW, set *TS to NULL (as this can avoid some + permissions issues). If both times are UTIME_OMIT, return true + (nothing further beyond the prior collection of STATBUF is + necessary); otherwise return false. */ +static bool +update_timespec (struct stat const *statbuf, struct timespec *ts[2]) +{ + struct timespec *timespec = *ts; + if (timespec[0].tv_nsec == UTIME_OMIT + && timespec[1].tv_nsec == UTIME_OMIT) + return true; + if (timespec[0].tv_nsec == UTIME_NOW + && timespec[1].tv_nsec == UTIME_NOW) + { + *ts = NULL; + return false; + } + + if (timespec[0].tv_nsec == UTIME_OMIT) + timespec[0] = get_stat_atime (statbuf); + else if (timespec[0].tv_nsec == UTIME_NOW) + gettime (×pec[0]); + + if (timespec[1].tv_nsec == UTIME_OMIT) + timespec[1] = get_stat_mtime (statbuf); + else if (timespec[1].tv_nsec == UTIME_NOW) + gettime (×pec[1]); + + return false; +} + /* Set the access and modification time stamps of FD (a.k.a. FILE) to be TIMESPEC[0] and TIMESPEC[1], respectively. FD must be either negative -- in which case it is ignored -- @@ -57,6 +134,19 @@ struct utimbuf int gl_futimens (int fd, char const *file, struct timespec const timespec[2]) { + struct timespec adjusted_timespec[2]; + struct timespec *ts = timespec ? adjusted_timespec : NULL; + int adjustment_needed = 0; + + if (ts) + { + adjusted_timespec[0] = timespec[0]; + adjusted_timespec[1] = timespec[1]; + adjustment_needed = validate_timespec (ts); + } + if (adjustment_needed < 0) + return -1; + /* Require that at least one of FD or FILE are valid. Works around a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather than failing. */ @@ -88,16 +178,16 @@ gl_futimens (int fd, char const *file, struct timespec const timespec[2]) fsync (fd); #endif - /* POSIX 200x added two interfaces to set file timestamps with + /* POSIX 2008 added two interfaces to set file timestamps with nanosecond resolution. We provide a fallback for ENOSYS (for example, compiling against Linux 2.6.25 kernel headers and glibc 2.7, but running on Linux 2.6.18 kernel). */ #if HAVE_UTIMENSAT if (fd < 0) { - int result = utimensat (AT_FDCWD, file, timespec, 0); + int result = utimensat (AT_FDCWD, file, ts, 0); # ifdef __linux__ - /* Work around what might be a kernel bug: + /* Work around a kernel bug: http://bugzilla.redhat.com/442352 http://bugzilla.redhat.com/449910 It appears that utimensat can mistakenly return 280 rather @@ -128,16 +218,26 @@ gl_futimens (int fd, char const *file, struct timespec const timespec[2]) /* The platform lacks an interface to set file timestamps with nanosecond resolution, so do the best we can, discarding any fractional part of the timestamp. */ + + if (adjustment_needed) + { + struct stat st; + if (fd < 0 ? stat (file, &st) : fstat (fd, &st)) + return -1; + if (update_timespec (&st, &ts)) + return 0; + } + { #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES struct timeval timeval[2]; struct timeval const *t; - if (timespec) + if (ts) { - timeval[0].tv_sec = timespec[0].tv_sec; - timeval[0].tv_usec = timespec[0].tv_nsec / 1000; - timeval[1].tv_sec = timespec[1].tv_sec; - timeval[1].tv_usec = timespec[1].tv_nsec / 1000; + timeval[0].tv_sec = ts[0].tv_sec; + timeval[0].tv_usec = ts[0].tv_nsec / 1000; + timeval[1].tv_sec = ts[1].tv_sec; + timeval[1].tv_usec = ts[1].tv_nsec / 1000; t = timeval; } else @@ -185,10 +285,10 @@ gl_futimens (int fd, char const *file, struct timespec const timespec[2]) { struct utimbuf utimbuf; struct utimbuf *ut; - if (timespec) + if (ts) { - utimbuf.actime = timespec[0].tv_sec; - utimbuf.modtime = timespec[1].tv_sec; + utimbuf.actime = ts[0].tv_sec; + utimbuf.modtime = ts[1].tv_sec; ut = &utimbuf; } else @@ -212,9 +312,21 @@ utimens (char const *file, struct timespec const timespec[2]) be TIMESPEC[0] and TIMESPEC[1], respectively. Fail with ENOSYS if the platform does not support changing symlink timestamps. */ int -lutimens (char const *file _UNUSED_PARAMETER_, - struct timespec const timespec[2] _UNUSED_PARAMETER_) +lutimens (char const *file, struct timespec const timespec[2]) { + struct timespec adjusted_timespec[2]; + struct timespec *ts = timespec ? adjusted_timespec : NULL; + int adjustment_needed = 0; + + if (ts) + { + adjusted_timespec[0] = timespec[0]; + adjusted_timespec[1] = timespec[1]; + adjustment_needed = validate_timespec (ts); + } + if (adjustment_needed < 0) + return -1; + /* The Linux kernel did not support symlink timestamps until utimensat, in version 2.6.22, so we don't need to mimic gl_futimens' worry about buggy NFS clients. But we do have to @@ -222,7 +334,7 @@ lutimens (char const *file _UNUSED_PARAMETER_, #if HAVE_UTIMENSAT { - int result = utimensat (AT_FDCWD, file, timespec, AT_SYMLINK_NOFOLLOW); + int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW); # ifdef __linux__ /* Work around a kernel bug: http://bugzilla.redhat.com/442352 @@ -243,16 +355,26 @@ lutimens (char const *file _UNUSED_PARAMETER_, /* The platform lacks an interface to set file timestamps with nanosecond resolution, so do the best we can, discarding any fractional part of the timestamp. */ + + if (adjustment_needed) + { + struct stat st; + if (lstat (file, &st)) + return -1; + if (update_timespec (&st, &ts)) + return 0; + } + #if HAVE_LUTIMES { struct timeval timeval[2]; struct timeval const *t; - if (timespec) + if (ts) { - timeval[0].tv_sec = timespec[0].tv_sec; - timeval[0].tv_usec = timespec[0].tv_nsec / 1000; - timeval[1].tv_sec = timespec[1].tv_sec; - timeval[1].tv_usec = timespec[1].tv_nsec / 1000; + timeval[0].tv_sec = ts[0].tv_sec; + timeval[0].tv_usec = ts[0].tv_nsec / 1000; + timeval[1].tv_sec = ts[1].tv_sec; + timeval[1].tv_usec = ts[1].tv_nsec / 1000; t = timeval; } else diff --git a/modules/utimecmp b/modules/utimecmp index 238ab30d7..7be906b21 100644 --- a/modules/utimecmp +++ b/modules/utimecmp @@ -26,6 +26,9 @@ Makefile.am: Include: "utimecmp.h" +Link: +$(LIB_CLOCK_GETTIME) + License: GPL diff --git a/modules/utimens b/modules/utimens index 48fbe8e43..9917cfc7e 100644 --- a/modules/utimens +++ b/modules/utimens @@ -11,6 +11,11 @@ m4/utimes.m4 Depends-on: dup2 errno +lstat +gettime +stat-time +stdbool +sys_stat sys_time time @@ -22,8 +27,11 @@ Makefile.am: Include: "utimens.h" +Link: +$(LIB_CLOCK_GETTIME) + License: GPL Maintainer: -Paul Eggert, Jim Meyering +Paul Eggert, Jim Meyering, Eric Blake diff --git a/modules/utimens-tests b/modules/utimens-tests index 85b9f657c..746b3acf2 100644 --- a/modules/utimens-tests +++ b/modules/utimens-tests @@ -5,8 +5,6 @@ tests/test-utimens.h tests/test-utimens.c Depends-on: -stat-time -stdbool symlink timespec utimecmp @@ -17,4 +15,4 @@ AC_CHECK_FUNCS_ONCE([usleep]) Makefile.am: TESTS += test-utimens check_PROGRAMS += test-utimens -test_utimens_LDADD = $(LDADD) @LIBINTL@ +test_utimens_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) @LIBINTL@ diff --git a/tests/test-sys_stat.c b/tests/test-sys_stat.c index 4b5eb767b..25fe30d08 100644 --- a/tests/test-sys_stat.c +++ b/tests/test-sys_stat.c @@ -1,5 +1,5 @@ /* Test of substitute. - Copyright (C) 2007-2008 Free Software Foundation, Inc. + Copyright (C) 2007-2009 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -253,6 +253,12 @@ verify (!S_ISWHT (S_IFLNK)); verify (!S_ISWHT (S_IFSOCK)); #endif +#if ((0 <= UTIME_NOW && UTIME_NOW < 1000000000) \ + || (0 <= UTIME_OMIT && UTIME_OMIT < 1000000000) \ + || UTIME_NOW == UTIME_OMIT) +invalid UTIME macros +#endif + /* Check the existence of some types. */ nlink_t t1;