/* Set file access and modification times.
- Copyright (C) 2003-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
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
{
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
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)
{
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)
{
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;
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;
{
#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;
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 */
}
}
-/* 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
/* 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:
{
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;
}
}
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 && !HAVE_UTIMENSAT
{
struct timeval timeval[2];
- struct timeval const *t;
+ struct timeval *t;
int result;
if (ts)
{
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;
}