X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Futimens.c;h=dd3ec668f3753a7bc9cb70a8c0e15324d0c397c2;hb=1276a2c5f24c0c932426aca9c899fa524d2443f2;hp=bd482d7d01a48f0113801a8dc89d8c6eefe43007;hpb=26c5fd742f9136e2ddbd4695a9172c3fa30ea260;p=gnulib.git diff --git a/lib/utimens.c b/lib/utimens.c index bd482d7d0..dd3ec668f 100644 --- a/lib/utimens.c +++ b/lib/utimens.c @@ -1,7 +1,6 @@ /* Set file access and modification times. - Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free - Software Foundation, Inc. + Copyright (C) 2003-2014 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 the @@ -22,6 +21,7 @@ #include +#define _GL_UTIMENS_INLINE _GL_EXTERN_INLINE #include "utimens.h" #include @@ -53,56 +53,74 @@ struct utimbuf #undef futimens #undef utimensat -#if HAVE_UTIMENSAT || HAVE_FUTIMENS -/* Cache variable for whether syscall works; used to avoid calling the - syscall if we know it will just fail with ENOSYS. 0 = unknown, 1 = - yes, -1 = no. */ -static int utimensat_works_really; -#endif /* HAVE_UTIMENSAT || HAVE_UTIMENSAT */ - /* Solaris 9 mistakenly succeeds when given a non-directory with a trailing slash. Force the use of rpl_stat for a fix. */ #ifndef REPLACE_FUNC_STAT_FILE # define REPLACE_FUNC_STAT_FILE 0 #endif +#if HAVE_UTIMENSAT || HAVE_FUTIMENS +/* Cache variables for whether the utimensat syscall works; used to + avoid calling the syscall if we know it will just fail with ENOSYS, + and to avoid unnecessary work in massaging timestamps if the + syscall will work. Multiple variables are needed, to distinguish + between the following scenarios on Linux: + utimensat doesn't exist, or is in glibc but kernel 2.6.18 fails with ENOSYS + kernel 2.6.22 and earlier rejects AT_SYMLINK_NOFOLLOW + kernel 2.6.25 and earlier reject UTIME_NOW/UTIME_OMIT with non-zero tv_sec + kernel 2.6.32 used with xfs or ntfs-3g fail to honor UTIME_OMIT + utimensat completely works + For each cache variable: 0 = unknown, 1 = yes, -1 = no. */ +static int utimensat_works_really; +static int lutimensat_works_really; +#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */ + /* 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 + work around bugs in utimensat). Return a positive value if the + timespec needs further adjustment based on stat results: 1 if any + adjustment is needed for utimes, and 2 if any adjustment is needed + for Linux utimensat. Return -1, with errno set to EINVAL, if timespec is out of range. */ static int validate_timespec (struct timespec timespec[2]) { int result = 0; + int utime_omit_count = 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)) + && ! (0 <= timespec[0].tv_nsec + && timespec[0].tv_nsec < TIMESPEC_RESOLUTION)) || (timespec[1].tv_nsec != UTIME_NOW && timespec[1].tv_nsec != UTIME_OMIT - && (timespec[1].tv_nsec < 0 || 1000000000 <= timespec[1].tv_nsec))) + && ! (0 <= timespec[1].tv_nsec + && timespec[1].tv_nsec < TIMESPEC_RESOLUTION))) { 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. */ + EINVAL if tv_sec is not 0 when using the flag values of tv_nsec. + Flag a Linux kernel 2.6.32 bug, where an mtime of UTIME_OMIT + fails to bump ctime. */ if (timespec[0].tv_nsec == UTIME_NOW || timespec[0].tv_nsec == UTIME_OMIT) { timespec[0].tv_sec = 0; result = 1; + if (timespec[0].tv_nsec == UTIME_OMIT) + utime_omit_count++; } if (timespec[1].tv_nsec == UTIME_NOW || timespec[1].tv_nsec == UTIME_OMIT) { timespec[1].tv_sec = 0; result = 1; + if (timespec[1].tv_nsec == UTIME_OMIT) + utime_omit_count++; } - return result; + return result + (utime_omit_count == 1); } /* Normalize any UTIME_NOW or UTIME_OMIT values in *TS, using stat @@ -149,11 +167,12 @@ update_timespec (struct stat const *statbuf, struct timespec *ts[2]) Return 0 on success, -1 (setting errno) on failure. */ int -fdutimens (char const *file, int fd, struct timespec const timespec[2]) +fdutimens (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; + struct stat st; if (ts) { @@ -164,18 +183,13 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) if (adjustment_needed < 0) return -1; - /* Require that at least one of FD or FILE are valid. Works around + /* Require that at least one of FD or FILE are potentially valid, to avoid a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather than failing. */ - if (!file) + if (fd < 0 && !file) { - if (fd < 0) - { - errno = EBADF; - return -1; - } - if (dup2 (fd, fd) != fd) - return -1; + errno = EBADF; + return -1; } /* Some Linux-based NFS clients are buggy, and mishandle time stamps @@ -203,10 +217,36 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) #if HAVE_UTIMENSAT || HAVE_FUTIMENS if (0 <= utimensat_works_really) { + int result; +# if __linux__ || __sun + /* As recently as Linux kernel 2.6.32 (Dec 2009), several file + systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT, + but work if both times are either explicitly specified or + UTIME_NOW. Work around it with a preparatory [f]stat prior + to calling futimens/utimensat; fortunately, there is not much + timing impact due to the extra syscall even on file systems + where UTIME_OMIT would have worked. + + The same bug occurs in Solaris 11.1 (Apr 2013). + + FIXME: Simplify this for Linux in 2016 and for Solaris in + 2024, when file system bugs are no longer common. */ + if (adjustment_needed == 2) + { + if (fd < 0 ? stat (file, &st) : fstat (fd, &st)) + return -1; + if (ts[0].tv_nsec == UTIME_OMIT) + ts[0] = get_stat_atime (&st); + else if (ts[1].tv_nsec == UTIME_OMIT) + ts[1] = get_stat_mtime (&st); + /* Note that st is good, in case utimensat gives ENOSYS. */ + adjustment_needed++; + } +# endif # if HAVE_UTIMENSAT if (fd < 0) { - int result = utimensat (AT_FDCWD, file, ts, 0); + result = utimensat (AT_FDCWD, file, ts, 0); # ifdef __linux__ /* Work around a kernel bug: http://bugzilla.redhat.com/442352 @@ -226,22 +266,24 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) } # endif /* HAVE_UTIMENSAT */ # if HAVE_FUTIMENS - { - int result = futimens (fd, ts); + if (0 <= fd) + { + result = futimens (fd, ts); # ifdef __linux__ - /* Work around the same bug as above. */ - if (0 < result) - errno = ENOSYS; + /* Work around the same bug as above. */ + if (0 < result) + errno = ENOSYS; # endif /* __linux__ */ - if (result == 0 || errno != ENOSYS) - { - utimensat_works_really = 1; - return result; - } - } + if (result == 0 || errno != ENOSYS) + { + utimensat_works_really = 1; + return result; + } + } # endif /* HAVE_FUTIMENS */ } utimensat_works_really = -1; + lutimensat_works_really = -1; #endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */ /* The platform lacks an interface to set file timestamps with @@ -250,8 +292,8 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) if (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0)) { - struct stat st; - if (fd < 0 ? stat (file, &st) : fstat (fd, &st)) + if (adjustment_needed != 3 + && (fd < 0 ? stat (file, &st) : fstat (fd, &st))) return -1; if (ts && update_timespec (&st, &ts)) return 0; @@ -260,7 +302,7 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) { #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES struct timeval timeval[2]; - struct timeval const *t; + struct timeval *t; if (ts) { timeval[0].tv_sec = ts[0].tv_sec; @@ -280,9 +322,9 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) } else { - /* If futimesat (above) or futimes fails here, don't try to speed - things up by returning right away. glibc can incorrectly fail - with errno == ENOENT if /proc isn't mounted. Also, Mandrake 10.0 + /* If futimesat or futimes fails here, don't try to speed things + up by returning right away. glibc can incorrectly fail with + errno == ENOENT if /proc isn't mounted. Also, Mandrake 10.0 in high security mode doesn't allow ordinary users to read /proc/self, so glibc incorrectly fails with errno == EACCES. If errno == EIO, EPERM, or EROFS, it's probably safe to fail @@ -290,16 +332,61 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) worth optimizing, and who knows what other messed-up systems are out there? So play it safe and fall back on the code below. */ -# if HAVE_FUTIMES + +# if (HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) || HAVE_FUTIMES +# if HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG +# undef futimes +# define futimes(fd, t) futimesat (fd, NULL, t) +# endif if (futimes (fd, t) == 0) - return 0; + { +# if __linux__ && __GLIBC__ + /* Work around a longstanding glibc bug, still present as + of 2010-12-27. On older Linux kernels that lack both + utimensat and utimes, glibc's futimes rounds instead of + truncating when falling back on utime. The same bug + occurs in futimesat with a null 2nd arg. */ + if (t) + { + bool abig = 500000 <= t[0].tv_usec; + bool mbig = 500000 <= t[1].tv_usec; + if ((abig | mbig) && fstat (fd, &st) == 0) + { + /* If these two subtractions overflow, they'll + track the overflows inside the buggy glibc. */ + time_t adiff = st.st_atime - t[0].tv_sec; + time_t mdiff = st.st_mtime - t[1].tv_sec; + + struct timeval *tt = NULL; + struct timeval truncated_timeval[2]; + truncated_timeval[0] = t[0]; + truncated_timeval[1] = t[1]; + if (abig && adiff == 1 && get_stat_atime_ns (&st) == 0) + { + tt = truncated_timeval; + tt[0].tv_usec = 0; + } + if (mbig && mdiff == 1 && get_stat_mtime_ns (&st) == 0) + { + tt = truncated_timeval; + tt[1].tv_usec = 0; + } + if (tt) + futimes (fd, tt); + } + } +# endif + + return 0; + } # endif } #endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */ if (!file) { -#if ! (HAVE_FUTIMESAT || (HAVE_WORKING_UTIMES && HAVE_FUTIMES)) +#if ! ((HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) \ + || (HAVE_WORKING_UTIMES && HAVE_FUTIMES)) errno = ENOSYS; #endif return -1; @@ -326,28 +413,12 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) } } -/* 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 -- - or a file descriptor that is open on FILE. - If FD is nonnegative, then FILE can be NULL, which means - use just futimes (or equivalent) instead of utimes (or equivalent), - and fail if on an old system without futimes (or equivalent). - If TIMESPEC is null, set the time stamps to the current time. - Return 0 on success, -1 (setting errno) on failure. */ - -int -gl_futimens (int fd, char const *file, struct timespec const timespec[2]) -{ - return fdutimens (file, fd, timespec); -} - /* Set the access and modification time stamps of FILE to be TIMESPEC[0] and TIMESPEC[1], respectively. */ int utimens (char const *file, struct timespec const timespec[2]) { - return fdutimens (file, -1, timespec); + return fdutimens (-1, file, timespec); } /* Set the access and modification time stamps of FILE to be @@ -373,13 +444,39 @@ lutimens (char const *file, struct timespec const timespec[2]) /* 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 + fdutimens' worry about buggy NFS clients. But we do have to worry about bogus return values. */ #if HAVE_UTIMENSAT - if (0 <= utimensat_works_really) + if (0 <= lutimensat_works_really) { - int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW); + int result; +# if __linux__ || __sun + /* As recently as Linux kernel 2.6.32 (Dec 2009), several file + systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT, + but work if both times are either explicitly specified or + UTIME_NOW. Work around it with a preparatory lstat prior to + calling utimensat; fortunately, there is not much timing + impact due to the extra syscall even on file systems where + UTIME_OMIT would have worked. + + The same bug occurs in Solaris 11.1 (Apr 2013). + + FIXME: Simplify this for Linux in 2016 and for Solaris in + 2024, when file system bugs are no longer common. */ + if (adjustment_needed == 2) + { + if (lstat (file, &st)) + return -1; + if (ts[0].tv_nsec == UTIME_OMIT) + ts[0] = get_stat_atime (&st); + else if (ts[1].tv_nsec == UTIME_OMIT) + ts[1] = get_stat_mtime (&st); + /* Note that st is good, in case utimensat gives ENOSYS. */ + adjustment_needed++; + } +# endif + result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW); # ifdef __linux__ /* Work around a kernel bug: http://bugzilla.redhat.com/442352 @@ -394,10 +491,11 @@ lutimens (char const *file, struct timespec const timespec[2]) if (result == 0 || errno != ENOSYS) { utimensat_works_really = 1; + lutimensat_works_really = 1; return result; } } - utimensat_works_really = -1; + lutimensat_works_really = -1; #endif /* HAVE_UTIMENSAT */ /* The platform lacks an interface to set file timestamps with @@ -406,16 +504,19 @@ lutimens (char const *file, struct timespec const timespec[2]) if (adjustment_needed || REPLACE_FUNC_STAT_FILE) { - if (lstat (file, &st)) + if (adjustment_needed != 3 && lstat (file, &st)) return -1; if (ts && update_timespec (&st, &ts)) return 0; } -#if HAVE_LUTIMES + /* On Linux, lutimes is a thin wrapper around utimensat, so there is + no point trying lutimes if utimensat failed with ENOSYS. */ +#if HAVE_LUTIMES && !HAVE_UTIMENSAT { struct timeval timeval[2]; - struct timeval const *t; + struct timeval *t; + int result; if (ts) { timeval[0].tv_sec = ts[0].tv_sec; @@ -427,15 +528,17 @@ lutimens (char const *file, struct timespec const timespec[2]) else t = NULL; - return lutimes (file, t); + result = lutimes (file, t); + if (result == 0 || errno != ENOSYS) + return result; } -#endif /* HAVE_LUTIMES */ +#endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */ /* Out of luck for symlinks, but we still handle regular files. */ if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st)) return -1; if (!S_ISLNK (st.st_mode)) - return fdutimens (file, -1, ts); + return fdutimens (-1, file, ts); errno = ENOSYS; return -1; }