bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: utimensat round 3


From: Eric Blake
Subject: Re: utimensat round 3
Date: Thu, 15 Oct 2009 18:22:58 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

Eric Blake <ebb9 <at> byu.net> writes:

> The current state of the series is now pushed.  Subsequent improvements,
> like utimensat, will come later.

Here's the current state; I'll probably commit up through patch 4 within the 
next 24 hours, but spend more time hammering on cygwin with patch 5.

Eric Blake (5):
      [1/5] sys_stat: sort replacement declarations
Matches what has been done in other headers, makes it easier to figure out 
where to declare utimensat.

      [2/5] utimens: cache whether utimensat syscall works
If we know that utimensat fails with ENOSYS, then why waste time calling it.

      [3/5] utimens: let lutimens work on non-symlinks
I'm working on adding 'touch -h' in coreutils; 'touch -h file' is much easier 
to write if I can use lutimens("file", NULL), rather than having to do lstat
("file"), S_ISLNK ? lutimens : utimens.  And POSIX requires utimensat with 
AT_SYMLINK_NOFOLLOW to work on non-symlinks.  So this reduces the number of 
situations where we might fail with ENOSYS, while still delaying any lstat 
calls until absolutely necessary.

      [4/5] utimensat: new module
When utimensat does not exist, this is pretty straight-forward - get to the 
right directory, then do utimens.  But when utimensat exists but might be buggy 
(as is the case on Linux, due to ENOSYS and EINVAL problems within the past 
three years), it gets fun.  rpl_utimensat checks for a working native version, 
then calls local_utimensat if the native didn't work right; local_utimensat 
gets to the right directory and calls utimens; then utimens again tries the 
native utimensat, but this time with sanitized parameters to avoid EINVAL bugs 
and with caching to avoid repeated ENOSYS situations.  I've tested that it 
passes on Linux (both pre- and post-utimensat), Solaris 10, NetBSD, and mingw.  
It also passes on cygwin 1.5 (no utimensat) and 1.7 (native utimensat is good 
enough without the need of rpl_utimensat) when single stepping through a 
debugger, but not when run at speed, because of:

      [5/5] utimens-tests: tweak for cygwin
Work in progress.  Right now, my utimensat series has exposed a bug in cygwin 
1.5's gettimeofday, which is still present in cygwin 1.7's clock_gettime 
(although I'm hoping to patch cygwin 1.7 in the near future).  Basically, 
cygwin tries to reduce the number of syscalls made to the relatively expensive 
underlying Windows GetSystemTimeOfDay call (good to 10^-7 resolution, but 
quantized somewhere around 10^-3), by caching the hi-res time and using the 
faster but lower-res timeGetTime (good only to 10^-3 resolution, but sometimes 
quantized to 10^-2) to guess whether the cached value is still good.  Thus, if 
the two syscalls have the same quantization, or if we are still in the 
quantization window of GetSystemTimeOfDay since when cygwin refreshed its 
cache, then cygwin time matches system time.  If the file system is super fast, 
then test-utimensat passes, because everything fits within the quantization 
window.  But if a small amount of time elapses from the first gettime, enough 
to cross the GetSystemTimeOfDay quantization boundary but not the timeGetTime 
boundary, then cygwin time is behind system time, such that:
  close(creat("file",0600)) - sets timestamps using GetSystemTimeOfDay
  utimens("file",NULL) - sets timestamps using cygwin time
makes time appear to move backwards.  I could not find a sleep amount between 
system events shorter than 40 seconds that reliably forced cygwin to use 
GetSystemTimeOfDay, but that makes the overall test run quite slowly.  At this 
point, I'm starting to toy with the idea of updating the gettime.c module to 
blindly use the Windows GetSystemTimeOfDay (even though cygwin normally frowns 
on external use of non-POSIX interfaces).  I'm also looking at the alternative 
windows function QueryPerformanceCounter, which has a quantization around 10^-6 
but does not correlate as nicely to GetSystemTimeOfDay.  Either change to 
gettime would then mean implementing rpl_utimensat to never pass NULL or 
UTIME_NOW to cygwin's underlying utimensat, so that we never cause cygwin to 
use its potentially stale timestamp.


>From 185f4b692276199741cc82eaddf632614b976cf7 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 12 Oct 2009 15:33:30 -0600
Subject: [PATCH 1/5] sys_stat: sort replacement declarations

* lib/sys_stat.in.h: Sort declarations.
* lib/futimens.c (futimens): Fix typo.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog         |    6 ++
 lib/futimens.c    |    4 +-
 lib/sys_stat.in.h |  168 +++++++++++++++++++++++++++--------------------------
 3 files changed, 94 insertions(+), 84 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index e19038b..9d253d1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2009-10-15  Eric Blake  <address@hidden>
+
+       sys_stat: sort replacement declarations
+       * lib/sys_stat.in.h: Sort declarations.
+       * lib/futimens.c (futimens): Fix typo.
+
 2009-10-14  Eric Blake  <address@hidden>

        fchdir: avoid infinite recursion in mingw
diff --git a/lib/futimens.c b/lib/futimens.c
index f253747..031a464 100644
--- a/lib/futimens.c
+++ b/lib/futimens.c
@@ -25,10 +25,10 @@
 /* Set the access and modification time stamps of FD to be
    TIMESPEC[0] and TIMESPEC[1], respectively.
    Fail with ENOSYS on systems without futimes (or equivalent).
-   If TIMESPEC is null, set theh time stamps to the current time.
+   If TIMESPEC is null, set the time stamps to the current time.
    Return 0 on success, -1 (setting errno) on failure.  */
 int
-futimens (int fd, const struct timespec times[2])
+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
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index 296cedd..a44e1da 100644
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -293,41 +293,6 @@ extern "C" {
 #endif


-#if @GNULIB_LSTAT@
-# if ! @HAVE_LSTAT@
-/* mingw does not support symlinks, therefore it does not have lstat.  But
-   without links, stat does just fine.  */
-#  define lstat stat
-# elif @REPLACE_LSTAT@
-#  undef lstat
-#  define lstat rpl_lstat
-extern int rpl_lstat (const char *name, struct stat *buf);
-# endif
-#elif defined GNULIB_POSIXCHECK
-# undef lstat
-# define lstat(p,b)                                                    \
-  (GL_LINK_WARNING ("lstat is unportable - "                           \
-                   "use gnulib module lstat for portability"),         \
-   lstat (p, b))
-#endif
-
-#if @GNULIB_STAT@
-# if @REPLACE_STAT@
-/* We can't use the object-like #define stat rpl_stat, because of
-   struct stat.  This means that rpl_stat will not be used if the user
-   does (stat)(a,b).  Oh well.  */
-#  undef stat
-#  define stat(name, st) rpl_stat (name, st)
-extern int stat (const char *name, struct stat *buf);
-# endif
-#elif defined GNULIB_POSIXCHECK
-# undef stat
-# define stat(p,b)                                                     \
-  (GL_LINK_WARNING ("stat is unportable - "                            \
-                   "use gnulib module stat for portability"),          \
-   stat (p, b))
-#endif
-
 #if @GNULIB_FCHMODAT@
 # if address@hidden@
 extern int fchmodat (int fd, char const *file, mode_t mode, int flag);
@@ -341,6 +306,12 @@ extern int fchmodat (int fd, char const *file, mode_t 
mode, int flag);
 #endif


+#if @REPLACE_FSTAT@
+# define fstat rpl_fstat
+extern int fstat (int fd, struct stat *buf);
+#endif
+
+
 #if @GNULIB_FSTATAT@
 # if @REPLACE_FSTATAT@
 #  undef fstatat
@@ -375,6 +346,71 @@ extern int futimens (int fd, struct timespec const times
[2]);
 #endif


+#if @GNULIB_LCHMOD@
+/* Change the mode of FILENAME to MODE, without dereferencing it if FILENAME
+   denotes a symbolic link.  */
+# if address@hidden@
+/* The lchmod replacement follows symbolic links.  Callers should take
+   this into account; lchmod should be applied only to arguments that
+   are known to not be symbolic links.  On hosts that lack lchmod,
+   this can lead to race conditions between the check and the
+   invocation of lchmod, but we know of no workarounds that are
+   reliable in general.  You might try requesting support for lchmod
+   from your operating system supplier.  */
+#  define lchmod chmod
+# endif
+# if 0 /* assume already declared */
+extern int lchmod (const char *filename, mode_t mode);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef lchmod
+# define lchmod(f,m) \
+    (GL_LINK_WARNING ("lchmod is unportable - " \
+                      "use gnulib module lchmod for portability"), \
+     lchmod (f, m))
+#endif
+
+
+#if @GNULIB_LSTAT@
+# if ! @HAVE_LSTAT@
+/* mingw does not support symlinks, therefore it does not have lstat.  But
+   without links, stat does just fine.  */
+#  define lstat stat
+# elif @REPLACE_LSTAT@
+#  undef lstat
+#  define lstat rpl_lstat
+extern int rpl_lstat (const char *name, struct stat *buf);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef lstat
+# define lstat(p,b)                                                    \
+  (GL_LINK_WARNING ("lstat is unportable - "                           \
+                   "use gnulib module lstat for portability"),         \
+   lstat (p, b))
+#endif
+
+
+#if @REPLACE_MKDIR@
+# undef mkdir
+# define mkdir rpl_mkdir
+extern int mkdir (char const *name, mode_t mode);
+#else
+/* mingw's _mkdir() function has 1 argument, but we pass 2 arguments.
+   Additionally, it declares _mkdir (and depending on compile flags, an
+   alias mkdir), only in the nonstandard <io.h>, which is included above.  */
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+
+static inline int
+rpl_mkdir (char const *name, mode_t mode)
+{
+  return _mkdir (name);
+}
+
+#  define mkdir rpl_mkdir
+# endif
+#endif
+
+
 #if @GNULIB_MKDIRAT@
 # if address@hidden@
 extern int mkdirat (int fd, char const *file, mode_t mode);
@@ -387,6 +423,7 @@ extern int mkdirat (int fd, char const *file, mode_t mode);
      mkdirat (d, n, m))
 #endif

+
 #if @GNULIB_MKFIFOAT@
 # if address@hidden@
 int mkfifoat (int fd, char const *file, mode_t mode);
@@ -399,6 +436,7 @@ int mkfifoat (int fd, char const *file, mode_t mode);
      mkfifoat (d, n, m))
 #endif

