Paul Eggert wrote:
> On this platform, <sys/time.h> declares utimens and lutimens and the
> C library defines them

But the functions defined in NetBSD's libc don't have the same
semantics as the one in Gnulib:
  - They don't validate their timespec[2] argument.
  - They don't bump the ctime when making a change.

The CI reported this test failure:

FAIL: test-utimens
==================

../../gltests/test-utimens.h:80: assertion 'func (BASE "file", ts) == -1' failed
Stack trace:
0x402e3c test_utimens
        ../../gltests/test-utimens.h:80
0x41e7d2 main
        ../../gltests/test-utimens.c:71

../../gltests/test-utimens.h:90: assertion 'func (BASE "file", ts) == -1' failed
Stack trace:
0x402f04 test_utimens
        ../../gltests/test-utimens.h:90
0x41e7d2 main
        ../../gltests/test-utimens.c:71

../../gltests/test-utimens.h:103: assertion 'st1.st_atime == st2.st_atime' 
failed
Stack trace:
0x403043 test_utimens
        ../../gltests/test-utimens.h:103
0x41e7d2 main
        ../../gltests/test-utimens.c:71

../../gltests/test-lutimens.h:132: assertion 'func (BASE "link", ts) == -1' 
failed
Stack trace:
0x404fbc test_lutimens
        ../../gltests/test-lutimens.h:132
0x41e840 main
        ../../gltests/test-utimens.c:75

../../gltests/test-lutimens.h:142: assertion 'func (BASE "link", ts) == -1' 
failed
Stack trace:
0x404f3e test_lutimens
        ../../gltests/test-lutimens.h:142
0x41e840 main
        ../../gltests/test-utimens.c:75

../../gltests/test-lutimens.h:148: assertion 'st1.st_atime == st2.st_atime' 
failed
Stack trace:
0x405b4d test_lutimens
        ../../gltests/test-lutimens.h:148
0x41e840 main
        ../../gltests/test-utimens.c:75

This patch fixes it.


2024-09-23  Bruno Haible  <br...@clisp.org>

        utimens: Fix test failure on NetBSD 10 (regression 2024-09-16).
        * lib/utimens.h (utimens): Declare as an override if utimens exists in
        libc.
        (lutimens): Declare as an override if lutimens exists in libc.
        * lib/utimens.c (is_valid_timespec, is_valid_timespecs): New functions,
        extracted from validate_timespec.
        (validate_timespec): Call is_valid_timespecs.
        (utimens, lutimens): On NetBSD, validate the argument before calling
        NetBSD's libc function.
        * tests/test-utimens-common.h (check_ctime): Set to -1 on NetBSD.

diff --git a/lib/utimens.c b/lib/utimens.c
index cd86a44ea7..3c81b5c349 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -78,6 +78,21 @@ static int utimensat_works_really;
 static int lutimensat_works_really;
 #endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */
 
+static bool
+is_valid_timespec (struct timespec const *timespec)
+{
+  return (timespec->tv_nsec == UTIME_NOW
+          || timespec->tv_nsec == UTIME_OMIT
+          || (0 <= timespec->tv_nsec && timespec->tv_nsec < TIMESPEC_HZ));
+}
+
+static bool
+is_valid_timespecs (struct timespec const timespec[2])
+{
+  return (is_valid_timespec (&timespec[0])
+          && is_valid_timespec (&timespec[1]));
+}
+
 /* 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 a positive value if the
@@ -90,14 +105,7 @@ validate_timespec (struct timespec timespec[2])
 {
   int result = 0;
   int utime_omit_count = 0;
-  if ((timespec[0].tv_nsec != UTIME_NOW
-       && timespec[0].tv_nsec != UTIME_OMIT
-       && ! (0 <= timespec[0].tv_nsec
-             && timespec[0].tv_nsec < TIMESPEC_HZ))
-      || (timespec[1].tv_nsec != UTIME_NOW
-          && timespec[1].tv_nsec != UTIME_OMIT
-          && ! (0 <= timespec[1].tv_nsec
-                && timespec[1].tv_nsec < TIMESPEC_HZ)))
+  if (!is_valid_timespecs (timespec))
     {
       errno = EINVAL;
       return -1;
@@ -516,24 +524,44 @@ fdutimens (int fd, char const *file, struct timespec 
const timespec[2])
   }
 }
 
-#if !HAVE_UTIMENS
 /* Set the access and modification timestamps of FILE to be
    TIMESPEC[0] and TIMESPEC[1], respectively.  */
 int
 utimens (char const *file, struct timespec const timespec[2])
+#undef utimens
 {
+#if HAVE_UTIMENS
+  /* NetBSD's native utimens() does not fulfil the Gnulib expectations:
+     At least in NetBSD 10.0, it does not validate the timespec argument.  */
+  if (timespec != NULL && !is_valid_timespecs (timespec))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+  return utimens (file, timespec);
+#else
   return fdutimens (-1, file, timespec);
-}
 #endif
