2009-10-16 Eric Blake <ebb9@byu.net>
+ utimens: let lutimens work on non-symlinks
+ * lib/utimens.c (lutimens): Fall back to utimens rather than
+ failing with ENOSYS, when file is not a symlink.
+ (utimens): Reduce redirection.
+ * tests/test-lutimens.h (test_lutimens): Update test to cover
+ non-symlinks.
+ * tests/test-utimens.h (test_utimens): Update test to cover
+ symlinks.
+ * tests/test-utimens.c (main): Update caller.
+
utimens: cache whether utimensat syscall works
* lib/utimens.c (utimensat_works_really): New cache variable.
(fdutimens, lutimens): Use it to avoid failing syscall.
int
utimens (char const *file, struct timespec const timespec[2])
{
- return gl_futimens (-1, file, timespec);
+ return fdutimens (file, -1, timespec);
}
-/* Set the access and modification time stamps of the symlink FILE to
- be TIMESPEC[0] and TIMESPEC[1], respectively. Fail with ENOSYS if
- the platform does not support changing symlink timestamps. */
+/* Set the access and modification time stamps 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])
{
struct timespec adjusted_timespec[2];
struct timespec *ts = timespec ? adjusted_timespec : NULL;
int adjustment_needed = 0;
+ struct stat st;
if (ts)
{
if (adjustment_needed)
{
- struct stat st;
if (lstat (file, &st))
return -1;
if (update_timespec (&st, &ts))
}
#endif /* HAVE_LUTIMES */
- /* Out of luck. Symlink timestamps can't be changed. We won't
- bother changing the timestamps if FILE was not a symlink. */
+ /* Out of luck for symlinks, but we still handle regular files. */
+ if (!adjustment_needed && lstat (file, &st))
+ return -1;
+ if (!S_ISLNK (st.st_mode))
+ return fdutimens (file, -1, ts);
errno = ENOSYS;
return -1;
}
test_lutimens (int (*func) (char const *, struct timespec const *), bool print)
{
int result;
+ int saved_errno;
struct stat st1;
struct stat st2;
bool atime_supported = true;
- if (symlink ("nowhere", BASE "link"))
+ /* Non-symlinks should be handled just like utimens. */
+ errno = 0;
+ ASSERT (func ("no_such", NULL) == -1);
+ ASSERT (errno == ENOENT);
+ 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 } };
+ ASSERT (func (BASE "file", ts) == 0);
+ }
+ ASSERT (stat (BASE "file", &st1) == 0);
+ ASSERT (st1.st_atime == Y2K);
+ ASSERT (st1.st_mtime == Y2K);
+
+ /* Play with symlink timestamps. */
+ if (symlink (BASE "file", BASE "link"))
{
+ ASSERT (unlink (BASE "file") == 0);
if (print)
fputs ("skipping test: symlinks not supported on this file system\n",
stderr);
}
errno = 0;
result = func (BASE "link", NULL);
- if (result == -1 && errno == ENOSYS)
+ saved_errno = errno;
+ /* Make sure we did not reference through link by accident. */
+ ASSERT (stat (BASE "file", &st1) == 0);
+ ASSERT (st1.st_atime == Y2K);
+ ASSERT (st1.st_mtime == Y2K);
+ ASSERT (lstat (BASE "link", &st1) == 0);
+ ASSERT (st1.st_atime != Y2K);
+ ASSERT (st1.st_mtime != Y2K);
+ ASSERT (unlink (BASE "file") == 0);
+ if (result == -1 && saved_errno == ENOSYS)
{
ASSERT (unlink (BASE "link") == 0);
if (print)
atime_supported = false;
/* Invalid arguments. */
- errno = 0;
- ASSERT (func ("no_such", NULL) == -1);
- ASSERT (errno == ENOENT);
- errno = 0;
- ASSERT (func ("", NULL) == -1);
- ASSERT (errno == ENOENT);
{
struct timespec ts[2] = { { Y2K, UTIME_BOGUS_POS }, { Y2K, 0 } };
errno = 0;
int
main ()
{
- int result1;
- int result2;
+ int result1; /* Skip because of no symlink support. */
+ int result2; /* Skip because of no futimens support. */
+ int result3; /* Skip because of no lutimens support. */
/* Clean up any trash from prior testsuite runs. */
ASSERT (system ("rm -rf " BASE "*") == 0);
- ASSERT (test_utimens (utimens) == 0);
- ASSERT (test_utimens (do_fdutimens) == 0);
- result1 = test_futimens (do_futimens, true);
- if (result1)
- ASSERT (result1 == 77);
+ result1 = test_utimens (utimens, true);
+ ASSERT (test_utimens (do_fdutimens, false) == result1);
/* Print only one skip message. */
- result2 = test_lutimens (lutimens, result1 == 0);
- if (result2)
- ASSERT (result2 == 77);
- return result1 | result2;
+ result2 = test_futimens (do_futimens, result1 == 0);
+ result3 = test_lutimens (lutimens, (result1 + result2) == 0);
+ /* We expect 0/0, 0/77, or 77/77, but not 77/0. */
+ ASSERT (result1 <= result3);
+ return result1 | result2 | result3;
}
/* This file is designed to test both utimens(a,b) and
utimensat(AT_FDCWD,a,b,0). FUNC is the function to test. Assumes
- that BASE and ASSERT are already defined. */
+ that BASE and ASSERT are already defined. If PRINT, warn before
+ skipping tests with status 77. */
static int
-test_utimens (int (*func) (char const *, struct timespec const *))
+test_utimens (int (*func) (char const *, struct timespec const *), bool print)
{
struct stat st1;
struct stat st2;
ASSERT (0 <= utimecmp (BASE "file", &st2, &st1, UTIMECMP_TRUNCATE_SOURCE));
}
+ /* Make sure this dereferences symlinks. */
+ if (symlink (BASE "file", BASE "link"))
+ {
+ ASSERT (unlink (BASE "file") == 0);
+ if (print)
+ fputs ("skipping test: symlinks not supported on this file system\n",
+ stderr);
+ return 77;
+ }
+ ASSERT (lstat (BASE "link", &st1) == 0);
+ ASSERT (st1.st_mtime != Y2K);
+ {
+ struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
+ ASSERT (func (BASE "link", ts) == 0);
+ ASSERT (lstat (BASE "link", &st2) == 0);
+ /* Can't compare atimes, since lstat() changes symlink atime on cygwin. */
+ ASSERT (st1.st_mtime == st2.st_mtime);
+ ASSERT (stat (BASE "link", &st2) == 0);
+ ASSERT (st2.st_mtime == Y2K);
+ ASSERT (get_stat_mtime_ns (&st2) == 0);
+ }
+
/* Cleanup. */
+ ASSERT (unlink (BASE "link") == 0);
ASSERT (unlink (BASE "file") == 0);
return 0;
}