+
 #if @GNULIB_MKNODAT@
 # if address@hidden@
 int mknodat (int fd, char const *file, mode_t mode, dev_t dev);
@@ -411,56 +449,22 @@ int mknodat (int fd, char const *file, mode_t mode, dev_t 
dev);
      mknodat (f, n, m, d))
 #endif

-#if @REPLACE_FSTAT@
-# define fstat rpl_fstat
-extern int fstat (int fd, struct stat *buf);
-#endif

-#if @REPLACE_MKDIR@
-# undef mkdir
-# define mkdir rpl_mkdir
-extern int mkdir (char const *name, mode_t mode);
-#else
-/* mingw's _mkdir() function has 1 argument, but we pass 2 arguments.
-   Additionally, it declares _mkdir (and depending on compile flags, an
-   alias mkdir), only in the nonstandard <io.h>, which is included above.  */
-# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
-
-static inline int
-rpl_mkdir (char const *name, mode_t mode)
-{
-  return _mkdir (name);
-}
-
-#  define mkdir rpl_mkdir
-# endif
-#endif
-
-
-/* Declare BSD extensions.  */
-
-#if @GNULIB_LCHMOD@
-/* Change the mode of FILENAME to MODE, without dereferencing it if FILENAME
-   denotes a symbolic link.  */
-# if address@hidden@
-/* The lchmod replacement follows symbolic links.  Callers should take
-   this into account; lchmod should be applied only to arguments that
-   are known to not be symbolic links.  On hosts that lack lchmod,
-   this can lead to race conditions between the check and the
-   invocation of lchmod, but we know of no workarounds that are
-   reliable in general.  You might try requesting support for lchmod
-   from your operating system supplier.  */
-#  define lchmod chmod
-# endif
-# if 0 /* assume already declared */
-extern int lchmod (const char *filename, mode_t mode);
+#if @GNULIB_STAT@
+# if @REPLACE_STAT@
+/* We can't use the object-like #define stat rpl_stat, because of
+   struct stat.  This means that rpl_stat will not be used if the user
+   does (stat)(a,b).  Oh well.  */
+#  undef stat
+#  define stat(name, st) rpl_stat (name, st)
+extern int stat (const char *name, struct stat *buf);
 # endif
 #elif defined GNULIB_POSIXCHECK
-# undef lchmod
-# define lchmod(f,m) \
-    (GL_LINK_WARNING ("lchmod is unportable - " \
-                      "use gnulib module lchmod for portability"), \
-     lchmod (f, m))
+# undef stat
+# define stat(p,b)                                                     \
+  (GL_LINK_WARNING ("stat is unportable - "                            \
+                   "use gnulib module stat for portability"),          \
+   stat (p, b))
 #endif


-- 
1.6.4.2


>From ba6904e3444df300c42198dc1a8e070f2778649b Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 12 Oct 2009 14:56:22 -0600
Subject: [PATCH 2/5] utimens: cache whether utimensat syscall works

No need to repeatedly try utimensat on older Linux kernel.

* lib/utimens.c (utimensat_works_really): New cache variable.
(fdutimens, lutimens): Use it to avoid failing syscall.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog     |    4 ++
 lib/utimens.c |  119 ++++++++++++++++++++++++++++++++++-----------------------
 2 files changed, 75 insertions(+), 48 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 9d253d1..7f0cee2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2009-10-15  Eric Blake  <address@hidden>

