2009-10-20 Eric Blake <ebb9@byu.net>
+ 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.
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
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
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:
@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
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,
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:
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.
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:
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
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;
}
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;
}
#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);
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);
ASSERT (stat (BASE "file", &st1) == 0);
ASSERT (st1.st_atime != Y2K);
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 (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;
}
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 (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));
}
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);