changeset 12468:ff7c15d38975

futimens, utimensat: work around Linux bug futimens is trivial - let fdutimens do the work. utimensat is tougher: we don't want to call into local_utimensat, because that can cause unnecessary chdir. So we have to repeat the logic from utimens.c. * m4/futimens.m4 (gl_FUNC_FUTIMENS): Detect ctime bug. * m4/utimensat.m4 (gl_FUNC_UTIMENSAT): Likewise. * lib/utimensat.c (rpl_utimensat): Work around it. * lib/futimens.c (rpl_futimens): Adjust comment.
author Eric Blake <ebb9@byu.net>
date Thu, 17 Dec 2009 07:32:00 -0700
parents 912bdb1c31fb
children 8b48f88c47f1
files ChangeLog lib/futimens.c lib/utimensat.c m4/futimens.m4 m4/utimensat.m4
diffstat 5 files changed, 111 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2009-12-19  Eric Blake  <ebb9@byu.net>
 
+	futimens, utimensat: work around Linux bug
+	* m4/futimens.m4 (gl_FUNC_FUTIMENS): Detect ctime bug.
+	* m4/utimensat.m4 (gl_FUNC_UTIMENSAT): Likewise.
+	* lib/utimensat.c (rpl_utimensat): Work around it.
+	* lib/futimens.c (rpl_futimens): Adjust comment.
+
 	utimens: work around Linux ctime bug
 	* lib/utimens.c (detect_ctime_bug): New helper function.
 	(update_timespec): Differentiate between workaround needed for
--- a/lib/futimens.c
+++ b/lib/futimens.c
@@ -32,6 +32,6 @@
 {
   /* fdutimens also works around bugs in native futimens, when running
      with glibc compiled against newer headers but on a Linux kernel
-     older than 2.6.26.  */
+     older than 2.6.32.  */
   return fdutimens (NULL, fd, times);
 }
--- a/lib/utimensat.c
+++ b/lib/utimensat.c
@@ -23,6 +23,8 @@
 #include <errno.h>
 #include <fcntl.h>
 
+#include "stat-time.h"
+#include "timespec.h"
 #include "utimens.h"
 
 #if HAVE_UTIMENSAT
@@ -31,10 +33,11 @@
 
 /* If we have a native utimensat, but are compiling this file, then
    utimensat was defined to rpl_utimensat by our replacement
-   sys/stat.h.  We assume the native version might fail with ENOSYS
-   (as is the case when using newer glibc but older Linux kernel).  In
-   this scenario, rpl_utimensat checks whether the native version is
-   usable, and local_utimensat provides the fallback manipulation.  */
+   sys/stat.h.  We assume the native version might fail with ENOSYS,
+   or succeed without properly affecting ctime (as is the case when
+   using newer glibc but older Linux kernel).  In this scenario,
+   rpl_utimensat checks whether the native version is usable, and
+   local_utimensat provides the fallback manipulation.  */
 
 static int local_utimensat (int, char const *, struct timespec const[2], int);
 # define AT_FUNC_NAME local_utimensat
@@ -45,10 +48,30 @@
 rpl_utimensat (int fd, char const *file, struct timespec const times[2],
                int flag)
 {
+  /* See comments in utimens.c for details.  */
   static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no.  */
+  static int utimensat_ctime_really; /* 0 = unknown, 1 = yes, -1 = no.  */
   if (0 <= utimensat_works_really)
     {
-      int result = utimensat (fd, file, times, flag);
+      int result;
+      struct stat st1;
+      struct stat st2;
+      struct timespec ts[2];
+      /* Linux kernel 2.6.32 has a bug where mtime of UTIME_OMIT fails
+         to change ctime.  */
+      if (utimensat_ctime_really <= 0 && times
+          && times[0].tv_nsec != UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT)
+        {
+          if (fstatat (fd, file, &st1, flag))
+            return -1;
+          if (utimensat_ctime_really < 0)
+            {
+              ts[0] = times[0];
+              ts[1] = get_stat_mtime (&st1);
+              times = ts;
+            }
+        }
+      result = utimensat (fd, file, times, flag);
       /* Linux kernel 2.6.25 has a bug where it returns EINVAL for
          UTIME_NOW or UTIME_OMIT with non-zero tv_sec, which
          local_utimensat works around.  Meanwhile, EINVAL for a bad
@@ -59,6 +82,37 @@
       if (result == 0 || (errno != ENOSYS && errno != EINVAL))
         {
           utimensat_works_really = 1;
+          if (result == 0 && utimensat_ctime_really == 0 && times
+              && times[0].tv_nsec != UTIME_OMIT
+              && times[1].tv_nsec == UTIME_OMIT)
+            {
+              /* Perform a followup [l]stat.  See detect_ctime_bug in
+                 utimens.c for more details.  */
+              struct timespec now;
+              if (fstatat (fd, file, &st2, flag))
+                return -1;
+              if (st1.st_ctime != st2.st_ctime
+                  || get_stat_ctime_ns (&st1) != get_stat_ctime_ns (&st2))
+                {
+                  utimensat_ctime_really = 1;
+                  return result;
+                }
+              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;
+              ts[0] = times[0];
+              ts[1] = get_stat_mtime (&st2);
+              result = utimensat (fd, file, ts, flag);
+            }
           return result;
         }
     }