+       utimens: cache whether utimensat syscall works
+       * lib/utimens.c (utimensat_works_really): New cache variable.
+       (fdutimens, lutimens): Use it to avoid failing syscall.
+
        sys_stat: sort replacement declarations
        * lib/sys_stat.in.h: Sort declarations.
        * lib/futimens.c (futimens): Fix typo.
diff --git a/lib/utimens.c b/lib/utimens.c
index 043f497..c57ff83 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -52,6 +52,13 @@ struct utimbuf
 /* Avoid recursion with rpl_futimens.  */
 #undef futimens

+#if HAVE_UTIMENSAT || HAVE_FUTIMENS
+/* Cache variable for whether syscall works; used to avoid calling the
+   syscall if we know it will just fail with ENOSYS.  0 = unknown, 1 =
+   yes, -1 = no.  */
+static int utimensat_works_really;
+#endif /* HAVE_UTIMENSAT || HAVE_UTIMENSAT */
+
 /* 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
@@ -182,41 +189,53 @@ fdutimens (char const *file, int fd, struct timespec 
const timespec[2])
 #endif

   /* POSIX 2008 added two interfaces to set file timestamps with
-     nanosecond resolution.  We provide a fallback for ENOSYS (for
-     example, compiling against Linux 2.6.25 kernel headers and glibc
-     2.7, but running on Linux 2.6.18 kernel).  */
-#if HAVE_UTIMENSAT
-  if (fd < 0)
+     nanosecond resolution; newer Linux implements both functions via
+     a single syscall.  We provide a fallback for ENOSYS (for example,
+     compiling against Linux 2.6.25 kernel headers and glibc 2.7, but
+     running on Linux 2.6.18 kernel).  */
+#if HAVE_UTIMENSAT || HAVE_FUTIMENS
+  if (0 <= utimensat_works_really)
     {
-      int result = utimensat (AT_FDCWD, file, ts, 0);
-# ifdef __linux__
-      /* Work around a kernel bug:
-         http://bugzilla.redhat.com/442352
-         http://bugzilla.redhat.com/449910
-         It appears that utimensat can mistakenly return 280 rather
-         than -1 upon failure.
-         FIXME: remove in 2010 or whenever the offending kernels
-         are no longer in common use.  */
-      if (0 < result)
-        errno = ENOSYS;
-# endif
-
-      if (result == 0 || errno != ENOSYS)
-        return result;
+# if HAVE_UTIMENSAT
+      if (fd < 0)
+        {
+          int result = utimensat (AT_FDCWD, file, ts, 0);
+#  ifdef __linux__
+          /* Work around a kernel bug:
+             http://bugzilla.redhat.com/442352
+             http://bugzilla.redhat.com/449910
+             It appears that utimensat can mistakenly return 280 rather
+             than -1 upon ENOSYS failure.
+             FIXME: remove in 2010 or whenever the offending kernels
+             are no longer in common use.  */
+          if (0 < result)
+            errno = ENOSYS;
+#  endif /* __linux__ */
+          if (result == 0 || errno != ENOSYS)
+            {
+              utimensat_works_really = 1;
+              return result;
+            }
+        }
+# endif /* HAVE_UTIMENSAT */
+# if HAVE_FUTIMENS
+      {
+        int result = futimens (fd, timespec);
+#  ifdef __linux__
+        /* Work around the same bug as above.  */
+        if (0 < result)
+          errno = ENOSYS;
+#  endif /* __linux__ */
+        if (result == 0 || errno != ENOSYS)
+          {
+            utimensat_works_really = 1;
+            return result;
+          }
+      }
+# endif /* HAVE_FUTIMENS */
     }
-#endif
-#if HAVE_FUTIMENS
-  {
-    int result = futimens (fd, timespec);
-# ifdef __linux__
-    /* Work around the same bug as above.  */
-    if (0 < result)
-      errno = ENOSYS;
-# endif
-    if (result == 0 || errno != ENOSYS)
-      return result;
-  }
-#endif
+  utimensat_works_really = -1;
+#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */

   /* The platform lacks an interface to set file timestamps with
      nanosecond resolution, so do the best we can, discarding any
@@ -352,23 +371,27 @@ lutimens (char const *file, struct timespec const timespec
[2])
      worry about bogus return values.  */

 #if HAVE_UTIMENSAT
