X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Futimens.c;h=c19041140754714a6ddaa17dbad971d14f90d7d8;hb=2726ca8984e8d2d8edf288b4d1d7007bfca0d82e;hp=915f1e7048737a9c16b4ae0ee8a5e0c9481418aa;hpb=68f0f1af0f2cc75615d89234494dca043e99a73d;p=gnulib.git diff --git a/lib/utimens.c b/lib/utimens.c index 915f1e704..c19041140 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-2011 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 @@ -68,65 +67,25 @@ struct utimbuf 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 and earlier fail to bump ctime if mtime is UTIME_OMIT + 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; -static int utimensat_ctime_really; - -/* Determine whether the kernel has a ctime bug. ST1 and ST2 - correspond to stat data before and after a successful time change. - TIMES contains the timestamps that were used during the time change - (mtime will be UTIME_OMIT). Update the cache variable if there is - conclusive evidence of the kernel working or being buggy. Return - true if TIMES has been updated and another kernel call is needed, - whether or not the kernel is known to have the bug. */ -static bool -detect_ctime_bug (struct stat *st1, struct stat *st2, struct timespec times[2]) -{ - struct timespec now; - if (st1->st_ctime != st2->st_ctime - || get_stat_ctime_ns (st1) != get_stat_ctime_ns (st2)) - { - utimensat_ctime_really = 1; - return false; - } - /* The results are inconclusive if the ctime in st1 is within a file - system quantization window of now. For FAT, this is 2 seconds, - for systems with sub-second resolution, a typical resolution is - 10 milliseconds; to be safe we declare an inconsistent result if - ctime is within a 20 millisecond window. Avoid an extra gettime - call if atime makes sense. It is unlikely that the original - ctime is later than now, but rather than deal with the overflow, - we treat that as consistent evidence of the bug. */ - if (times[0].tv_nsec == UTIME_NOW) - now = get_stat_atime (st2); - else - gettime (&now); - if (now.tv_sec < st2->st_ctime - || 2 < now.tv_sec - st2->st_ctime - || (get_stat_ctime_ns (st2) - && now.tv_sec - st2->st_ctime < 2 - && (20000000 < (1000000000 * (now.tv_sec - st2->st_ctime) - + now.tv_nsec - get_stat_ctime_ns (st2))))) - utimensat_ctime_really = -1; - times[1] = get_stat_mtime (st2); - return true; -} #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 a positive value if the timespec needs further adjustment based on stat results: 1 if any - adjustment is needed for utimes, and 2 if mtime was UTIME_OMIT and - an adjustment is needed for utimensat. Return -1, with errno set - to EINVAL, if timespec is out of range. */ + 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 @@ -147,18 +106,18 @@ validate_timespec (struct timespec timespec[2]) { timespec[0].tv_sec = 0; result = 1; + if (timespec[0].tv_nsec == UTIME_OMIT) + utime_omit_count++; } - if (timespec[1].tv_nsec == UTIME_NOW) + 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++; } - else if (timespec[1].tv_nsec == UTIME_OMIT) - { - timespec[1].tv_sec = 0; - result = 2; - } - return result; + return result + (utime_omit_count == 1); } /* Normalize any UTIME_NOW or UTIME_OMIT values in *TS, using stat @@ -205,11 +164,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) { @@ -260,20 +220,27 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) if (0 <= utimensat_works_really) { int result; - struct stat st1; - struct stat st2; - /* Linux kernel 2.6.32 has a bug where it fails to bump ctime if - UTIME_OMIT was used for mtime. It costs time to do an extra - [f]stat up front, so we cache whether the function works. */ - if (utimensat_ctime_really <= 0 && adjustment_needed == 2) +# if __linux__ + /* 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. FIXME: Simplify this in + 2012, when file system bugs are no longer common. */ + if (adjustment_needed == 2) { - if (fd < 0 ? stat (file, &st1) : fstat (fd, &st1)) + if (fd < 0 ? stat (file, &st) : fstat (fd, &st)) return -1; if (ts[0].tv_nsec == UTIME_OMIT) - return 0; - if (utimensat_ctime_really < 0) - ts[1] = get_stat_mtime (&st1); + 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 /* __linux__ */ # if HAVE_UTIMENSAT if (fd < 0) { @@ -292,43 +259,25 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) if (result == 0 || errno != ENOSYS) { utimensat_works_really = 1; - if (result == 0 && utimensat_ctime_really == 0 - && adjustment_needed == 2) - { - /* Perform a followup stat to see if the kernel has - a ctime bug. */ - if (stat (file, &st2)) - return -1; - if (detect_ctime_bug (&st1, &st2, ts)) - result = utimensat (AT_FDCWD, file, ts, 0); - } return result; } } # endif /* HAVE_UTIMENSAT */ # if HAVE_FUTIMENS - { - 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; - /* Work around the same bug as above. */ - if (result == 0 && utimensat_ctime_really == 0 - && adjustment_needed == 2) - { - if (fstat (fd, &st2)) - return -1; - if (detect_ctime_bug (&st1, &st2, ts)) - result = futimens (fd, ts); - } - return result; - } - } + if (result == 0 || errno != ENOSYS) + { + utimensat_works_really = 1; + return result; + } + } # endif /* HAVE_FUTIMENS */ } utimensat_works_really = -1; @@ -341,8 +290,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; @@ -351,7 +300,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; @@ -381,12 +330,53 @@ 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_FUTIMESAT && !FUTIMESAT_NULL_BUG - if (futimesat (fd, NULL, t) == 0) - return 0; -# elif 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 */ @@ -421,28 +411,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 @@ -468,27 +442,34 @@ 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 <= lutimensat_works_really) { int result; - struct stat st1; - struct stat st2; - /* Linux kernel 2.6.32 has a bug where it fails to bump ctime if - UTIME_OMIT was used for mtime. It costs time to do an extra - lstat up front, so we cache whether the function works. */ - if (utimensat_ctime_really <= 0 && adjustment_needed == 2) +# if __linux__ + /* 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. FIXME: Simplify this in 2012, + when file system bugs are no longer common. */ + if (adjustment_needed == 2) { - if (lstat (file, &st1)) + if (lstat (file, &st)) return -1; if (ts[0].tv_nsec == UTIME_OMIT) - return 0; - if (utimensat_ctime_really < 0) - ts[1] = get_stat_mtime (&st1); + 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 /* __linux__ */ result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW); # ifdef __linux__ /* Work around a kernel bug: @@ -505,16 +486,6 @@ lutimens (char const *file, struct timespec const timespec[2]) { utimensat_works_really = 1; lutimensat_works_really = 1; - if (result == 0 && utimensat_ctime_really == 0 - && adjustment_needed == 2) - { - /* Perform a followup stat to see if the kernel has a - ctime bug. */ - if (lstat (file, &st2)) - return -1; - if (detect_ctime_bug (&st1, &st2, ts)) - result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW); - } return result; } } @@ -527,7 +498,7 @@ 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; @@ -538,7 +509,7 @@ lutimens (char const *file, struct timespec const timespec[2]) #if HAVE_LUTIMES && !HAVE_UTIMENSAT { struct timeval timeval[2]; - struct timeval const *t; + struct timeval *t; int result; if (ts) { @@ -561,7 +532,7 @@ lutimens (char const *file, struct timespec const timespec[2]) 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; }