From 8c871c07bc6a6bd7f4112053863833020569b272 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Tue, 20 Oct 2009 16:47:36 -0600 Subject: [PATCH] utimensat: work around Solaris 9 bug utimes("file/",times) mistakenly succeeds. This commit doesn't fix utimes, but does make utimensat be careful before calling utimes. The test is now enhanced to test trailing slashes and directories. Meanwhile, cygwin 1.5 stat() on a directory changes atime (it does a readdir under the hood to populate st_nlink), so only mtime of a directory is reliable enough for testing. Cygwin 1.7 no longer has this problem, because it no longer wastes time on st_nlink. * lib/utimens.c (fdutimens, lutimens): Force a stat if platform has trailing slash bugs. * tests/test-lutimens.h (test_lutimens): Enhance test. * tests/test-utimens.h (test_utimens): Likewise. * doc/posix-functions/utime.texi (utime): Document the bug. * doc/posix-functions/utimes.texi (utimes): Likewise. * doc/posix-functions/utimensat.texi (utimensat): Likewise. * doc/glibc-functions/futimesat.texi (futimesat): Likewise. * doc/glibc-functions/lutimes.texi (lutimes): Mention utimens. * doc/posix-functions/futimens.texi (futimens): Mention limitation. Signed-off-by: Eric Blake --- ChangeLog | 12 ++++++++++++ doc/glibc-functions/futimesat.texi | 6 +++++- doc/glibc-functions/lutimes.texi | 3 ++- doc/posix-functions/futimens.texi | 9 ++++++++- doc/posix-functions/utime.texi | 6 +++++- doc/posix-functions/utimensat.texi | 11 ++++++++++- doc/posix-functions/utimes.texi | 6 +++++- lib/utimens.c | 16 +++++++++++----- tests/test-lutimens.h | 35 +++++++++++++++++++++++++++++++++++ tests/test-utimens.h | 12 ++++++++++++ 10 files changed, 105 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index fe0baaea7..327136602 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,17 @@ 2009-10-20 Eric Blake + utimensat: work around Solaris 9 bug + * lib/utimens.c (fdutimens, lutimens): Force a stat if platform + has trailing slash bugs. + * tests/test-lutimens.h (test_lutimens): Enhance test. + * tests/test-utimens.h (test_utimens): Likewise. + * doc/posix-functions/utime.texi (utime): Enhance documentation. + * doc/posix-functions/utimes.texi (utimes): Likewise. + * doc/posix-functions/utimensat.texi (utimensat): Likewise. + * doc/glibc-functions/futimesat.texi (futimesat): Likewise. + * doc/glibc-functions/lutimes.texi (lutimes): Likewise. + * doc/posix-functions/futimens.texi (futimens): Likewise. + fdutimensat: new module * modules/fdutimensat: New file. * lib/fdutimensat.c (fdutimensat): Likewise. diff --git a/doc/glibc-functions/futimesat.texi b/doc/glibc-functions/futimesat.texi index dbd3d1de3..109e4122f 100644 --- a/doc/glibc-functions/futimesat.texi +++ b/doc/glibc-functions/futimesat.texi @@ -16,6 +16,10 @@ glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 8, Cygwin 1.5.x, mingw, Interix 3.5, BeOS. @item +On some platforms, this function mis-handles trailing slash: +Solaris 9. +@item This function cannot set full timestamp resolution. Use -@code{file ? utimensat(fd,file,times,0) : futimens(fd,times)} instead. +@code{file ? utimensat(fd,file,times,0) : futimens(fd,times)}, or the +gnulib module fdutimensat, instead. @end itemize diff --git a/doc/glibc-functions/lutimes.texi b/doc/glibc-functions/lutimes.texi index 2971c99ec..2371c5125 100644 --- a/doc/glibc-functions/lutimes.texi +++ b/doc/glibc-functions/lutimes.texi @@ -16,7 +16,8 @@ MacOS X 10.3, OpenBSD 3.8, AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, mingw, Interix 3.5, BeOS. @item This function cannot set full timestamp resolution. Use -@code{utimensat(AT_FDCWD,file,times,AT_SYMLINK_NOFOLLOW)} instead. +@code{utimensat(AT_FDCWD,file,times,AT_SYMLINK_NOFOLLOW)}, or the +gnulib module utimens, instead. @item The mere act of using @code{lstat} modifies the access time of symlinks on some platforms, so @code{lutimes} can only effectively diff --git a/doc/posix-functions/futimens.texi b/doc/posix-functions/futimens.texi index 1434033ea..fee3d087f 100644 --- a/doc/posix-functions/futimens.texi +++ b/doc/posix-functions/futimens.texi @@ -13,6 +13,8 @@ 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 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 3.5, BeOS. +However, the replacement function may end up truncating timestamps to +less resolution than supported by the file system. @item This function returns a bogus value instead of failing with @code{ENOSYS} on some platforms: @@ -29,5 +31,10 @@ Portability problems not fixed by Gnulib: @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{gl_futimens}. +module @samp{utimens} provides a more reliable interface @code{fdutimens}. +@item +The mere act of using @code{stat} modifies the access time of +directories on some platforms, so @code{utimensat} can only +effectively change directory modification time: +Cygwin 1.5.x. @end itemize diff --git a/doc/posix-functions/utime.texi b/doc/posix-functions/utime.texi index d14685c1b..f50af81e1 100644 --- a/doc/posix-functions/utime.texi +++ b/doc/posix-functions/utime.texi @@ -16,8 +16,12 @@ file's timestamp to the current time. Portability problems not fixed by Gnulib: @itemize @item +On some platforms, this function mis-handles trailing slash: +Solaris 9. +@item This function cannot set full timestamp resolution. Use -@code{utimensat(AT_FDCWD,file,times,0)} instead. +@code{utimensat(AT_FDCWD,file,times,0)}, or the gnulib module utimens, +instead. @item On some platforms, the prototype for @code{utime} omits @code{const} for the second argument. Fortunately, the argument is not modified, diff --git a/doc/posix-functions/utimensat.texi b/doc/posix-functions/utimensat.texi index 7164965d6..67f50785a 100644 --- a/doc/posix-functions/utimensat.texi +++ b/doc/posix-functions/utimensat.texi @@ -13,6 +13,10 @@ 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 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 3.5, BeOS. +However, the replacement function may end up truncating timestamps to +less resolution than supported by the file system. Furthermore, the +replacement function is not safe to be used in libraries and is not +multithread-safe. @item This function returns a bogus value instead of failing with @code{ENOSYS} on some platforms: @@ -35,6 +39,11 @@ The mere act of using @code{lstat} modifies the access time of symlinks on some platforms, so @code{utimensat} with @code{AT_SYMLINK_NOFOLLOW} can only effectively change modification time: Cygwin. +@item +The mere act of using @code{stat} modifies the access time of +directories on some platforms, so @code{utimensat} can only +effectively change directory modification time: +Cygwin 1.5.x. @end itemize -The gnulib module utimens provides a similar interface. +The gnulib module fdutimensat provides a similar interface. diff --git a/doc/posix-functions/utimes.texi b/doc/posix-functions/utimes.texi index 5f6cb7b74..390aa988a 100644 --- a/doc/posix-functions/utimes.texi +++ b/doc/posix-functions/utimes.texi @@ -16,9 +16,13 @@ Portability problems not fixed by Gnulib: This function is missing on some platforms: mingw, Interix 3.5, BeOS. @item +On some platforms, this function mis-handles trailing slash: +Solaris 9. +@item This function cannot set full timestamp resolution. In particular, some platforms incorrectly round rather than truncate. Use -@code{utimensat(AT_FDCWD,file,times,0)} instead. +@code{utimensat(AT_FDCWD,file,times,0)}, or the gnulib module utimens, +instead. @item On some platforms, @code{utimes (file, NULL)} fails to set the file's timestamp to the current time: diff --git a/lib/utimens.c b/lib/utimens.c index b33f883e2..ffc60b699 100644 --- a/lib/utimens.c +++ b/lib/utimens.c @@ -60,6 +60,12 @@ struct utimbuf static int utimensat_works_really; #endif /* HAVE_UTIMENSAT || HAVE_UTIMENSAT */ +/* Solaris 9 mistakenly succeeds when given a non-directory with a + trailing slash. Force the use of rpl_stat for a fix. */ +#ifndef REPLACE_FUNC_STAT_FILE +# define REPLACE_FUNC_STAT_FILE 0 +#endif + /* 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 @@ -242,12 +248,12 @@ fdutimens (char const *file, int fd, struct timespec const timespec[2]) nanosecond resolution, so do the best we can, discarding any fractional part of the timestamp. */ - if (adjustment_needed) + if (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0)) { struct stat st; if (fd < 0 ? stat (file, &st) : fstat (fd, &st)) return -1; - if (update_timespec (&st, &ts)) + if (ts && update_timespec (&st, &ts)) return 0; } @@ -401,11 +407,11 @@ lutimens (char const *file, struct timespec const timespec[2]) nanosecond resolution, so do the best we can, discarding any fractional part of the timestamp. */ - if (adjustment_needed) + if (adjustment_needed || REPLACE_FUNC_STAT_FILE) { if (lstat (file, &st)) return -1; - if (update_timespec (&st, &ts)) + if (ts && update_timespec (&st, &ts)) return 0; } @@ -429,7 +435,7 @@ lutimens (char const *file, struct timespec const timespec[2]) #endif /* HAVE_LUTIMES */ /* Out of luck for symlinks, but we still handle regular files. */ - if (!adjustment_needed && lstat (file, &st)) + if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st)) return -1; if (!S_ISLNK (st.st_mode)) return fdutimens (file, -1, ts); diff --git a/tests/test-lutimens.h b/tests/test-lutimens.h index 67658325c..c9302c89e 100644 --- a/tests/test-lutimens.h +++ b/tests/test-lutimens.h @@ -34,6 +34,9 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (func ("no_such", NULL) == -1); ASSERT (errno == ENOENT); errno = 0; + ASSERT (func ("no_such/", NULL) == -1); + ASSERT (errno == ENOENT || errno == ENOTDIR); + errno = 0; ASSERT (func ("", NULL) == -1); ASSERT (errno == ENOENT); ASSERT (close (creat (BASE "file", 0600)) == 0); @@ -42,6 +45,15 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (st1.st_mtime != Y2K); { struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } }; + errno = 0; + ASSERT (func (BASE "file/", ts) == -1); + ASSERT (errno == ENOTDIR); + ASSERT (stat (BASE "file", &st2) == 0); + ASSERT (st1.st_atime == st2.st_atime); + ASSERT (st1.st_mtime == st2.st_mtime); + } + { + struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } }; ASSERT (func (BASE "file", ts) == 0); } ASSERT (stat (BASE "file", &st1) == 0); @@ -138,7 +150,30 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (utimecmp (BASE "link", &st1, &st2, 0) <= 0); } + /* Symlink to directory. */ + ASSERT (unlink (BASE "link") == 0); + ASSERT (symlink (BASE "dir", BASE "link") == 0); + ASSERT (mkdir (BASE "dir", 0700) == 0); + { + struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } }; + ASSERT (func (BASE "link/", ts) == 0); + } + /* On cygwin 1.5, stat() changes atime of directories, so only check + mtime. */ + ASSERT (stat (BASE "dir", &st1) == 0); + ASSERT (st1.st_mtime == Y2K); + ASSERT (lstat (BASE "link", &st1) == 0); + 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); + ASSERT (st1.st_mtime != Y2K); + /* Cleanup. */ + ASSERT (rmdir (BASE "dir") == 0); ASSERT (unlink (BASE "link") == 0); return 0; } diff --git a/tests/test-utimens.h b/tests/test-utimens.h index abb4d26af..710741a7c 100644 --- a/tests/test-utimens.h +++ b/tests/test-utimens.h @@ -58,6 +58,9 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (func ("no_such", NULL) == -1); ASSERT (errno == ENOENT); errno = 0; + ASSERT (func ("no_such/", NULL) == -1); + ASSERT (errno == ENOENT || errno == ENOTDIR); + errno = 0; ASSERT (func ("", NULL) == -1); ASSERT (errno == ENOENT); { @@ -72,6 +75,12 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) ASSERT (func (BASE "file", ts) == -1); ASSERT (errno == EINVAL); } + { + struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } }; + errno = 0; + ASSERT (func (BASE "file/", ts) == -1); + 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)); @@ -113,6 +122,9 @@ test_utimens (int (*func) (char const *, struct timespec const *), bool print) } ASSERT (lstat (BASE "link", &st1) == 0); ASSERT (st1.st_mtime != Y2K); + errno = 0; + ASSERT (func (BASE "link/", NULL) == -1); + ASSERT (errno == ENOTDIR); { struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } }; ASSERT (func (BASE "link", ts) == 0); -- 2.11.0