-  {
-    int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
+  if (0 <= utimensat_works_really)
+    {
+      int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
 # ifdef __linux__
-    /* Work around a kernel bug:
-       http://bugzilla.redhat.com/442352
-       http://bugzilla.redhat.com/449910
-       It appears that utimensat can mistakenly return 280 rather
-       than -1 upon ENOSYS failure.
-       FIXME: remove in 2010 or whenever the offending kernels
-       are no longer in common use.  */
-    if (0 < result)
-      errno = ENOSYS;
+      /* Work around a kernel bug:
+         http://bugzilla.redhat.com/442352
+         http://bugzilla.redhat.com/449910
+         It appears that utimensat can mistakenly return 280 rather
+         than -1 upon ENOSYS failure.
+         FIXME: remove in 2010 or whenever the offending kernels
+         are no longer in common use.  */
+      if (0 < result)
+        errno = ENOSYS;
 # endif
-
-    if (result == 0 || errno != ENOSYS)
-      return result;
-  }
+      if (result == 0 || errno != ENOSYS)
+        {
+          utimensat_works_really = 1;
+          return result;
+        }
+    }
+  utimensat_works_really = -1;
 #endif /* HAVE_UTIMENSAT */

   /* The platform lacks an interface to set file timestamps with
-- 
1.6.4.2


>From 4f76af943e9bc1dfedb9355ff337e3b54fffb392 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 15 Oct 2009 12:04:57 -0600
Subject: [PATCH 3/5] utimens: let lutimens work on non-symlinks

Coreutils new 'touch -h' is easier to write if we guarantee POSIX
semantics of utimensat(fd,"file",NULL,AT_SYMLINK_NOFOLLOW), rather
than blindly failing with ENOSYS even on non-symlinks.

* lib/utimens.c (lutimens): Fall back to utimens rather than
failing with ENOSYS, when file is not a symlink.
(utimens): Reduce redirection.
* tests/test-lutimens.h (test_lutimens): Update test to cover
non-symlinks.
* tests/test-utimens.h (test_utimens): Update test to cover
symlinks.
* tests/test-utimens.c (main): Update caller.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog             |   10 ++++++++++
 lib/utimens.c         |   18 +++++++++++-------
 tests/test-lutimens.h |   44 ++++++++++++++++++++++++++++++++++----------
 tests/test-utimens.c  |   21 ++++++++++-----------
 tests/test-utimens.h  |   28 ++++++++++++++++++++++++++--
 5 files changed, 91 insertions(+), 30 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7f0cee2..3c998f2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2009-10-15  Eric Blake  <address@hidden>

+       utimens: let lutimens work on non-symlinks
+       * lib/utimens.c (lutimens): Fall back to utimens rather than
+       failing with ENOSYS, when file is not a symlink.
+       (utimens): Reduce redirection.
+       * tests/test-lutimens.h (test_lutimens): Update test to cover
+       non-symlinks.
+       * tests/test-utimens.h (test_utimens): Update test to cover
+       symlinks.
+       * tests/test-utimens.c (main): Update caller.
+
        utimens: cache whether utimensat syscall works
        * lib/utimens.c (utimensat_works_really): New cache variable.
        (fdutimens, lutimens): Use it to avoid failing syscall.
diff --git a/lib/utimens.c b/lib/utimens.c
index c57ff83..8709a1c 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -343,18 +343,20 @@ gl_futimens (int fd, char const *file, struct timespec 
const timespec[2])
 int
 utimens (char const *file, struct timespec const timespec[2])
 {
-  return gl_futimens (-1, file, timespec);
+  return fdutimens (file, -1, timespec);
 }

-/* Set the access and modification time stamps of the symlink FILE to
-   be TIMESPEC[0] and TIMESPEC[1], respectively.  Fail with ENOSYS if
-   the platform does not support changing symlink timestamps.  */
+/* Set the access and modification time stamps of FILE to be
+   TIMESPEC[0] and TIMESPEC[1], respectively, without dereferencing
+   symlinks.  Fail with ENOSYS if the platform does not support
+   changing symlink timestamps, but FILE was a symlink.  */
 int
 lutimens (char const *file, struct timespec const timespec[2])
 {
   struct timespec adjusted_timespec[2];
   struct timespec *ts = timespec ? adjusted_timespec : NULL;
   int adjustment_needed = 0;
+  struct stat st;

   if (ts)
     {
@@ -400,7 +402,6 @@ lutimens (char const *file, struct timespec const timespec
[2])

   if (adjustment_needed)
     {
-      struct stat st;
       if (lstat (file, &st))
         return -1;
       if (update_timespec (&st, &ts))
@@ -426,8 +427,11 @@ lutimens (char const *file, struct timespec const timespec
[2])
   }
 #endif /* HAVE_LUTIMES */

-  /* Out of luck.  Symlink timestamps can't be changed.  We won't
-     bother changing the timestamps if FILE was not a symlink.  */
+  /* Out of luck for symlinks, but we still handle regular files.  */
+  if (!adjustment_needed && lstat (file, &st))
+    return -1;
+  if (!S_ISLNK (st.st_mode))
+    return fdutimens (file, -1, ts);
   errno = ENOSYS;
   return -1;
 }
diff --git a/tests/test-lutimens.h b/tests/test-lutimens.h
index 18cf75f..cc764b0 100644
--- a/tests/test-lutimens.h
+++ b/tests/test-lutimens.h
@@ -53,12 +53,34 @@ static int
 test_lutimens (int (*func) (char const *, struct timespec const *), bool print)
 {
   int result;
+  int saved_errno;
   struct stat st1;
   struct stat st2;
   bool atime_supported = true;

-  if (symlink ("nowhere", BASE "link"))
+  /* Non-symlinks should be handled just like utimens.  */
+  errno = 0;
+  ASSERT (func ("no_such", NULL) == -1);
+  ASSERT (errno == ENOENT);
+  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 } };
+    ASSERT (func (BASE "file", ts) == 0);
+  }
+  ASSERT (stat (BASE "file", &st1) == 0);
+  ASSERT (st1.st_atime == Y2K);
+  ASSERT (st1.st_mtime == Y2K);
+
+  /* Play with symlink timestamps.  */
+  if (symlink (BASE "file", BASE "link"))
     {
+      ASSERT (unlink (BASE "file") == 0);
       if (print)
         fputs ("skipping test: symlinks not supported on this file system\n",
                stderr);
@@ -66,7 +88,16 @@ test_lutimens (int (*func) (char const *, struct timespec 
const *), bool print)
     }
   errno = 0;
   result = func (BASE "link", NULL);
-  if (result == -1 && errno == ENOSYS)
+  saved_errno = errno;
+  /* Make sure we did not reference through link by accident.  */
+  ASSERT (stat (BASE "file", &st1) == 0);
+  ASSERT (st1.st_atime == Y2K);
+  ASSERT (st1.st_mtime == Y2K);
+  ASSERT (lstat (BASE "link", &st1) == 0);
+  ASSERT (st1.st_atime != Y2K);
+  ASSERT (st1.st_mtime != Y2K);
+  ASSERT (unlink (BASE "file") == 0);
+  if (result == -1 && saved_errno == ENOSYS)
     {
       ASSERT (unlink (BASE "link") == 0);
       if (print)
@@ -76,7 +107,6 @@ test_lutimens (int (*func) (char const *, struct timespec 
const *), bool print)
       return 77;
     }
   ASSERT (!result);
-  ASSERT (lstat (BASE "link", &st1) == 0);
 #if HAVE_USLEEP
   /* On Cygwin, the mere act of lstat changes symlink atime, even
      though POSIX says that only readlink is allowed to do that.
@@ -87,15 +117,9 @@ test_lutimens (int (*func) (char const *, struct timespec 
const *), bool print)
   ASSERT (lstat (BASE "link", &st2) == 0);
   if (st1.st_atime != st2.st_atime
       || get_stat_atime_ns (&st1) != get_stat_atime_ns (&st2))
-     atime_supported = false;
+    atime_supported = false;

   /* Invalid arguments.  */
