From 0c278290674f9bb9749d27f2885da510a78acc79 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 15 Oct 2009 12:04:57 -0600 Subject: [PATCH] utimens: let lutimens work on non-symlinks Coreutils new 'touch -h' is easier to write if we guarantee POSIX semantics of utimensat(fd,"file",NULL,AT_SYMLINK_NOFOLLOW), rather than blindly failing with ENOSYS even 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. Signed-off-by: Eric Blake --- ChangeLog | 10 ++++++++++ lib/utimens.c | 18 +++++++++++------- tests/test-lutimens.h | 41 +++++++++++++++++++++++++++++++++-------- tests/test-utimens.c | 21 ++++++++++----------- tests/test-utimens.h | 28 ++++++++++++++++++++++++++-- 5 files changed, 90 insertions(+), 28 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1fdfbdec7..383e890e4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,15 @@ 2009-10-16 Eric Blake + 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. diff --git a/lib/utimens.c b/lib/utimens.c index c57ff8390..8709a1cf3 100644 --- a/lib/utimens.c +++ b/lib/utimens.c @@ -343,18 +343,20 @@ gl_futimens (int fd, char const *file, struct timespec const timespec[2]) 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) { @@ -400,7 +402,6 @@ lutimens (char const *file, struct timespec const timespec[2]) if (adjustment_needed) { - struct stat st; if (lstat (file, &st)) return -1; if (update_timespec (&st, &ts)) @@ -426,8 +427,11 @@ lutimens (char const *file, struct timespec const timespec[2]) } #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; } diff --git a/tests/test-lutimens.h b/tests/test-lutimens.h index 632b103c1..67658325c 100644 --- a/tests/test-lutimens.h +++ b/tests/test-lutimens.h @@ -24,12 +24,34 @@ static int 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); @@ -37,7 +59,16 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool print) } 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) @@ -57,12 +88,6 @@ test_lutimens (int (*func) (char const *, struct timespec const *), bool 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; diff --git a/tests/test-utimens.c b/tests/test-utimens.c index e43663a12..651ea9b51 100644 --- a/tests/test-utimens.c +++ b/tests/test-utimens.c @@ -71,20 +71,19 @@ do_fdutimens (char const *name, struct timespec const times[2]) 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; } diff --git a/tests/test-utimens.h b/tests/test-utimens.h index d9676f2e8..abb4d26af 100644 --- a/tests/test-utimens.h +++ b/tests/test-utimens.h @@ -18,9 +18,10 @@ /* 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; @@ -101,7 +102,30 @@ test_utimens (int (*func) (char const *, struct timespec const *)) 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; } -- 2.11.0