utimensat: work around Solaris 9 bug
authorEric Blake <ebb9@byu.net>
Tue, 20 Oct 2009 22:47:36 +0000 (16:47 -0600)
committerEric Blake <ebb9@byu.net>
Wed, 21 Oct 2009 03:21:52 +0000 (21:21 -0600)
utimes("file/",times) mistakenly succeeds.  This commit doesn't fix
utimes, but does make utimensat be careful before calling utimes.
The test is now enhanced to test trailing slashes and directories.

Meanwhile, cygwin 1.5 stat() on a directory changes atime (it does
a readdir under the hood to populate st_nlink), so only mtime of
a directory is reliable enough for testing.  Cygwin 1.7 no longer
has this problem, because it no longer wastes time on st_nlink.

* lib/utimens.c (fdutimens, lutimens): Force a stat if platform
has trailing slash bugs.
* tests/test-lutimens.h (test_lutimens): Enhance test.
* tests/test-utimens.h (test_utimens): Likewise.
* doc/posix-functions/utime.texi (utime): Document the bug.
* doc/posix-functions/utimes.texi (utimes): Likewise.
* doc/posix-functions/utimensat.texi (utimensat): Likewise.
* doc/glibc-functions/futimesat.texi (futimesat): Likewise.
* doc/glibc-functions/lutimes.texi (lutimes): Mention utimens.
* doc/posix-functions/futimens.texi (futimens): Mention
limitation.

Signed-off-by: Eric Blake <ebb9@byu.net>
ChangeLog
doc/glibc-functions/futimesat.texi
doc/glibc-functions/lutimes.texi
doc/posix-functions/futimens.texi
doc/posix-functions/utime.texi
doc/posix-functions/utimensat.texi
doc/posix-functions/utimes.texi
lib/utimens.c
tests/test-lutimens.h
tests/test-utimens.h

index fe0baaea7afcfb792d22292a9c47b460b3c2b3b5..327136602c19eda787d2c95da9c5d11648246f58 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
 2009-10-20  Eric Blake  <ebb9@byu.net>
 
+       utimensat: work around Solaris 9 bug
+       * lib/utimens.c (fdutimens, lutimens): Force a stat if platform
+       has trailing slash bugs.
+       * tests/test-lutimens.h (test_lutimens): Enhance test.
+       * tests/test-utimens.h (test_utimens): Likewise.
+       * doc/posix-functions/utime.texi (utime): Enhance documentation.
+       * doc/posix-functions/utimes.texi (utimes): Likewise.
+       * doc/posix-functions/utimensat.texi (utimensat): Likewise.
+       * doc/glibc-functions/futimesat.texi (futimesat): Likewise.
+       * doc/glibc-functions/lutimes.texi (lutimes): Likewise.
+       * doc/posix-functions/futimens.texi (futimens): Likewise.
+
        fdutimensat: new module
        * modules/fdutimensat: New file.
        * lib/fdutimensat.c (fdutimensat): Likewise.
index dbd3d1de3e7ad330aa0e0e30eb3d45afe4f42576..109e4122fab44d35796fe21322eab5b6daf354ea 100644 (file)
@@ -16,6 +16,10 @@ glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 8, Cygwin 1.5.x, mingw,
 Interix 3.5, BeOS.
 @item
+On some platforms, this function mis-handles trailing slash:
+Solaris 9.
+@item
 This function cannot set full timestamp resolution.  Use
-@code{file ? utimensat(fd,file,times,0) : futimens(fd,times)} instead.
+@code{file ? utimensat(fd,file,times,0) : futimens(fd,times)}, or the
+gnulib module fdutimensat, instead.
 @end itemize
index 2971c99ece9716ea17a3aeb1eba4f5515cb4b68d..2371c512539183766894f3f73e5c0126c7545083 100644 (file)
@@ -16,7 +16,8 @@ MacOS X 10.3, OpenBSD 3.8, AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1,
 Solaris 10, mingw, Interix 3.5, BeOS.
 @item
 This function cannot set full timestamp resolution.  Use
-@code{utimensat(AT_FDCWD,file,times,AT_SYMLINK_NOFOLLOW)} instead.
+@code{utimensat(AT_FDCWD,file,times,AT_SYMLINK_NOFOLLOW)}, or the
+gnulib module utimens, instead.
 @item
 The mere act of using @code{lstat} modifies the access time of
 symlinks on some platforms, so @code{lutimes} can only effectively
index 1434033ead90f8c556c42dec5021b49d7b60ff7f..fee3d087fec0e57b60264865b58db97174fed130 100644 (file)
@@ -13,6 +13,8 @@ This function is missing on some platforms:
 glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw,
 Interix 3.5, BeOS.