-  errno = 0;
-  ASSERT (func ("no_such", NULL) == -1);
-  ASSERT (errno == ENOENT);
-  errno = 0;
-  ASSERT (func ("", NULL) == -1);
-  ASSERT (errno == ENOENT);
   {
     struct timespec ts[2] = { { Y2K, UTIME_BOGUS_POS }, { Y2K, 0 } };
     errno = 0;
diff --git a/tests/test-utimens.c b/tests/test-utimens.c
index e43663a..651ea9b 100644
--- a/tests/test-utimens.c
+++ b/tests/test-utimens.c
@@ -71,20 +71,19 @@ do_fdutimens (char const *name, struct timespec const times
[2])
 int
 main ()
 {
-  int result1;
-  int result2;
+  int result1; /* Skip because of no symlink support.  */
+  int result2; /* Skip because of no futimens support.  */
+  int result3; /* Skip because of no lutimens support.  */

   /* Clean up any trash from prior testsuite runs.  */
   ASSERT (system ("rm -rf " BASE "*") == 0);

-  ASSERT (test_utimens (utimens) == 0);
-  ASSERT (test_utimens (do_fdutimens) == 0);
-  result1 = test_futimens (do_futimens, true);
-  if (result1)
-    ASSERT (result1 == 77);
+  result1 = test_utimens (utimens, true);
+  ASSERT (test_utimens (do_fdutimens, false) == result1);
   /* Print only one skip message.  */
-  result2 = test_lutimens (lutimens, result1 == 0);
-  if (result2)
-    ASSERT (result2 == 77);
-  return result1 | result2;
+  result2 = test_futimens (do_futimens, result1 == 0);
+  result3 = test_lutimens (lutimens, (result1 + result2) == 0);
+  /* We expect 0/0, 0/77, or 77/77, but not 77/0.  */
+  ASSERT (result1 <= result3);
+  return result1 | result2 | result3;
 }
diff --git a/tests/test-utimens.h b/tests/test-utimens.h
index 08d3ac0..74dea21 100644
--- a/tests/test-utimens.h
+++ b/tests/test-utimens.h
@@ -47,9 +47,10 @@ enum {
 #endif /* GL_TEST_UTIMENS */

 /* This function is designed to test both utimens(a,b) and
-   utimensat(AT_FDCWD,a,b,0).  FUNC is the function to test.  */
+   utimensat(AT_FDCWD,a,b,0).  FUNC is the function to test.  If
+   PRINT, warn before skipping tests with status 77.  */
 static int
-test_utimens (int (*func) (char const *, struct timespec const *))
+test_utimens (int (*func) (char const *, struct timespec const *), bool print)
 {
   struct stat st1;
   struct stat st2;
@@ -128,7 +129,30 @@ test_utimens (int (*func) (char const *, struct timespec 
const *))
     ASSERT (0 <= utimecmp (BASE "file", &st2, &st1, UTIMECMP_TRUNCATE_SOURCE));
   }

+  /* Make sure this dereferences symlinks.  */
+  if (symlink (BASE "file", BASE "link"))
+    {
+       ASSERT (unlink (BASE "file") == 0);
+      if (print)
+        fputs ("skipping test: symlinks not supported on this file system\n",
+               stderr);
+      return 77;
+    }
+  ASSERT (lstat (BASE "link", &st1) == 0);
+  ASSERT (st1.st_mtime != Y2K);
+  {
+    struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
+    ASSERT (func (BASE "link", ts) == 0);
+    ASSERT (lstat (BASE "link", &st2) == 0);
+    /* Can't compare atimes, since lstat() changes symlink atime on cygwin.  */
+    ASSERT (st1.st_mtime == st2.st_mtime);
+    ASSERT (stat (BASE "link", &st2) == 0);
+    ASSERT (st2.st_mtime == Y2K);
+    ASSERT (get_stat_mtime_ns (&st2) == 0);
+  }
+
   /* Cleanup.  */
+  ASSERT (unlink (BASE "link") == 0);
   ASSERT (unlink (BASE "file") == 0);
   return 0;
 }
-- 
1.6.4.2


>From 15b8d5b61dd7ac7fbc834e201d9148ceb50521b6 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 12 Oct 2009 16:48:57 -0600
Subject: [PATCH 4/5] utimensat: new module

Provide utimensat where it is missing, and rpl_utimensat to work
around ENOSYS and EINVAL bugs in older Linux kernels.

* modules/utimensat: New file.
* lib/utimensat.c (utimensat): Likewise.
* m4/utimensat.m4 (gl_FUNC_UTIMENSAT): Likewise.
* lib/utimens.c (utimensat): Avoid recursion into rpl_utimensat,
so we can work around Linux bugs.
* m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses.
* modules/sys_stat (Makefile.am): Substitute them.
* lib/sys_stat.in.h (utimensat): Declare it.
* MODULES.html.sh (systems lacking POSIX:2008): Mention module.
* doc/posix-functions/utimensat.texi (utimensat): Likewise.
* modules/utimensat-tests: New test.
* tests/test-utimensat.c: Likewise.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog                          |   14 ++++
 MODULES.html.sh                    |    1 +
 doc/posix-functions/utimensat.texi |   14 ++--
 lib/sys_stat.in.h                  |   18 ++++++
 lib/utimens.c                      |    3 +-
 lib/utimensat.c                    |  101 +++++++++++++++++++++++++++++++
 m4/sys_stat_h.m4                   |   51 ++++++++-------
 m4/utimensat.m4                    |   40 ++++++++++++
 modules/sys_stat                   |    3 +
 modules/utimensat                  |   29 +++++++++
 modules/utimensat-tests            |   17 +++++
 tests/test-utimensat.c             |  117 ++++++++++++++++++++++++++++++++++++
 12 files changed, 376 insertions(+), 32 deletions(-)
 create mode 100644 lib/utimensat.c
 create mode 100644 m4/utimensat.m4
 create mode 100644 modules/utimensat
 create mode 100644 modules/utimensat-tests
 create mode 100644 tests/test-utimensat.c

diff --git a/ChangeLog b/ChangeLog
index 3c998f2..35dc932 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2009-10-15  Eric Blake  <address@hidden>

+       utimensat: new module
+       * modules/utimensat: New file.
+       * lib/utimensat.c (utimensat): Likewise.
+       * m4/utimensat.m4 (gl_FUNC_UTIMENSAT): Likewise.
+       * lib/utimens.c (utimensat): Avoid recursion into rpl_utimensat,
+       so we can work around Linux bugs.
+       * m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses.
+       * modules/sys_stat (Makefile.am): Substitute them.
+       * lib/sys_stat.in.h (utimensat): Declare it.
+       * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+       * doc/posix-functions/utimensat.texi (utimensat): Likewise.
+       * modules/utimensat-tests: New test.
+       * tests/test-utimensat.c: Likewise.
+
        utimens: let lutimens work on non-symlinks
        * lib/utimens.c (lutimens): Fall back to utimens rather than
        failing with ENOSYS, when file is not a symlink.
diff --git a/MODULES.html.sh b/MODULES.html.sh
index 7966717..ca27ad9 100755
--- a/MODULES.html.sh
+++ b/MODULES.html.sh
@@ -2366,6 +2366,7 @@ func_all_modules ()
   func_module unistd
   func_module unlink
   func_module utime
+  func_module utimensat
   func_module vasnprintf-posix
   func_module vasprintf-posix
   func_module vfprintf-posix
diff --git a/doc/posix-functions/utimensat.texi b/doc/posix-
functions/utimensat.texi
index 1dcfe8c..7164965 100644
--- a/doc/posix-functions/utimensat.texi
+++ b/doc/posix-functions/utimensat.texi
@@ -4,14 +4,10 @@ utimensat

 POSIX specification: @url
{http://www.opengroup.org/onlinepubs/9699919799/functions/utimensat.html}

-Gnulib module: ---
+Gnulib module: utimensat

 Portability problems fixed by Gnulib:
 @itemize
address@hidden itemize
-
-Portability problems not fixed by Gnulib:
address@hidden
 @item
 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
@@ -26,10 +22,14 @@ utimensat
 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.
address@hidden itemize
+
+Portability problems not fixed by Gnulib:
address@hidden
 @item
 On some platforms, timestamps of symbolic links cannot be modified, so
-this function fails with @code{ENOSYS} if passed the flag
address@hidden
+the replacement fails with @code{ENOSYS} if passed the flag
address@hidden on a symlink.
 @item
 The mere act of using @code{lstat} modifies the access time of
 symlinks on some platforms, so @code{utimensat} with
diff --git a/lib/sys_stat.in.h b/lib/sys_stat.in.h
index a44e1da..e7cb5ee 100644
--- a/lib/sys_stat.in.h
+++ b/lib/sys_stat.in.h
@@ -468,6 +468,24 @@ extern int stat (const char *name, struct stat *buf);
 #endif


+#if @GNULIB_UTIMENSAT@
+# if @REPLACE_UTIMENSAT@
+#  undef utimensat
+#  define utimensat rpl_utimensat
+# endif
+# if address@hidden@ || @REPLACE_UTIMENSAT@
+   extern int utimensat (int fd, char const *name,
+                         struct timespec const times[2], int flag);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef utimensat
+# define utimensat(d,n,t,f)                          \
+    (GL_LINK_WARNING ("utimensat is not portable - " \
+                      "use gnulib module utimensat for portability"), \
+     utimensat (d, n, t, f))
+#endif
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/utimens.c b/lib/utimens.c
index 8709a1c..b33f883 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -49,8 +49,9 @@ struct utimbuf
 };
 #endif

-/* Avoid recursion with rpl_futimens.  */
+/* Avoid recursion with rpl_futimens or rpl_utimensat.  */
 #undef futimens
+#undef utimensat

 #if HAVE_UTIMENSAT || HAVE_FUTIMENS
 /* Cache variable for whether syscall works; used to avoid calling the
diff --git a/lib/utimensat.c b/lib/utimensat.c
new file mode 100644
index 0000000..d7415d5
--- /dev/null
+++ b/lib/utimensat.c
@@ -0,0 +1,101 @@
+/* Set the access and modification time of a file relative to directory fd.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+
+#include "utimens.h"
+
+#if HAVE_UTIMENSAT
+
+# undef 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.  */
+
+static int local_utimensat (int, char const *, struct timespec const[2], int);
+# define AT_FUNC_NAME local_utimensat
+
+/* Like utimensat, but work around native bugs.  */
+
+int
+rpl_utimensat (int fd, char const *file, struct timespec const times[2],
+               int flag)
+{
+  static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no.  */
+  if (0 <= utimensat_works_really)
+    {
+      int 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
+         flag is indeterminate whether the native utimensat works, but
+         local_utimensat will also reject it.  */
+      if (result == -1 && errno == EINVAL && (flag & ~AT_SYMLINK_NOFOLLOW))
+        return result;
+      if (result == 0 || (errno != ENOSYS && errno != EINVAL))
+        {
+          utimensat_works_really = 1;
+          return result;
+        }
+    }
+  /* No point in trying openat/futimens, since on Linux, futimens is
+     implemented with the same syscall as utimensat.  Only avoid the
+     native utimensat due to an ENOSYS failure; an EINVAL error was
+     data-dependent, and the next caller may pass valid data.  */
+  if (0 <= utimensat_works_really && errno == ENOSYS)
+    utimensat_works_really = -1;
+  return local_utimensat (fd, file, times, flag);
+}
+
+#else /* !HAVE_UTIMENSAT */
+
+# define AT_FUNC_NAME utimensat
+
+#endif /* !HAVE_UTIMENSAT */
+
+/* Set the access and modification time stamps of FILE to be
+   TIMESPEC[0] and TIMESPEC[1], respectively; relative to directory
+   FD.  If flag is AT_SYMLINK_NOFOLLOW, change the times of a symlink,
+   or fail with ENOSYS if not possible.  If TIMESPEC is null, set the
+   time stamps to the current time.  If possible, do it without
+   changing the working directory.  Otherwise, resort to using
+   save_cwd/fchdir, then utimens/restore_cwd.  If either the save_cwd
+   or the restore_cwd fails, then give a diagnostic and exit nonzero.
+   Return 0 on success, -1 (setting errno) on failure.  */
+
+/* AT_FUNC_NAME is now utimensat or local_utimensat.  */
+#define AT_FUNC_F1 lutimens
+#define AT_FUNC_F2 utimens
+#define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
+#define AT_FUNC_POST_FILE_PARAM_DECLS , struct timespec const ts[2], int flag
+#define AT_FUNC_POST_FILE_ARGS        , ts
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_F2
+#undef AT_FUNC_USE_F1_COND
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
diff --git a/m4/sys_stat_h.m4 b/m4/sys_stat_h.m4
index 6004890..1edf548 100644
--- a/m4/sys_stat_h.m4
+++ b/m4/sys_stat_h.m4
@@ -1,4 +1,4 @@
-# sys_stat_h.m4 serial 18   -*- Autoconf -*-
+# sys_stat_h.m4 serial 19   -*- Autoconf -*-
 dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -39,28 +39,31 @@ AC_DEFUN([gl_SYS_STAT_MODULE_INDICATOR],
 AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
 [
   AC_REQUIRE([gl_UNISTD_H_DEFAULTS]) dnl for REPLACE_FCHDIR
-  GNULIB_FCHMODAT=0;  AC_SUBST([GNULIB_FCHMODAT])
-  GNULIB_FSTATAT=0;   AC_SUBST([GNULIB_FSTATAT])
-  GNULIB_FUTIMENS=0;  AC_SUBST([GNULIB_FUTIMENS])
-  GNULIB_LCHMOD=0;    AC_SUBST([GNULIB_LCHMOD])
-  GNULIB_LSTAT=0;     AC_SUBST([GNULIB_LSTAT])
-  GNULIB_MKDIRAT=0;   AC_SUBST([GNULIB_MKDIRAT])
-  GNULIB_MKFIFOAT=0;  AC_SUBST([GNULIB_MKFIFOAT])
-  GNULIB_MKNODAT=0;   AC_SUBST([GNULIB_MKNODAT])
-  GNULIB_STAT=0;      AC_SUBST([GNULIB_STAT])
+  GNULIB_FCHMODAT=0;    AC_SUBST([GNULIB_FCHMODAT])
+  GNULIB_FSTATAT=0;     AC_SUBST([GNULIB_FSTATAT])
+  GNULIB_FUTIMENS=0;    AC_SUBST([GNULIB_FUTIMENS])
+  GNULIB_LCHMOD=0;      AC_SUBST([GNULIB_LCHMOD])
+  GNULIB_LSTAT=0;       AC_SUBST([GNULIB_LSTAT])
+  GNULIB_MKDIRAT=0;     AC_SUBST([GNULIB_MKDIRAT])
+  GNULIB_MKFIFOAT=0;    AC_SUBST([GNULIB_MKFIFOAT])
+  GNULIB_MKNODAT=0;     AC_SUBST([GNULIB_MKNODAT])
+  GNULIB_STAT=0;        AC_SUBST([GNULIB_STAT])
+  GNULIB_UTIMENSAT=0;   AC_SUBST([GNULIB_UTIMENSAT])
   dnl Assume proper GNU behavior unless another module says otherwise.
-  HAVE_FCHMODAT=1;    AC_SUBST([HAVE_FCHMODAT])
-  HAVE_FSTATAT=1;     AC_SUBST([HAVE_FSTATAT])
-  HAVE_FUTIMENS=1;    AC_SUBST([HAVE_FUTIMENS])
-  HAVE_LCHMOD=1;      AC_SUBST([HAVE_LCHMOD])
-  HAVE_LSTAT=1;       AC_SUBST([HAVE_LSTAT])
-  HAVE_MKDIRAT=1;     AC_SUBST([HAVE_MKDIRAT])
-  HAVE_MKFIFOAT=1;    AC_SUBST([HAVE_MKFIFOAT])
-  HAVE_MKNODAT=1;     AC_SUBST([HAVE_MKNODAT])
-  REPLACE_FSTAT=0;    AC_SUBST([REPLACE_FSTAT])
-  REPLACE_FSTATAT=0;  AC_SUBST([REPLACE_FSTATAT])
-  REPLACE_FUTIMENS=0; AC_SUBST([REPLACE_FUTIMENS])
-  REPLACE_LSTAT=0;    AC_SUBST([REPLACE_LSTAT])
-  REPLACE_MKDIR=0;    AC_SUBST([REPLACE_MKDIR])
-  REPLACE_STAT=0;     AC_SUBST([REPLACE_STAT])
+  HAVE_FCHMODAT=1;      AC_SUBST([HAVE_FCHMODAT])
+  HAVE_FSTATAT=1;       AC_SUBST([HAVE_FSTATAT])
+  HAVE_FUTIMENS=1;      AC_SUBST([HAVE_FUTIMENS])
+  HAVE_LCHMOD=1;        AC_SUBST([HAVE_LCHMOD])
+  HAVE_LSTAT=1;         AC_SUBST([HAVE_LSTAT])
+  HAVE_MKDIRAT=1;       AC_SUBST([HAVE_MKDIRAT])
+  HAVE_MKFIFOAT=1;      AC_SUBST([HAVE_MKFIFOAT])
+  HAVE_MKNODAT=1;       AC_SUBST([HAVE_MKNODAT])
+  HAVE_UTIMENSAT=1;     AC_SUBST([HAVE_UTIMENSAT])
+  REPLACE_FSTAT=0;      AC_SUBST([REPLACE_FSTAT])
+  REPLACE_FSTATAT=0;    AC_SUBST([REPLACE_FSTATAT])
+  REPLACE_FUTIMENS=0;   AC_SUBST([REPLACE_FUTIMENS])
+  REPLACE_LSTAT=0;      AC_SUBST([REPLACE_LSTAT])
+  REPLACE_MKDIR=0;      AC_SUBST([REPLACE_MKDIR])
+  REPLACE_STAT=0;       AC_SUBST([REPLACE_STAT])
+  REPLACE_UTIMENSAT=0;  AC_SUBST([REPLACE_UTIMENSAT])
 ])
diff --git a/m4/utimensat.m4 b/m4/utimensat.m4
new file mode 100644
index 0000000..2a6033f
--- /dev/null
+++ b/m4/utimensat.m4
@@ -0,0 +1,40 @@
+# serial 1
+# See if we need to provide utimensat replacement.
+
+dnl Copyright (C) 2009 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Written by Eric Blake.
+
+AC_DEFUN([gl_FUNC_UTIMENSAT],
+[
+  AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
+  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+  AC_CHECK_FUNCS_ONCE([utimensat])
+  if test $ac_cv_func_utimensat = no; then
+    HAVE_UTIMENSAT=0
+    AC_LIBOBJ([utimensat])
+  else
+    AC_CACHE_CHECK([whether utimensat works],
+      [gl_cv_func_utimensat_works],
+      [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <fcntl.h>
+#include <sys/stat.h>
+#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.  */
+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="guessing no"])])
+    if test "$gl_cv_func_utimensat_works" != yes; then
+      REPLACE_UTIMENSAT=1
+      AC_LIBOBJ([utimensat])
+    fi
+  fi
+])
diff --git a/modules/sys_stat b/modules/sys_stat
index 708491f..246011f 100644
--- a/modules/sys_stat
+++ b/modules/sys_stat
@@ -36,6 +36,7 @@ sys/stat.h: sys_stat.in.h
              -e 's|@''GNULIB_MKFIFOAT''@|$(GNULIB_MKFIFOAT)|g' \
              -e 's|@''GNULIB_MKNODAT''@|$(GNULIB_MKNODAT)|g' \
              -e 's|@''GNULIB_STAT''@|$(GNULIB_STAT)|g' \
