changeset 12466:e31a9e90d604

utimens: check for ctime update futimens/utimensat on Linux fails to bump ctime if mtime is UTIME_OMIT and atime is specified. * tests/test-utimens-common.h (check_ctime): Define. * tests/test-utimens.h (test_utimens): Expose the Linux bug. * tests/test-futimens.h (test_futimens): Likewise. * tests/test-lutimens.h (test_lutimens): Likewise. * doc/posix-functions/futimens.texi (futimens): Document the bug. * doc/posix-functions/utimensat.texi (utimensat): Likewise. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Thu, 17 Dec 2009 12:30:47 -0700
parents 196d4470a4b5
children 912bdb1c31fb
files ChangeLog doc/posix-functions/futimens.texi doc/posix-functions/utimensat.texi tests/test-futimens.h tests/test-lutimens.h tests/test-utimens-common.h tests/test-utimens.h
diffstat 7 files changed, 144 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2009-12-19  Eric Blake  <ebb9@byu.net>
+
+	utimens: check for ctime update
+	* tests/test-utimens-common.h (check_ctime): Define.
+	* tests/test-utimens.h (test_utimens): Expose the Linux bug.
+	* tests/test-futimens.h (test_futimens): Likewise.
+	* tests/test-lutimens.h (test_lutimens): Likewise.
+	* doc/posix-functions/futimens.texi (futimens): Document the bug.
+	* doc/posix-functions/utimensat.texi (utimensat): Likewise.
+
 2009-12-19  Bruno Haible  <bruno@clisp.org>
 
 	dprintf-posix: Check against memory leak fixed on 2009-12-15.
--- a/doc/posix-functions/futimens.texi
+++ b/doc/posix-functions/futimens.texi
@@ -24,6 +24,10 @@
 the @code{tv_sec} argument to be 0, and don't necessarily handle all
 file permissions in the manner required by POSIX:
 Linux kernel 2.6.25.
+@item
+When using @code{UTIME_OMIT} for the modification time, but specifying
+an access time, some systems fail to update the change time:
+Linux kernel 2.6.32.
 @end itemize
 
 Portability problems not fixed by Gnulib:
--- a/doc/posix-functions/utimensat.texi
+++ b/doc/posix-functions/utimensat.texi
@@ -22,10 +22,18 @@
 @code{ENOSYS} on some platforms:
 Linux kernel 2.6.21.
 @item
+This function fails with @code{ENOSYS} if passed the flag
+@code{AT_SYMLINK_NOFOLLOW} on a regular file:
+Linux kernel 2.6.22.
+@item
 When using @code{UTIME_OMIT} or @code{UTIME_NOW}, some systems require
 the @code{tv_sec} argument to be 0, and don't necessarily handle all
 file permissions in the manner required by POSIX:
 Linux kernel 2.6.25.
+@item
+When using @code{UTIME_OMIT} for the modification time, but specifying
+an access time, some systems fail to update the change time:
+Linux kernel 2.6.32.
 @end itemize
 
 Portability problems not fixed by Gnulib:
--- a/tests/test-futimens.h
+++ b/tests/test-futimens.h
@@ -53,6 +53,10 @@
      UTIMECMP_TRUNCATE_SOURCE to compensate, with st1 as the
      source.  */
   ASSERT (0 <= utimecmp (BASE "file", &st2, &st1, UTIMECMP_TRUNCATE_SOURCE));
+  if (check_ctime)
+    ASSERT (st1.st_ctime < st2.st_ctime
+            || (st1.st_ctime == st2.st_ctime
+                && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
   {
     /* On some NFS systems, the 'now' timestamp of creat or a NULL
        timespec is determined by the server, but the 'now' timestamp
@@ -101,17 +105,40 @@
     ASSERT (st2.st_mtime == Y2K);
     ASSERT (0 <= get_stat_mtime_ns (&st2));
     ASSERT (get_stat_mtime_ns (&st2) < BILLION);
+    if (check_ctime)
+      ASSERT (st1.st_ctime < st2.st_ctime
+              || (st1.st_ctime == st2.st_ctime
+                  && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
   }
 
   /* Play with UTIME_OMIT, UTIME_NOW.  */
   {
+    struct stat st3;
     struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } };
+    nap ();
+    ASSERT (func (fd, ts) == 0);
+    ASSERT (fstat (fd, &st3) == 0);
+    ASSERT (st3.st_atime == Y2K);
+    ASSERT (0 <= get_stat_atime_ns (&st3));
+    ASSERT (get_stat_atime_ns (&st3) <= BILLION / 2);
+    ASSERT (utimecmp (BASE "file", &st1, &st3, 0) <= 0);
+    if (check_ctime)
+      ASSERT (st2.st_ctime < st3.st_ctime
+              || (st2.st_ctime == st3.st_ctime
+                  && get_stat_ctime_ns (&st2) < get_stat_ctime_ns (&st3)));
+    nap ();
+    ts[0].tv_nsec = 0;
+    ts[1].tv_nsec = UTIME_OMIT;
     ASSERT (func (fd, ts) == 0);
     ASSERT (fstat (fd, &st2) == 0);
-    ASSERT (st2.st_atime == Y2K);
-    ASSERT (0 <= get_stat_atime_ns (&st2));
-    ASSERT (get_stat_atime_ns (&st2) <= BILLION / 2);
-    ASSERT (utimecmp (BASE "file", &st1, &st2, 0) <= 0);
+    ASSERT (st2.st_atime == BILLION);
+    ASSERT (get_stat_atime_ns (&st2) == 0);
+    ASSERT (st3.st_mtime == st2.st_mtime);
+    ASSERT (get_stat_mtime_ns (&st3) == get_stat_mtime_ns (&st2));
+    if (check_ctime)
+      ASSERT (st3.st_ctime < st2.st_ctime
+              || (st3.st_ctime == st2.st_ctime
+                  && get_stat_ctime_ns (&st3) < get_stat_ctime_ns (&st2)));
   }
 
   /* Cleanup.  */
--- a/tests/test-lutimens.h
+++ b/tests/test-lutimens.h
@@ -54,11 +54,16 @@
   }
   {
     struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
+    nap ();
     ASSERT (func (BASE "file", ts) == 0);
   }
