Problem encountered on Ubuntu 24.04 zfs mounted noatime. * tests/test-fdutimensat.c (main): * tests/test-futimens.h (test_futimens): * tests/test-lutimens.h (test_lutimens): * tests/test-utime.c (test_utime): * tests/test-utimens-common.h (checkable_atime): New function. * tests/test-utimens.h (test_utimens): * tests/test-utimensat.c (main): Do not check atime on file systems mounted noatime. --- ChangeLog | 13 ++++++++ doc/posix-functions/futimens.texi | 4 +++ doc/posix-functions/utimensat.texi | 4 +++ doc/posix-functions/utimes.texi | 4 +++ tests/test-fdutimensat.c | 10 ++++-- tests/test-futimens.h | 36 ++++++++++++++-------- tests/test-lutimens.h | 49 +++++++++++++++++++----------- tests/test-utime.c | 27 +++++++++++----- tests/test-utimens-common.h | 21 +++++++++++++ tests/test-utimens.h | 46 +++++++++++++++++++++------- tests/test-utimensat.c | 12 ++++++-- 11 files changed, 171 insertions(+), 55 deletions(-)
diff --git a/ChangeLog b/ChangeLog index c31e68d8ee..e82dd02834 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2024-08-09 Paul Eggert <egg...@cs.ucla.edu> + + test-utime: port to noatime file systems + Problem encountered on Ubuntu 24.04 zfs mounted noatime. + * tests/test-fdutimensat.c (main): + * tests/test-futimens.h (test_futimens): + * tests/test-lutimens.h (test_lutimens): + * tests/test-utime.c (test_utime): + * tests/test-utimens-common.h (checkable_atime): New function. + * tests/test-utimens.h (test_utimens): + * tests/test-utimensat.c (main): + Do not check atime on file systems mounted noatime. + 2024-08-09 Bruno Haible <br...@clisp.org> pthread-rwlock: Attempt to avoid test failure on some machines. diff --git a/doc/posix-functions/futimens.texi b/doc/posix-functions/futimens.texi index 2c8beca267..7df36c09ce 100644 --- a/doc/posix-functions/futimens.texi +++ b/doc/posix-functions/futimens.texi @@ -35,6 +35,10 @@ glibc 2.11, musl libc, Solaris 11. Portability problems not fixed by Gnulib: @itemize @item +On file systems mounted with the @code{noatime} attribute, +this function might not modify the access time as requested: +Linux kernel 6.9. +@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{fdutimens}. diff --git a/doc/posix-functions/utimensat.texi b/doc/posix-functions/utimensat.texi index 9024235416..189c5cb79a 100644 --- a/doc/posix-functions/utimensat.texi +++ b/doc/posix-functions/utimensat.texi @@ -44,6 +44,10 @@ AIX 7.2. Portability problems not fixed by Gnulib: @itemize @item +On file systems mounted with the @code{noatime} attribute, +this function might not modify the access time as requested: +Linux kernel 6.9. +@item On some platforms, timestamps of symbolic links cannot be modified, so the replacement fails with @code{ENOSYS} if passed the flag @code{AT_SYMLINK_NOFOLLOW} on a symlink. diff --git a/doc/posix-functions/utimes.texi b/doc/posix-functions/utimes.texi index 70ab62f4b7..98a97184d9 100644 --- a/doc/posix-functions/utimes.texi +++ b/doc/posix-functions/utimes.texi @@ -24,6 +24,10 @@ some platforms incorrectly round rather than truncate. Use @code{utimensat(AT_FDCWD,file,times,0)}, or the gnulib module @code{utimens}, instead. @item +On file systems mounted with the @code{noatime} attribute, +this function might not modify the access time as requested: +Linux kernel 6.9. +@item On some platforms, @code{utimes (file, NULL)} fails to set the file's timestamp to the current time: glibc 2.3.3. diff --git a/tests/test-fdutimensat.c b/tests/test-fdutimensat.c index a21e297f03..5f8d0f6d07 100644 --- a/tests/test-fdutimensat.c +++ b/tests/test-fdutimensat.c @@ -118,7 +118,7 @@ main (void) /* Directory relative tests. */ ASSERT (mkdir (BASE "dir", 0700) == 0); ASSERT (chdir (BASE "dir") == 0); - fd = creat ("file", 0600); + fd = open ("file", O_RDWR | O_CREAT | O_TRUNC, 0600); ASSERT (0 <= fd); errno = 0; ASSERT (fdutimensat (AT_FDCWD, fd, ".", NULL, 0) == -1); @@ -126,13 +126,17 @@ main (void) { struct timespec ts[2]; struct stat st; + bool check_atime = checkable_atime (fd, &st); ts[0].tv_sec = Y2K; ts[0].tv_nsec = 0; ts[1] = ts[0]; ASSERT (fdutimensat (fd, dfd, BASE "dir/file", ts, 0) == 0); ASSERT (stat ("file", &st) == 0); - ASSERT (st.st_atime == Y2K); - ASSERT (get_stat_atime_ns (&st) == 0); + if (check_atime) + { + ASSERT (st.st_atime == Y2K); + ASSERT (get_stat_atime_ns (&st) == 0); + } ASSERT (st.st_mtime == Y2K); ASSERT (get_stat_mtime_ns (&st) == 0); } diff --git a/tests/test-futimens.h b/tests/test-futimens.h index a6fd5c15cf..af8f630cc9 100644 --- a/tests/test-futimens.h +++ b/tests/test-futimens.h @@ -24,14 +24,14 @@ static int test_futimens (int (*func) (int, struct timespec const *), bool print) { - int fd = creat (BASE "file", 0600); + int fd = open (BASE "file", O_RDWR | O_CREAT | O_TRUNC, 0600); int result; struct stat st1; struct stat st2; ASSERT (0 <= fd); + bool check_atime = checkable_atime (fd, &st1); /* Sanity check. */ - ASSERT (fstat (fd, &st1) == 0); nap (); errno = 0; result = func (fd, NULL); @@ -117,8 +117,11 @@ test_futimens (int (*func) (int, struct timespec const *), ASSERT (errno == EINVAL); } ASSERT (fstat (fd, &st2) == 0); - ASSERT (st1.st_atime == st2.st_atime); - ASSERT (get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2)); + if (check_atime) + { + ASSERT (st1.st_atime == st2.st_atime); + ASSERT (get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2)); + } ASSERT (utimecmp (BASE "file", &st1, &st2, 0) == 0); /* Set both times. */ @@ -130,9 +133,12 @@ test_futimens (int (*func) (int, struct timespec const *), ts[1].tv_nsec = BILLION - 1; 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); + if (check_atime) + { + ASSERT (st2.st_atime == Y2K); + ASSERT (0 <= get_stat_atime_ns (&st2)); + ASSERT (get_stat_atime_ns (&st2) < BILLION / 2); + } ASSERT (st2.st_mtime == Y2K); ASSERT (0 <= get_stat_mtime_ns (&st2)); ASSERT (get_stat_mtime_ns (&st2) < BILLION); @@ -151,9 +157,12 @@ test_futimens (int (*func) (int, struct timespec const *), 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); + if (check_atime) + { + 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, UTIMECMP_TRUNCATE_SOURCE) <= 0); if (check_ctime) ASSERT (ctime_compare (&st2, &st3) < 0); @@ -162,8 +171,11 @@ test_futimens (int (*func) (int, struct timespec const *), ts[1].tv_nsec = UTIME_OMIT; ASSERT (func (fd, ts) == 0); ASSERT (fstat (fd, &st2) == 0); - ASSERT (st2.st_atime == BILLION); - ASSERT (get_stat_atime_ns (&st2) == 0); + if (check_atime) + { + 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 > 0) diff --git a/tests/test-lutimens.h b/tests/test-lutimens.h index 385b3c0acc..b909ec4b75 100644 --- a/tests/test-lutimens.h +++ b/tests/test-lutimens.h @@ -27,7 +27,6 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) int saved_errno; struct stat st1; struct stat st2; - bool atime_supported = true; /* Non-symlinks should be handled just like utimens. */ errno = 0; @@ -39,10 +38,15 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) errno = 0; ASSERT (func ("", NULL) == -1); ASSERT (errno == ENOENT); - ASSERT (close (creat (BASE "file", 0600)) == 0); - ASSERT (stat (BASE "file", &st1) == 0); + + int fd = open (BASE "file", O_RDWR | O_CREAT | O_TRUNC, 0600); + ASSERT (0 <= fd); + + bool check_atime = checkable_atime (fd, &st1); + ASSERT (close (fd) == 0); ASSERT (st1.st_atime != Y2K); ASSERT (st1.st_mtime != Y2K); + { struct timespec ts[2]; ts[0].tv_sec = Y2K; @@ -52,7 +56,8 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (func (BASE "file/", ts) == -1); ASSERT (errno == ENOTDIR); ASSERT (stat (BASE "file", &st2) == 0); - ASSERT (st1.st_atime == st2.st_atime); + if (check_atime) + ASSERT (st1.st_atime == st2.st_atime); ASSERT (st1.st_mtime == st2.st_mtime); } { @@ -64,7 +69,8 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (func (BASE "file", ts) == 0); } ASSERT (stat (BASE "file", &st2) == 0); - ASSERT (st2.st_atime == Y2K); + if (check_atime) + ASSERT (st2.st_atime == Y2K); ASSERT (st2.st_mtime == Y2K); if (check_ctime) ASSERT (ctime_compare (&st1, &st2) < 0); @@ -83,10 +89,12 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) saved_errno = errno; /* Make sure we did not reference through link by accident. */ ASSERT (stat (BASE "file", &st1) == 0); - ASSERT (st1.st_atime == Y2K); + if (check_atime) + ASSERT (st1.st_atime == Y2K); ASSERT (st1.st_mtime == Y2K); ASSERT (lstat (BASE "link", &st1) == 0); - ASSERT (st1.st_atime != Y2K); + if (check_atime) + ASSERT (st1.st_atime != Y2K); ASSERT (st1.st_mtime != Y2K); ASSERT (unlink (BASE "file") == 0); if (result == -1 && saved_errno == ENOSYS) @@ -100,13 +108,16 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) } ASSERT (!result); ASSERT (lstat (BASE "link", &st1) == 0); - /* On cygwin, lstat() changes atime of symlinks, so that lutimens - can only effectively modify mtime. */ nap (); 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; + + /* On cygwin, lstat() changes atime of symlinks, so that lutimens + can effectively modify only mtime. */ + bool check_atime_on_symlinks + = (check_atime + && st1.st_atime == st2.st_atime + && get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2)); + ASSERT (st1.st_ctime == st2.st_ctime); ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2)); @@ -132,7 +143,7 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (errno == EINVAL); } ASSERT (lstat (BASE "link", &st2) == 0); - if (atime_supported) + if (check_atime_on_symlinks) { ASSERT (st1.st_atime == st2.st_atime); ASSERT (get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2)); @@ -149,7 +160,7 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) nap (); ASSERT (func (BASE "link", ts) == 0); ASSERT (lstat (BASE "link", &st2) == 0); - if (atime_supported) + if (check_atime_on_symlinks) { ASSERT (st2.st_atime == Y2K); ASSERT (0 <= get_stat_atime_ns (&st2)); @@ -173,7 +184,7 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) nap (); ASSERT (func (BASE "link", ts) == 0); ASSERT (lstat (BASE "link", &st3) == 0); - if (atime_supported) + if (check_atime_on_symlinks) { ASSERT (st3.st_atime == Y2K); ASSERT (0 <= get_stat_atime_ns (&st3)); @@ -187,7 +198,7 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ts[1].tv_nsec = UTIME_OMIT; ASSERT (func (BASE "link", ts) == 0); ASSERT (lstat (BASE "link", &st2) == 0); - if (atime_supported) + if (check_atime_on_symlinks) { ASSERT (st2.st_atime == BILLION); ASSERT (get_stat_atime_ns (&st2) == 0); @@ -214,13 +225,15 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (stat (BASE "dir", &st1) == 0); ASSERT (st1.st_mtime == Y2K); ASSERT (lstat (BASE "link", &st1) == 0); - ASSERT (st1.st_atime != Y2K); + if (check_atime) + 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); + if (check_atime) + ASSERT (st1.st_atime != Y2K); ASSERT (st1.st_mtime != Y2K); /* Cleanup. */ diff --git a/tests/test-utime.c b/tests/test-utime.c index 00cee63265..fd87f91cd8 100644 --- a/tests/test-utime.c +++ b/tests/test-utime.c @@ -35,8 +35,11 @@ test_utime (bool print) struct stat st1; struct stat st2; - ASSERT (close (creat (BASE "file", 0600)) == 0); - ASSERT (stat (BASE "file", &st1) == 0); + int fd = open (BASE "file", O_RDWR | O_CREAT | O_TRUNC, 0600); + ASSERT (0 <= fd); + bool check_atime = checkable_atime (fd, &st1); + ASSERT (close (fd) == 0); + nap (); ASSERT (utime (BASE "file", NULL) == 0); ASSERT (stat (BASE "file", &st2) == 0); @@ -76,8 +79,11 @@ test_utime (bool print) 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)); + if (check_atime) + { + ASSERT (st1.st_atime == st2.st_atime); + ASSERT (get_stat_atime_ns (&st1) == get_stat_atime_ns (&st2)); + } ASSERT (utimecmp (BASE "file", &st1, &st2, 0) == 0); /* Set both times. */ @@ -86,9 +92,12 @@ test_utime (bool print) ts.actime = ts.modtime = Y2K; ASSERT (utime (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); + if (check_atime) + { + ASSERT (st2.st_atime == Y2K); + ASSERT (0 <= get_stat_atime_ns (&st2)); + ASSERT (get_stat_atime_ns (&st2) < BILLION / 2); + } ASSERT (st2.st_mtime == Y2K); ASSERT (0 <= get_stat_mtime_ns (&st2)); ASSERT (get_stat_mtime_ns (&st2) < BILLION); @@ -117,7 +126,9 @@ test_utime (bool print) ts.actime = ts.modtime = Y2K; ASSERT (utime (BASE "link", &ts) == 0); ASSERT (lstat (BASE "link", &st2) == 0); - /* Can't compare atimes, since lstat() changes symlink atime on cygwin. */ + /* Make sure symlink time hasn't been modified. + Can't compare symlink atimes, since when utime follows the + symlink it might update the symlink atime. */ ASSERT (st1.st_mtime == st2.st_mtime); ASSERT (stat (BASE "link", &st2) == 0); ASSERT (st2.st_mtime == Y2K); diff --git a/tests/test-utimens-common.h b/tests/test-utimens-common.h index 317a01b3da..f9fdb9a867 100644 --- a/tests/test-utimens-common.h +++ b/tests/test-utimens-common.h @@ -79,4 +79,25 @@ ctime_compare (struct stat const *a, struct stat const *b) return 0; } +/* Test whether FD's file access times are updated by the file system. + FD must be readable. Set *ST to the file's status, after any + change to its access time due to the test. */ +static bool +checkable_atime (int fd, struct stat *st) +{ + char buf[1]; + struct stat st1, st2; + + ASSERT (fstat (fd, &st1) == 0); + nap (); + ASSERT (read (fd, buf, sizeof buf) == 0); + ASSERT (fstat (fd, &st2) == 0); + bool check_atime + = (st1.st_atime != st2.st_atime + || get_stat_atime_ns (&st1) != get_stat_atime_ns (&st2)); + if (st) + *st = st2; + return check_atime; +} + #endif /* GL_TEST_UTIMENS_COMMON */ diff --git a/tests/test-utimens.h b/tests/test-utimens.h index 83d785a4dd..256d48de90 100644 --- a/tests/test-utimens.h +++ b/tests/test-utimens.h @@ -26,13 +26,18 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) struct stat st1; struct stat st2; - ASSERT (close (creat (BASE "file", 0600)) == 0); + int fd = open (BASE "file", O_RDWR | O_CREAT | O_TRUNC, 0600); + ASSERT (0 <= fd); /* If utimens truncates to worse resolution than the file system supports, then time can appear to go backwards between now and a follow-up utimens with UTIME_NOW or a NULL timespec. Use UTIMECMP_TRUNCATE_SOURCE to compensate, with st1 as the source. */ - ASSERT (stat (BASE "file", &st1) == 0); + bool check_atime = checkable_atime (fd, &st1); + ASSERT (close (fd) == 0); + ASSERT (st1.st_atime != Y2K); + ASSERT (st1.st_mtime != Y2K); + nap (); ASSERT (func (BASE "file", NULL) == 0); ASSERT (stat (BASE "file", &st2) == 0); @@ -108,9 +113,12 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) ts[1].tv_nsec = BILLION - 1; 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); + if (check_atime) + { + ASSERT (st2.st_atime == Y2K); + ASSERT (0 <= get_stat_atime_ns (&st2)); + ASSERT (get_stat_atime_ns (&st2) < BILLION / 2); + } ASSERT (st2.st_mtime == Y2K); ASSERT (0 <= get_stat_mtime_ns (&st2)); ASSERT (get_stat_mtime_ns (&st2) < BILLION); @@ -129,9 +137,12 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) 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); + if (check_atime) + { + 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) @@ -141,8 +152,11 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) ts[1].tv_nsec = UTIME_OMIT; ASSERT (func (BASE "file", ts) == 0); ASSERT (stat (BASE "file", &st2) == 0); - ASSERT (st2.st_atime == BILLION); - ASSERT (get_stat_atime_ns (&st2) == 0); + if (check_atime) + { + 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 > 0) @@ -170,9 +184,19 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) ts[1] = ts[0]; ASSERT (func (BASE "link", ts) == 0); ASSERT (lstat (BASE "link", &st2) == 0); - /* Can't compare atimes, since lstat() changes symlink atime on cygwin. */ + + /* Make sure symlink time hasn't been modified. + Can't compare symlink atimes, since when func follows the + symlink it might update the symlink atime. */ ASSERT (st1.st_mtime == st2.st_mtime); + ASSERT (get_stat_mtime_ns (&st1) == get_stat_mtime_ns (&st2)); + ASSERT (stat (BASE "link", &st2) == 0); + if (check_atime) + { + ASSERT (st2.st_atime == BILLION); + ASSERT (get_stat_atime_ns (&st2) == 0); + } ASSERT (st2.st_mtime == Y2K); ASSERT (get_stat_mtime_ns (&st2) == 0); } diff --git a/tests/test-utimensat.c b/tests/test-utimensat.c index 6819a335de..0f7d73b3c1 100644 --- a/tests/test-utimensat.c +++ b/tests/test-utimensat.c @@ -94,8 +94,11 @@ main (void) /* Directory-relative tests. */ ASSERT (mkdir (BASE "dir", 0700) == 0); ASSERT (chdir (BASE "dir") == 0); - fd = creat ("file", 0600); + fd = open ("file", O_RDWR | O_CREAT | O_TRUNC, 0600); ASSERT (0 <= fd); + + bool check_atime = checkable_atime (fd, NULL); + errno = 0; ASSERT (utimensat (fd, ".", NULL, 0) == -1); ASSERT (errno == ENOTDIR); @@ -108,8 +111,11 @@ main (void) ts[1].tv_nsec = 0; ASSERT (utimensat (dfd, BASE "dir/file", ts, AT_SYMLINK_NOFOLLOW) == 0); ASSERT (stat ("file", &st) == 0); - ASSERT (st.st_atime == Y2K); - ASSERT (get_stat_atime_ns (&st) == 0); + if (check_atime) + { + ASSERT (st.st_atime == Y2K); + ASSERT (get_stat_atime_ns (&st) == 0); + } ASSERT (st.st_mtime == Y2K); ASSERT (get_stat_mtime_ns (&st) == 0); } -- 2.43.0