+However, the replacement function may end up truncating timestamps to
+less resolution than supported by the file system.
 @item
 This function returns a bogus value instead of failing with
 @code{ENOSYS} on some platforms:
@@ -29,5 +31,10 @@ Portability problems not fixed by Gnulib:
 @item
 Some platforms lack the ability to change the timestamps of a file
 descriptor, so the replacement can fail with @code{ENOSYS}; the gnulib
-module @samp{utimens} provides a more reliable interface @code{gl_futimens}.
+module @samp{utimens} provides a more reliable interface @code{fdutimens}.
+@item
+The mere act of using @code{stat} modifies the access time of
+directories on some platforms, so @code{utimensat} can only
+effectively change directory modification time:
+Cygwin 1.5.x.
 @end itemize
index d14685c1bc28c9d77cf7c87325930068d48634a9..f50af81e1edd0d7f5f45257ba8e09a92da22a2f4 100644 (file)
@@ -16,8 +16,12 @@ file's timestamp to the current time.
 Portability problems not fixed by Gnulib:
 @itemize
 @item
+On some platforms, this function mis-handles trailing slash:
+Solaris 9.
+@item
 This function cannot set full timestamp resolution.  Use
-@code{utimensat(AT_FDCWD,file,times,0)} instead.
+@code{utimensat(AT_FDCWD,file,times,0)}, or the gnulib module utimens,
+instead.
 @item
 On some platforms, the prototype for @code{utime} omits @code{const}
 for the second argument.  Fortunately, the argument is not modified,
index 7164965d665094fe90f87b5617593265c3de06fb..67f50785acf97264403dd1a1937863c4b84c2845 100644 (file)
@@ -13,6 +13,10 @@ This function is missing on some platforms:
 glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw,
 Interix 3.5, BeOS.
+However, the replacement function may end up truncating timestamps to
+less resolution than supported by the file system.  Furthermore, the
+replacement function is not safe to be used in libraries and is not
+multithread-safe.
 @item
 This function returns a bogus value instead of failing with
 @code{ENOSYS} on some platforms:
@@ -35,6 +39,11 @@ The mere act of using @code{lstat} modifies the access time of
 symlinks on some platforms, so @code{utimensat} with
 @code{AT_SYMLINK_NOFOLLOW} can only effectively change modification time:
 Cygwin.
+@item
+The mere act of using @code{stat} modifies the access time of
+directories on some platforms, so @code{utimensat} can only
+effectively change directory modification time:
+Cygwin 1.5.x.
 @end itemize
 
-The gnulib module utimens provides a similar interface.
+The gnulib module fdutimensat provides a similar interface.
index 5f6cb7b74b7b7f9ba404f8c63806c5731b9d2782..390aa988ab10ea0e3ffad14b0d7e1c6c5ad82308 100644 (file)
@@ -16,9 +16,13 @@ Portability problems not fixed by Gnulib:
 This function is missing on some platforms:
 mingw, Interix 3.5, BeOS.
 @item
+On some platforms, this function mis-handles trailing slash:
+Solaris 9.
+@item
 This function cannot set full timestamp resolution.  In particular,
 some platforms incorrectly round rather than truncate.  Use
-@code{utimensat(AT_FDCWD,file,times,0)} instead.
+@code{utimensat(AT_FDCWD,file,times,0)}, or the gnulib module utimens,
+instead.
 @item
 On some platforms, @code{utimes (file, NULL)} fails to set the
 file's timestamp to the current time:
index b33f883e286cf36f54aa9e173d9e94f31ee0d6d1..ffc60b699a68e014ffb1fa12e5596e971ec512af 100644 (file)
@@ -60,6 +60,12 @@ struct utimbuf
 static int utimensat_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
+
 /* 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
@@ -242,12 +248,12 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2])
      nanosecond resolution, so do the best we can, discarding any
      fractional part of the timestamp.  */
 
-  if (adjustment_needed)
+  if (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0))
     {
       struct stat st;
       if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
         return -1;
-      if (update_timespec (&st, &ts))
+      if (ts && update_timespec (&st, &ts))
         return 0;
     }
 
@@ -401,11 +407,11 @@ lutimens (char const *file, struct timespec const timespec[2])
      nanosecond resolution, so do the best we can, discarding any
      fractional part of the timestamp.  */
 
-  if (adjustment_needed)
+  if (adjustment_needed || REPLACE_FUNC_STAT_FILE)
     {
       if (lstat (file, &st))
         return -1;
-      if (update_timespec (&st, &ts))
+      if (ts && update_timespec (&st, &ts))
         return 0;
     }
 