+             -e 's|@''GNULIB_UTIMENSAT''@|$(GNULIB_UTIMENSAT)|g' \
              -e 's|@''HAVE_FCHMODAT''@|$(HAVE_FCHMODAT)|g' \
              -e 's|@''HAVE_FSTATAT''@|$(HAVE_FSTATAT)|g' \
              -e 's|@''HAVE_FUTIMENS''@|$(HAVE_FUTIMENS)|g' \
@@ -44,12 +45,14 @@ sys/stat.h: sys_stat.in.h
              -e 's|@''HAVE_MKDIRAT''@|$(HAVE_MKDIRAT)|g' \
              -e 's|@''HAVE_MKFIFOAT''@|$(HAVE_MKFIFOAT)|g' \
              -e 's|@''HAVE_MKNODAT''@|$(HAVE_MKNODAT)|g' \
+             -e 's|@''HAVE_UTIMENSAT''@|$(HAVE_UTIMENSAT)|g' \
              -e 's|@''REPLACE_FSTAT''@|$(REPLACE_FSTAT)|g' \
              -e 's|@''REPLACE_FSTATAT''@|$(REPLACE_FSTATAT)|g' \
              -e 's|@''REPLACE_FUTIMENS''@|$(REPLACE_FUTIMENS)|g' \
              -e 's|@''REPLACE_LSTAT''@|$(REPLACE_LSTAT)|g' \
              -e 's|@''REPLACE_MKDIR''@|$(REPLACE_MKDIR)|g' \
              -e 's|@''REPLACE_STAT''@|$(REPLACE_STAT)|g' \
