utimens: let lutimens work on non-symlinks
authorEric Blake <ebb9@byu.net>
Thu, 15 Oct 2009 18:04:57 +0000 (12:04 -0600)
committerEric Blake <ebb9@byu.net>
Fri, 16 Oct 2009 14:26:12 +0000 (08:26 -0600)
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 <ebb9@byu.net>
ChangeLog
lib/utimens.c
tests/test-lutimens.h
tests/test-utimens.c
tests/test-utimens.h

index 1fdfbde..383e890 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2009-10-16  Eric Blake  <ebb9@byu.net>
 
+       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.
index c57ff83..8709a1c 100644 (file)
@@ -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;
 }
index 632b103..6765832 100644 (file)
@@ -24,12 +24,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);
@@ -37,7 +59,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)
@@ -57,12 +88,6 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print)
     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;
index e43663a..651ea9b 100644 (file)
@@ -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;
 }
index d9676f2..abb4d26 100644 (file)
 
 /* This file is designed to test both utimens(a,b) and
    utimensat(AT_FDCWD,a,b,0).  FUNC is the function to test.  Assumes
-   that BASE and ASSERT are already defined.  */
+   that BASE and ASSERT are already defined.  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;
@@ -101,7 +102,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;
 }