From 9963a98120addb9fd80299e5242554d62b002217 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 17 Dec 2009 12:30:47 -0700 Subject: [PATCH] utimens: check for ctime update futimens/utimensat on Linux fails to bump ctime if mtime is UTIME_OMIT and atime is specified. * tests/test-utimens-common.h (check_ctime): Define. * tests/test-utimens.h (test_utimens): Expose the Linux bug. * tests/test-futimens.h (test_futimens): Likewise. * tests/test-lutimens.h (test_lutimens): Likewise. * doc/posix-functions/futimens.texi (futimens): Document the bug. * doc/posix-functions/utimensat.texi (utimensat): Likewise. Signed-off-by: Eric Blake --- ChangeLog | 10 ++++++++ doc/posix-functions/futimens.texi | 4 ++++ doc/posix-functions/utimensat.texi | 8 +++++++ tests/test-futimens.h | 35 +++++++++++++++++++++++---- tests/test-lutimens.h | 48 ++++++++++++++++++++++++++++++++------ tests/test-utimens-common.h | 27 ++++++++++++++------- tests/test-utimens.h | 37 +++++++++++++++++++++++++---- 7 files changed, 144 insertions(+), 25 deletions(-) diff --git a/ChangeLog b/ChangeLog index 014e8f546..8a387748b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2009-12-19 Eric Blake + + utimens: check for ctime update + * tests/test-utimens-common.h (check_ctime): Define. + * tests/test-utimens.h (test_utimens): Expose the Linux bug. + * tests/test-futimens.h (test_futimens): Likewise. + * tests/test-lutimens.h (test_lutimens): Likewise. + * doc/posix-functions/futimens.texi (futimens): Document the bug. + * doc/posix-functions/utimensat.texi (utimensat): Likewise. + 2009-12-19 Bruno Haible dprintf-posix: Check against memory leak fixed on 2009-12-15. diff --git a/doc/posix-functions/futimens.texi b/doc/posix-functions/futimens.texi index fee3d087f..ce71bf4db 100644 --- a/doc/posix-functions/futimens.texi +++ b/doc/posix-functions/futimens.texi @@ -24,6 +24,10 @@ When using @code{UTIME_OMIT} or @code{UTIME_NOW}, some systems require 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. +@item +When using @code{UTIME_OMIT} for the modification time, but specifying +an access time, some systems fail to update the change time: +Linux kernel 2.6.32. @end itemize Portability problems not fixed by Gnulib: diff --git a/doc/posix-functions/utimensat.texi b/doc/posix-functions/utimensat.texi index 67f50785a..85fc218f4 100644 --- a/doc/posix-functions/utimensat.texi +++ b/doc/posix-functions/utimensat.texi @@ -22,10 +22,18 @@ This function returns a bogus value instead of failing with @code{ENOSYS} on some platforms: Linux kernel 2.6.21. @item +This function fails with @code{ENOSYS} if passed the flag +@code{AT_SYMLINK_NOFOLLOW} on a regular file: +Linux kernel 2.6.22. +@item When using @code{UTIME_OMIT} or @code{UTIME_NOW}, some systems require 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. +@item +When using @code{UTIME_OMIT} for the modification time, but specifying +an access time, some systems fail to update the change time: +Linux kernel 2.6.32. @end itemize Portability problems not fixed by Gnulib: diff --git a/tests/test-futimens.h b/tests/test-futimens.h index 7c05bbf1a..795aa9e56 100644 --- a/tests/test-futimens.h +++ b/tests/test-futimens.h @@ -53,6 +53,10 @@ test_futimens (int (*func) (int, struct timespec const *), UTIMECMP_TRUNCATE_SOURCE to compensate, with st1 as the source. */ ASSERT (0 <= utimecmp (BASE "file", &st2, &st1, UTIMECMP_TRUNCATE_SOURCE)); + if (check_ctime) + ASSERT (st1.st_ctime < st2.st_ctime + || (st1.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2))); { /* On some NFS systems, the 'now' timestamp of creat or a NULL timespec is determined by the server, but the 'now' timestamp @@ -101,17 +105,40 @@ test_futimens (int (*func) (int, struct timespec const *), ASSERT (st2.st_mtime == Y2K); ASSERT (0 <= get_stat_mtime_ns (&st2)); ASSERT (get_stat_mtime_ns (&st2) < BILLION); + if (check_ctime) + ASSERT (st1.st_ctime < st2.st_ctime + || (st1.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2))); } /* Play with UTIME_OMIT, UTIME_NOW. */ { + struct stat st3; struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } }; + 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); + ASSERT (utimecmp (BASE "file", &st1, &st3, 0) <= 0); + if (check_ctime) + ASSERT (st2.st_ctime < st3.st_ctime + || (st2.st_ctime == st3.st_ctime + && get_stat_ctime_ns (&st2) < get_stat_ctime_ns (&st3))); + nap (); + ts[0].tv_nsec = 0; + ts[1].tv_nsec = UTIME_OMIT; 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); - ASSERT (utimecmp (BASE "file", &st1, &st2, 0) <= 0); + 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) + ASSERT (st3.st_ctime < st2.st_ctime + || (st3.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st3) < get_stat_ctime_ns (&st2))); } /* Cleanup. */ diff --git a/tests/test-lutimens.h b/tests/test-lutimens.h index c9302c89e..f19df80d7 100644 --- a/tests/test-lutimens.h +++ b/tests/test-lutimens.h @@ -54,11 +54,16 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) } { struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } }; + nap (); ASSERT (func (BASE "file", ts) == 0); } - ASSERT (stat (BASE "file", &st1) == 0); - ASSERT (st1.st_atime == Y2K); - ASSERT (st1.st_mtime == Y2K); + ASSERT (stat (BASE "file", &st2) == 0); + ASSERT (st2.st_atime == Y2K); + ASSERT (st2.st_mtime == Y2K); + if (check_ctime) + ASSERT (st1.st_ctime < st2.st_ctime + || (st1.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2))); /* Play with symlink timestamps. */ if (symlink (BASE "file", BASE "link")) @@ -98,6 +103,8 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) if (st1.st_atime != st2.st_atime || get_stat_atime_ns (&st1) != get_stat_atime_ns (&st2)) atime_supported = false; + ASSERT (st1.st_ctime == st2.st_ctime); + ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2)); /* Invalid arguments. */ { @@ -123,6 +130,7 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) /* Set both times. */ { struct timespec ts[2] = { { Y2K, BILLION / 2 - 1 }, { Y2K, BILLION - 1 } }; + nap (); ASSERT (func (BASE "link", ts) == 0); ASSERT (lstat (BASE "link", &st2) == 0); if (atime_supported) @@ -134,20 +142,46 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (st2.st_mtime == Y2K); ASSERT (0 <= get_stat_mtime_ns (&st2)); ASSERT (get_stat_mtime_ns (&st2) < BILLION); + if (check_ctime) + ASSERT (st1.st_ctime < st2.st_ctime + || (st1.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2))); } /* Play with UTIME_OMIT, UTIME_NOW. */ { + struct stat st3; struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } }; + nap (); + ASSERT (func (BASE "link", ts) == 0); + ASSERT (lstat (BASE "link", &st3) == 0); + if (atime_supported) + { + ASSERT (st3.st_atime == Y2K); + ASSERT (0 <= get_stat_atime_ns (&st3)); + ASSERT (get_stat_atime_ns (&st3) < BILLION / 2); + } + ASSERT (utimecmp (BASE "link", &st1, &st3, 0) <= 0); + if (check_ctime) + ASSERT (st2.st_ctime < st3.st_ctime + || (st2.st_ctime == st3.st_ctime + && get_stat_ctime_ns (&st2) < get_stat_ctime_ns (&st3))); + nap (); + ts[0].tv_nsec = 0; + ts[1].tv_nsec = UTIME_OMIT; ASSERT (func (BASE "link", ts) == 0); ASSERT (lstat (BASE "link", &st2) == 0); if (atime_supported) { - ASSERT (st2.st_atime == Y2K); - ASSERT (0 <= get_stat_atime_ns (&st2)); - ASSERT (get_stat_atime_ns (&st2) < BILLION / 2); + ASSERT (st2.st_atime == BILLION); + ASSERT (get_stat_atime_ns (&st2) == 0); } - ASSERT (utimecmp (BASE "link", &st1, &st2, 0) <= 0); + ASSERT (st3.st_mtime == st2.st_mtime); + ASSERT (get_stat_mtime_ns (&st3) == get_stat_mtime_ns (&st2)); + if (check_ctime) + ASSERT (st3.st_ctime < st2.st_ctime + || (st3.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st3) < get_stat_ctime_ns (&st2))); } /* Symlink to directory. */ diff --git a/tests/test-utimens-common.h b/tests/test-utimens-common.h index 6c404cc5d..707971abe 100644 --- a/tests/test-utimens-common.h +++ b/tests/test-utimens-common.h @@ -19,14 +19,14 @@ #ifndef GL_TEST_UTIMENS_COMMON # define GL_TEST_UTIMENS_COMMON -#include -#include -#include -#include +# include +# include +# include +# include -#include "stat-time.h" -#include "timespec.h" -#include "utimecmp.h" +# include "stat-time.h" +# include "timespec.h" +# include "utimecmp.h" enum { BILLION = 1000 * 1000 * 1000, @@ -62,9 +62,18 @@ nap (void) a quantization boundary equal to the resolution. Our usage of utimecmp allows equality, so no need to waste 980 milliseconds if the replacement usleep rounds to 1 second. */ -#if HAVE_USLEEP +# if HAVE_USLEEP usleep (20 * 1000); /* 20 milliseconds. */ -#endif +# endif } +# if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__ +/* Skip ctime tests on native Windows, since it is either a copy of + mtime or birth time (depending on the file system), rather than a + properly tracked change time. */ +# define check_ctime 0 +# else +# define check_ctime 1 +# endif + #endif /* GL_TEST_UTIMENS_COMMON */ diff --git a/tests/test-utimens.h b/tests/test-utimens.h index 710741a7c..b211b41d8 100644 --- a/tests/test-utimens.h +++ b/tests/test-utimens.h @@ -37,6 +37,10 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (func (BASE "file", NULL) == 0); ASSERT (stat (BASE "file", &st2) == 0); ASSERT (0 <= utimecmp (BASE "file", &st2, &st1, UTIMECMP_TRUNCATE_SOURCE)); + if (check_ctime) + ASSERT (st1.st_ctime < st2.st_ctime + || (st1.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2))); { /* On some NFS systems, the 'now' timestamp of creat or a NULL timespec is determined by the server, but the 'now' timestamp @@ -97,18 +101,41 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (st2.st_mtime == Y2K); ASSERT (0 <= get_stat_mtime_ns (&st2)); ASSERT (get_stat_mtime_ns (&st2) < BILLION); + if (check_ctime) + ASSERT (st1.st_ctime < st2.st_ctime + || (st1.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2))); } /* Play with UTIME_OMIT, UTIME_NOW. */ { + struct stat st3; struct timespec ts[2] = { { BILLION, UTIME_OMIT }, { 0, UTIME_NOW } }; + nap (); 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); + 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); /* See comment above about this utimecmp call. */ - ASSERT (0 <= utimecmp (BASE "file", &st2, &st1, UTIMECMP_TRUNCATE_SOURCE)); + ASSERT (0 <= utimecmp (BASE "file", &st3, &st1, UTIMECMP_TRUNCATE_SOURCE)); + if (check_ctime) + ASSERT (st2.st_ctime < st3.st_ctime + || (st2.st_ctime == st3.st_ctime + && get_stat_ctime_ns (&st2) < get_stat_ctime_ns (&st3))); + nap (); + ts[0].tv_nsec = 0; + 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); + ASSERT (st3.st_mtime == st2.st_mtime); + ASSERT (get_stat_mtime_ns (&st3) == get_stat_mtime_ns (&st2)); + if (check_ctime) + ASSERT (st3.st_ctime < st2.st_ctime + || (st3.st_ctime == st2.st_ctime + && get_stat_ctime_ns (&st3) < get_stat_ctime_ns (&st2))); } /* Make sure this dereferences symlinks. */ -- 2.11.0