+             -e 's|@''REPLACE_UTIMENSAT''@|$(REPLACE_UTIMENSAT)|g' \
              -e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
              < $(srcdir)/sys_stat.in.h; \
        } > address@hidden && \
diff --git a/modules/utimensat b/modules/utimensat
new file mode 100644
index 0000000..5741b92
--- /dev/null
+++ b/modules/utimensat
@@ -0,0 +1,29 @@
+Description:
+Set file access and modification times of a file relative to a directory fd.
+
+Files:
+lib/utimensat.c
+m4/utimensat.m4
+
+Depends-on:
+openat
+sys_stat
+utimens
+
+configure.ac:
+gl_FUNC_UTIMENSAT
+gl_SYS_STAT_MODULE_INDICATOR([utimensat])
+
+Makefile.am:
+
+Include:
+<sys/stat.h>
+
+Link:
+$(LIB_CLOCK_GETTIME)
+
+License:
+GPL
+
+Maintainer:
+Eric Blake
diff --git a/modules/utimensat-tests b/modules/utimensat-tests
new file mode 100644
index 0000000..f40db57
--- /dev/null
+++ b/modules/utimensat-tests
@@ -0,0 +1,17 @@
+Files:
+tests/test-lutimens.h
+tests/test-utimens.h
+tests/test-utimensat.c
+
+Depends-on:
+progname
+timespec
+utimecmp
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([usleep])
+
+Makefile.am:
+TESTS += test-utimensat
+check_PROGRAMS += test-utimensat
+test_utimensat_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) @LIBINTL@
diff --git a/tests/test-utimensat.c b/tests/test-utimensat.c
new file mode 100644
index 0000000..a1a29ca
--- /dev/null
+++ b/tests/test-utimensat.c
@@ -0,0 +1,117 @@
+/* Tests of utimensat.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Eric Blake <address@hidden>, 2009.  */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "stat-time.h"
+#include "timespec.h"
+#include "utimecmp.h"
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+        {                                                                    \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__);  \
+          fflush (stderr);                                                   \
+          abort ();                                                          \
+        }                                                                    \
+    }                                                                        \
+  while (0)
+
+#define BASE "test-utimensat.t"
+
+#include "test-lutimens.h"
+#include "test-utimens.h"
+
+static int dfd = AT_FDCWD;
+
+/* Wrap utimensat to behave like utimens.  */
+static int
+do_utimensat (char const *name, struct timespec const times[2])
+{
+  return utimensat (dfd, name, times, 0);
+}
+
+/* Wrap utimensat to behave like lutimens.  */
+static int
+do_lutimensat (char const *name, struct timespec const times[2])
+{
+  return utimensat (dfd, name, times, AT_SYMLINK_NOFOLLOW);
+}
+
+int
+main ()
+{
+  int result1; /* Skip because of no symlink support.  */
+  int result2; /* Skip because of no lutimens support.  */
+  int fd;
+
+  /* Clean up any trash from prior testsuite runs.  */
+  ASSERT (system ("rm -rf " BASE "*") == 0);
+
+  /* Basic tests.  */
+  result1 = test_utimens (do_utimensat, true);
+  result2 = test_lutimens (do_lutimensat, result1 == 0);
+  dfd = open (".", O_RDONLY);
+  ASSERT (0 <= dfd);
+  ASSERT (test_utimens (do_utimensat, false) == result1);
+  ASSERT (test_lutimens (do_lutimensat, false) == result2);
+  /* We expect 0/0, 0/77, or 77/77, but not 77/0.  */
+  ASSERT (result1 <= result2);
+
+  /* Directory-relative tests.  */
+  ASSERT (mkdir (BASE "dir", 0700) == 0);
+  ASSERT (chdir (BASE "dir") == 0);
+  fd = creat ("file", 0600);
+  ASSERT (0 <= fd);
+  errno = 0;
+  ASSERT (utimensat (fd, ".", NULL, 0) == -1);
+  ASSERT (errno == ENOTDIR);
+  {
+    struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
+    struct stat st;
+    ASSERT (utimensat (dfd, BASE "dir/file", ts, 0) == 0);
+    ASSERT (stat ("file", &st) == 0);
+    ASSERT (st.st_atime == Y2K);
+    ASSERT (get_stat_atime_ns (&st) == 0);
+    ASSERT (st.st_mtime == Y2K);
+    ASSERT (get_stat_mtime_ns (&st) == 0);
+  }
+  ASSERT (close (fd) == 0);
+  ASSERT (close (dfd) == 0);
+  errno = 0;
+  ASSERT (utimensat (dfd, ".", NULL, 0) == -1);
+  ASSERT (errno == EBADF);
+
+  /* Cleanup.  */
+  ASSERT (chdir ("..") == 0);
+  ASSERT (unlink (BASE "dir/file") == 0);
+  ASSERT (rmdir (BASE "dir") == 0);
+  return result1 | result2;
+}
-- 
1.6.4.2