--- a/m4/futimens.m4
+++ b/m4/futimens.m4
@@ -1,4 +1,4 @@
-# serial 1
+# serial 2
 # See if we need to provide futimens replacement.
 
 dnl Copyright (C) 2009 Free Software Foundation, Inc.
@@ -22,17 +22,32 @@
       [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
 #include <fcntl.h>
 #include <sys/stat.h>
+#include <unistd.h>
+]], [[struct timespec ts[2] = { { 1, UTIME_OMIT }, { 1, UTIME_NOW } };
+      int fd = creat ("conftest.file", 0600);
+      struct stat st;
+      if (fd < 0) return 1;
+      if (futimens (AT_FDCWD, NULL)) return 2;
+      if (futimens (fd, ts)) return 3;
+      sleep (1);
+      ts[0].tv_nsec = UTIME_NOW;
+      ts[1].tv_nsec = UTIME_OMIT;
+      if (futimens (fd, ts)) return 4;
+      if (fstat (fd, &st)) return 5;
+      if (st.st_ctime < st.st_atime) return 6;
+      ]])],
+         [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
 #ifdef __linux__
-/* The Linux kernel added futimens in 2.6.22, but it had bugs until 2.6.26.
-   Always replace futimens to support older kernels.  */
+/* The Linux kernel added futimens in 2.6.22, but has bugs with UTIME_OMIT
+   in 2.6.32.  Always replace futimens to support older kernels.  */
 choke me
 #endif
-]], [[struct timespec ts[2] = { { 1, UTIME_OMIT }, { 1, UTIME_OMIT } };
-      if (futimens (AT_FDCWD, NULL)) return 1;
-      return futimens (open (".", O_RDONLY), ts);]])],
-         [gl_cv_func_futimens_works=yes],
-         [gl_cv_func_futimens_works="needs runtime check"],
-         [gl_cv_func_futimens_works="guessing no"])])
+      ]])],
+           [gl_cv_func_futimens_works=yes],
+           [gl_cv_func_futimens_works="needs runtime check"])],
+         [gl_cv_func_futimens_works=no],
+         [gl_cv_func_futimens_works="guessing no"])
+      rm -f conftest.file])
     if test "$gl_cv_func_futimens_works" != yes; then
       REPLACE_FUTIMENS=1
       AC_LIBOBJ([futimens])
--- a/m4/utimensat.m4
+++ b/m4/utimensat.m4
@@ -1,4 +1,4 @@
-# serial 1
+# serial 2
 # See if we need to provide utimensat replacement.
 
 dnl Copyright (C) 2009 Free Software Foundation, Inc.
@@ -22,15 +22,29 @@
       [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
 #include <fcntl.h>
 #include <sys/stat.h>
+#include <unistd.h>
+]], [[struct timespec ts[2] = { { 1, UTIME_OMIT }, { 1, UTIME_NOW } };
+      struct stat st;
+      const char *f = "conftest.file";
+      if (close (creat (f, 0600))) return 1;
+      if (utimensat (AT_FDCWD, f, NULL, AT_SYMLINK_NOFOLLOW)) return 2;
+      if (utimensat (AT_FDCWD, f, ts, 0)) return 3;
+      sleep (1);
+      ts[0].tv_nsec = UTIME_NOW;
+      ts[1].tv_nsec = UTIME_OMIT;
+      if (utimensat (AT_FDCWD, f, ts, 0)) return 4;
+      if (stat (f, &st)) return 5;
+      if (st.st_ctime < st.st_atime) return 6;]])],
+         [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
 #ifdef __linux__
-/* The Linux kernel added utimensat in 2.6.22, but it had bugs until 2.6.26.
-   Always replace utimensat to support older kernels.  */
+/* The Linux kernel added utimensat in 2.6.22, but has bugs with UTIME_OMIT
+   in 2.6.32.  Always replace utimensat to support older kernels.  */
 choke me
 #endif
-]], [[struct timespec ts[2] = { { 1, UTIME_OMIT }, { 1, UTIME_OMIT } };
-      return utimensat (AT_FDCWD, ".", NULL, AT_SYMLINK_NOFOLLOW);]])],
-         [gl_cv_func_utimensat_works=yes],
-         [gl_cv_func_utimensat_works="needs runtime check"],
+      ]])],
+           [gl_cv_func_utimensat_works=yes],
+           [gl_cv_func_utimensat_works="needs runtime check"])],
+         [gl_cv_func_utimensat_works=no],
          [gl_cv_func_utimensat_works="guessing no"])])
     if test "$gl_cv_func_utimensat_works" != yes; then
       REPLACE_UTIMENSAT=1