#undef futimens
#undef utimensat
-#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.
- There are some Linux kernel versions where a flag of 0 passes, but
- not AT_SYMLINK_NOFOLLOW. 0 = unknown, 1 = yes, -1 = no. */
-static int utimensat_works_really;
-static int lutimensat_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 and earlier fail to bump ctime if mtime is 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 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. */
+ 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. */
static int
validate_timespec (struct timespec timespec[2])
{
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[1].tv_nsec == UTIME_NOW
- || timespec[1].tv_nsec == UTIME_OMIT)
+ if (timespec[1].tv_nsec == UTIME_NOW)
{
timespec[1].tv_sec = 0;
result = 1;
}
+ else if (timespec[1].tv_nsec == UTIME_OMIT)
+ {
+ timespec[1].tv_sec = 0;
+ result = 2;
+ }
return result;
}
#if HAVE_UTIMENSAT || HAVE_FUTIMENS
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 (fd < 0 ? stat (file, &st1) : fstat (fd, &st1))
+ return -1;
+ if (ts[0].tv_nsec == UTIME_OMIT)
+ return 0;
+ if (utimensat_ctime_really < 0)
+ ts[1] = get_stat_mtime (&st1);
+ }
# 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
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
{
- int result = futimens (fd, ts);
+ result = futimens (fd, ts);
# ifdef __linux__
/* Work around the same bug as above. */
if (0 < result)
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 HAVE_UTIMENSAT
if (0 <= lutimensat_works_really)
{
- int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
+ 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 (lstat (file, &st1))
+ return -1;
+ if (ts[0].tv_nsec == UTIME_OMIT)
+ return 0;
+ if (utimensat_ctime_really < 0)
+ ts[1] = get_stat_mtime (&st1);
+ }
+ result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
# ifdef __linux__
/* Work around a kernel bug:
http://bugzilla.redhat.com/442352
{
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;
}
}