>From c9f01180055b3e7adeb29a52fd7f506a0d4b5f6b Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 12 Oct 2009 10:42:35 -0600
Subject: [PATCH 5/5] utimens-tests: tweak for cygwin

One millisecond was not reliably enough to cross a quantization
boundary on slower cygwin machines.
MORE TWEAKS NEEDED due to clock_gettime weirdness; may require
fixing lib/gettime.c first.

* tests/test-lutimens.h (test_lutimens): Lengthen sleep amount, to
ensure success.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog             |    6 ++++++
 tests/test-lutimens.h |    4 ++--
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 35dc932..4102b7b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2009-10-15  Eric Blake  <address@hidden>

+       utimens-tests: tweak for cygwin
+       * tests/test-lutimens.h (test_lutimens): Lengthen sleep amount, to
+       ensure success.
+
+2009-10-15  Eric Blake  <address@hidden>
+
        utimensat: new module
        * modules/utimensat: New file.
        * lib/utimensat.c (utimensat): Likewise.
diff --git a/tests/test-lutimens.h b/tests/test-lutimens.h
index cc764b0..7e409e1 100644
--- a/tests/test-lutimens.h
+++ b/tests/test-lutimens.h
@@ -110,9 +110,9 @@ test_lutimens (int (*func) (char const *, struct timespec 
const *), bool print)
 #if HAVE_USLEEP
   /* On Cygwin, the mere act of lstat changes symlink atime, even
      though POSIX says that only readlink is allowed to do that.
-     Sleeping for one millisecond is enough to expose this.  Platforms
+     Sleeping for fifteen milliseconds is enough to expose this.  Platforms
      without usleep either don't have symlinks, or are immune.  */
-  usleep (1000);
+  usleep (15000);
 #endif
   ASSERT (lstat (BASE "link", &st2) == 0);
   if (st1.st_atime != st2.st_atime
-- 
1.6.4.2







reply via email to

[Prev in Thread] Current Thread [Next in Thread]