+}
 
-#if !HAVE_LUTIMENS
 /* Set the access and modification timestamps 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])
+#undef lutimens
 {
+#if HAVE_LUTIMENS
+  /* NetBSD's native lutimens() does not fulfil the Gnulib expectations:
+     At least in NetBSD 10.0, it does not validate the timespec argument.  */
+  if (timespec != NULL && !is_valid_timespecs (timespec))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+  return lutimens (file, timespec);
+#else
   struct timespec adjusted_timespec[2];
   struct timespec *ts = timespec ? adjusted_timespec : NULL;
   int adjustment_needed = 0;
@@ -553,11 +581,11 @@ lutimens (char const *file, struct timespec const 
timespec[2])
      fdutimens' worry about buggy NFS clients.  But we do have to
      worry about bogus return values.  */
 
-#if HAVE_UTIMENSAT
+# if HAVE_UTIMENSAT
   if (0 <= lutimensat_works_really)
     {
       int result;
-# if defined __linux__ || defined __sun || defined __NetBSD__
+#  if defined __linux__ || defined __sun || defined __NetBSD__
       /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
          systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
          but work if both times are either explicitly specified or
@@ -582,9 +610,9 @@ lutimens (char const *file, struct timespec const 
timespec[2])
           /* Note that st is good, in case utimensat gives ENOSYS.  */
           adjustment_needed++;
         }
-# endif
+#  endif
       result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
-# ifdef __linux__
+#  ifdef __linux__
       /* Work around a kernel bug:
          https://bugzilla.redhat.com/show_bug.cgi?id=442352
          https://bugzilla.redhat.com/show_bug.cgi?id=449910
@@ -594,7 +622,7 @@ lutimens (char const *file, struct timespec const 
timespec[2])
          are no longer in common use.  */
       if (0 < result)
         errno = ENOSYS;
-# endif
+#  endif
       if (result == 0 || errno != ENOSYS)
         {
           utimensat_works_really = 1;
@@ -603,7 +631,7 @@ lutimens (char const *file, struct timespec const 
timespec[2])
         }
     }
   lutimensat_works_really = -1;
-#endif /* HAVE_UTIMENSAT */
+# endif /* HAVE_UTIMENSAT */
 
   /* The platform lacks an interface to set file timestamps with
      nanosecond resolution, so do the best we can, discarding any
@@ -619,7 +647,7 @@ lutimens (char const *file, struct timespec const 
timespec[2])
 
   /* On Linux, lutimes is a thin wrapper around utimensat, so there is
      no point trying lutimes if utimensat failed with ENOSYS.  */
-#if HAVE_LUTIMES && !HAVE_UTIMENSAT
+# if HAVE_LUTIMES && !HAVE_UTIMENSAT
   {
     struct timeval timeval[2];
     struct timeval *t;
@@ -639,7 +667,7 @@ lutimens (char const *file, struct timespec const 
timespec[2])
     if (result == 0 || errno != ENOSYS)
       return result;
   }
-#endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */
+# endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */
 
   /* Out of luck for symlinks, but we still handle regular files.  */
   if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st))
@@ -648,5 +676,5 @@ lutimens (char const *file, struct timespec const 
timespec[2])
     return fdutimens (-1, file, ts);
   errno = ENOSYS;
   return -1;
-}
 #endif
+}
diff --git a/lib/utimens.h b/lib/utimens.h
index e85477b849..762c3f9a85 100644
--- a/lib/utimens.h
+++ b/lib/utimens.h
@@ -33,12 +33,16 @@ extern "C" {
 #endif
 
 int fdutimens (int, char const *, struct timespec const [2]);
-#if !HAVE_UTIMENS
+
+#if HAVE_UTIMENS
+# define utimens rpl_utimens
+#endif
 int utimens (char const *, struct timespec const [2]);
+
+#if HAVE_LUTIMENS
+# define lutimens rpl_lutimens
 #endif
-#if !HAVE_LUTIMENS
 int lutimens (char const *, struct timespec const [2]);
-#endif
 
 #ifdef __cplusplus
 }
diff --git a/tests/test-utimens-common.h b/tests/test-utimens-common.h
index a59e65df8c..f564323630 100644
--- a/tests/test-utimens-common.h
+++ b/tests/test-utimens-common.h
@@ -55,7 +55,7 @@ enum {
    properly tracked change time.  See
    
<https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/stat-functions>.
  */
 #  define check_ctime 0
-# elif defined __APPLE__ && defined __MACH__
+# elif (defined __APPLE__ && defined __MACH__) || defined __NetBSD__
 /* On macOS, the ctime is not updated when only the st_atime changes.  */
 #  define check_ctime -1
 # else





Reply via email to