futimens, utimensat: work around Linux bug
authorEric Blake <ebb9@byu.net>
Thu, 17 Dec 2009 14:32:00 +0000 (07:32 -0700)
committerEric Blake <ebb9@byu.net>
Sat, 19 Dec 2009 23:58:45 +0000 (16:58 -0700)
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.

ChangeLog
lib/futimens.c
lib/utimensat.c
m4/futimens.m4
m4/utimensat.m4

index 04bdbcc8aca26ca52e7836e67236b83080d6a709..eb560d53591e5d9ae207f059b34a315a6efd2ee5 100644 (file)
--- 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
index 031a464359ad2fa02fcf0cd46a86daa371a75aaa..e93ffc470a9f46e29b064f38da5a254eb51eb4a7 100644 (file)
@@ -32,6 +32,6 @@ futimens (int fd, struct timespec const times[2])
 {
   /* 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);
 }
index 3808439f55a66e0fc2f3e6be64650412190e54ee..03e65bfae125b1fedcd1548e91e186fb782a6272 100644 (file)
@@ -23,6 +23,8 @@
 #include <errno.h>
 #include <fcntl.h>
 
+#include "stat-time.h"
+#include "timespec.h"
 #include "utimens.h"
 
 #if HAVE_UTIMENSAT
 
 /* 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 @@ int
 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 @@ rpl_utimensat (int fd, char const *file, struct timespec const times[2],
       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;
         }
     }
index 0547e7ad544e786a038bc517d521635c42c36f61..ba5d6b666fba9fbd831cacf00a97852c2e40856f 100644 (file)
@@ -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_DEFUN([gl_FUNC_FUTIMENS],
       [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])
index 2a6033f2c08540db8e2ecfbf78ca9b676882fe7c..21e1ea526d805f88f41962caf9ecffc472cab464 100644 (file)
@@ -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_DEFUN([gl_FUNC_UTIMENSAT],
       [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