-  ASSERT (stat (BASE "file", &st1) == 0);
-  ASSERT (st1.st_atime == Y2K);
-  ASSERT (st1.st_mtime == Y2K);
+  ASSERT (stat (BASE "file", &st2) == 0);
+  ASSERT (st2.st_atime == Y2K);
+  ASSERT (st2.st_mtime == Y2K);
+  if (check_ctime)
+    ASSERT (st1.st_ctime < st2.st_ctime
+            || (st1.st_ctime == st2.st_ctime
+                && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
 
   /* Play with symlink timestamps.  */
   if (symlink (BASE "file", BASE "link"))
@@ -98,6 +103,8 @@
   if (st1.st_atime != st2.st_atime
       || get_stat_atime_ns (&st1) != get_stat_atime_ns (&st2))
     atime_supported = false;
+  ASSERT (st1.st_ctime == st2.st_ctime);
+  ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
 
   /* Invalid arguments.  */
   {
@@ -123,6 +130,7 @@
   /* Set both times.  */
   {
     struct timespec ts[2] = { { Y2K, BILLION / 2 - 1 }, { Y2K, BILLION - 1 } };
+    nap ();
     ASSERT (func (BASE "link", ts) == 0);
     ASSERT (lstat (BASE "link", &st2) == 0);
     if (atime_supported)
@@ -134,20 +142,46 @@
     ASSERT (st2.st_mtime == Y2K);
     ASSERT (0 <= get_stat_mtime_ns (&st2));
     ASSERT (get_stat_mtime_ns (&st2) < BILLION);
+    if (check_ctime)
+      ASSERT (st1.st_ctime < st2.st_ctime
+              || (st1.st_ctime == st2.st_ctime
+                  && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
   }
 
   /* Play with UTIME_OMIT, UTIME_NOW.  */
   {
+    struct stat st3;
     struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } };
+    nap ();
+    ASSERT (func (BASE "link", ts) == 0);
+    ASSERT (lstat (BASE "link", &st3) == 0);
+    if (atime_supported)
+      {
+        ASSERT (st3.st_atime == Y2K);
+        ASSERT (0 <= get_stat_atime_ns (&st3));
+        ASSERT (get_stat_atime_ns (&st3) < BILLION / 2);
+      }
+    ASSERT (utimecmp (BASE "link", &st1, &st3, 0) <= 0);
+    if (check_ctime)
+      ASSERT (st2.st_ctime < st3.st_ctime
+              || (st2.st_ctime == st3.st_ctime
+                  && get_stat_ctime_ns (&st2) < get_stat_ctime_ns (&st3)));
+    nap ();
+    ts[0].tv_nsec = 0;
+    ts[1].tv_nsec = UTIME_OMIT;
     ASSERT (func (BASE "link", ts) == 0);
     ASSERT (lstat (BASE "link", &st2) == 0);
     if (atime_supported)
       {
-        ASSERT (st2.st_atime == Y2K);
-        ASSERT (0 <= get_stat_atime_ns (&st2));
-        ASSERT (get_stat_atime_ns (&st2) < BILLION / 2);
+        ASSERT (st2.st_atime == BILLION);
+        ASSERT (get_stat_atime_ns (&st2) == 0);
       }
-    ASSERT (utimecmp (BASE "link", &st1, &st2, 0) <= 0);
+    ASSERT (st3.st_mtime == st2.st_mtime);
+    ASSERT (get_stat_mtime_ns (&st3) == get_stat_mtime_ns (&st2));
+    if (check_ctime)
+      ASSERT (st3.st_ctime < st2.st_ctime
+              || (st3.st_ctime == st2.st_ctime
+                  && get_stat_ctime_ns (&st3) < get_stat_ctime_ns (&st2)));
   }
 
   /* Symlink to directory.  */
--- a/tests/test-utimens-common.h
+++ b/tests/test-utimens-common.h
@@ -19,14 +19,14 @@
 #ifndef GL_TEST_UTIMENS_COMMON
 # define GL_TEST_UTIMENS_COMMON
 
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
+# include <fcntl.h>
+# include <errno.h>
+# include <string.h>
+# include <unistd.h>
 
-#include "stat-time.h"
-#include "timespec.h"
-#include "utimecmp.h"
+# include "stat-time.h"
+# include "timespec.h"
+# include "utimecmp.h"
 
 enum {
   BILLION = 1000 * 1000 * 1000,
@@ -62,9 +62,18 @@
      a quantization boundary equal to the resolution.  Our usage of
      utimecmp allows equality, so no need to waste 980 milliseconds
      if the replacement usleep rounds to 1 second.  */
-#if HAVE_USLEEP
+# if HAVE_USLEEP
   usleep (20 * 1000); /* 20 milliseconds.  */
-#endif
+# endif
 }
 
+# if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
+/* Skip ctime tests on native Windows, since it is either a copy of
+   mtime or birth time (depending on the file system), rather than a
+   properly tracked change time.  */
+#  define check_ctime 0
+# else
+#  define check_ctime 1
+# endif
+
 #endif /* GL_TEST_UTIMENS_COMMON */
--- a/tests/test-utimens.h
+++ b/tests/test-utimens.h
@@ -37,6 +37,10 @@
   ASSERT (func (BASE "file", NULL) == 0);
   ASSERT (stat (BASE "file", &st2) == 0);
   ASSERT (0 <= utimecmp (BASE "file", &st2, &st1, UTIMECMP_TRUNCATE_SOURCE));
+  if (check_ctime)
+    ASSERT (st1.st_ctime < st2.st_ctime
+            || (st1.st_ctime == st2.st_ctime
+                && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
   {
     /* On some NFS systems, the 'now' timestamp of creat or a NULL
        timespec is determined by the server, but the 'now' timestamp
@@ -97,18 +101,41 @@
     ASSERT (st2.st_mtime == Y2K);
     ASSERT (0 <= get_stat_mtime_ns (&st2));
     ASSERT (get_stat_mtime_ns (&st2) < BILLION);
+    if (check_ctime)
+      ASSERT (st1.st_ctime < st2.st_ctime
+              || (st1.st_ctime == st2.st_ctime
+                  && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
   }
 
   /* Play with UTIME_OMIT, UTIME_NOW.  */
   {
+    struct stat st3;
     struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } };
+    nap ();
+    ASSERT (func (BASE "file", ts) == 0);
+    ASSERT (stat (BASE "file", &st3) == 0);
+    ASSERT (st3.st_atime == Y2K);
+    ASSERT (0 <= get_stat_atime_ns (&st3));
+    ASSERT (get_stat_atime_ns (&st3) < BILLION / 2);
+    /* See comment above about this utimecmp call.  */
+    ASSERT (0 <= utimecmp (BASE "file", &st3, &st1, UTIMECMP_TRUNCATE_SOURCE));
+    if (check_ctime)
+      ASSERT (st2.st_ctime < st3.st_ctime
+              || (st2.st_ctime == st3.st_ctime
+                  && get_stat_ctime_ns (&st2) < get_stat_ctime_ns (&st3)));
+    nap ();
+    ts[0].tv_nsec = 0;
+    ts[1].tv_nsec = UTIME_OMIT;
     ASSERT (func (BASE "file", ts) == 0);
     ASSERT (stat (BASE "file", &st2) == 0);
-    ASSERT (st2.st_atime == Y2K);
-    ASSERT (0 <= get_stat_atime_ns (&st2));
-    ASSERT (get_stat_atime_ns (&st2) < BILLION / 2);
-    /* See comment above about this utimecmp call.  */
-    ASSERT (0 <= utimecmp (BASE "file", &st2, &st1, UTIMECMP_TRUNCATE_SOURCE));
+    ASSERT (st2.st_atime == BILLION);
+    ASSERT (get_stat_atime_ns (&st2) == 0);
+    ASSERT (st3.st_mtime == st2.st_mtime);
+    ASSERT (get_stat_mtime_ns (&st3) == get_stat_mtime_ns (&st2));
+    if (check_ctime)
+      ASSERT (st3.st_ctime < st2.st_ctime
+              || (st3.st_ctime == st2.st_ctime
+                  && get_stat_ctime_ns (&st3) < get_stat_ctime_ns (&st2)));
   }
 
   /* Make sure this dereferences symlinks.  */