@@ -429,7 +435,7 @@ lutimens (char const *file, struct timespec const timespec[2])
 #endif /* HAVE_LUTIMES */
 
   /* Out of luck for symlinks, but we still handle regular files.  */
-  if (!adjustment_needed && lstat (file, &st))
+  if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st))
     return -1;
   if (!S_ISLNK (st.st_mode))
     return fdutimens (file, -1, ts);
index 67658325cfc97b9d94b6bab28f561e0ed2fce880..c9302c89e34514ee2d7d74486331eafdcc28d220 100644 (file)
@@ -34,12 +34,24 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print)
   ASSERT (func ("no_such", NULL) == -1);
   ASSERT (errno == ENOENT);
   errno = 0;
+  ASSERT (func ("no_such/", NULL) == -1);
+  ASSERT (errno == ENOENT || errno == ENOTDIR);
+  errno = 0;
   ASSERT (func ("", NULL) == -1);
   ASSERT (errno == ENOENT);
   ASSERT (close (creat (BASE "file", 0600)) == 0);
   ASSERT (stat (BASE "file", &st1) == 0);
   ASSERT (st1.st_atime != Y2K);
   ASSERT (st1.st_mtime != Y2K);
+  {
+    struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
+    errno = 0;
+    ASSERT (func (BASE "file/", ts) == -1);
+    ASSERT (errno == ENOTDIR);
+    ASSERT (stat (BASE "file", &st2) == 0);
+    ASSERT (st1.st_atime == st2.st_atime);
+    ASSERT (st1.st_mtime == st2.st_mtime);
+  }
   {
     struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
     ASSERT (func (BASE "file", ts) == 0);
@@ -138,7 +150,30 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print)
     ASSERT (utimecmp (BASE "link", &st1, &st2, 0) <= 0);
   }
 
+  /* Symlink to directory.  */
+  ASSERT (unlink (BASE "link") == 0);
+  ASSERT (symlink (BASE "dir", BASE "link") == 0);
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  {
+    struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
+    ASSERT (func (BASE "link/", ts) == 0);
+  }
+  /* On cygwin 1.5, stat() changes atime of directories, so only check
+     mtime.  */
+  ASSERT (stat (BASE "dir", &st1) == 0);
+  ASSERT (st1.st_mtime == Y2K);
+  ASSERT (lstat (BASE "link", &st1) == 0);
+  ASSERT (st1.st_atime != Y2K);
+  ASSERT (st1.st_mtime != Y2K);
+  ASSERT (func (BASE "link", NULL) == 0);
+  ASSERT (stat (BASE "dir", &st1) == 0);
+  ASSERT (st1.st_mtime == Y2K);
+  ASSERT (lstat (BASE "link", &st1) == 0);
+  ASSERT (st1.st_atime != Y2K);
+  ASSERT (st1.st_mtime != Y2K);
+
   /* Cleanup.  */
+  ASSERT (rmdir (BASE "dir") == 0);
   ASSERT (unlink (BASE "link") == 0);
   return 0;
 }
index abb4d26af69faf68760b0794cb2d940acdd73374..710741a7c242b6454705cb9538e31b6152589c52 100644 (file)
@@ -58,6 +58,9 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print)
   ASSERT (func ("no_such", NULL) == -1);
   ASSERT (errno == ENOENT);
   errno = 0;
+  ASSERT (func ("no_such/", NULL) == -1);
+  ASSERT (errno == ENOENT || errno == ENOTDIR);
+  errno = 0;
   ASSERT (func ("", NULL) == -1);
   ASSERT (errno == ENOENT);
   {
@@ -72,6 +75,12 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print)
     ASSERT (func (BASE "file", ts) == -1);
     ASSERT (errno == EINVAL);
   }
+  {
+    struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
+    errno = 0;
+    ASSERT (func (BASE "file/", ts) == -1);
+    ASSERT (errno == ENOTDIR || errno == EINVAL);
+  }
   ASSERT (stat (BASE "file", &st2) == 0);
   ASSERT (st1.st_atime == st2.st_atime);
   ASSERT (get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2));
@@ -113,6 +122,9 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print)
     }
   ASSERT (lstat (BASE "link", &st1) == 0);
   ASSERT (st1.st_mtime != Y2K);
+  errno = 0;
+  ASSERT (func (BASE "link/", NULL) == -1);
+  ASSERT (errno == ENOTDIR);
   {
     struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
     ASSERT (func (BASE "link", ts) == 0);