From 9a669cf64253a2b2149d7f7cc5e0664c1bc7dda9 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 17 Dec 2009 07:32:00 -0700 Subject: [PATCH] futimens, utimensat: work around Linux bug futimens is trivial - let fdutimens do the work. utimensat is tougher: we don't want to call into local_utimensat, because that can cause unnecessary chdir. So we have to repeat the logic from utimens.c. * m4/futimens.m4 (gl_FUNC_FUTIMENS): Detect ctime bug. * m4/utimensat.m4 (gl_FUNC_UTIMENSAT): Likewise. * lib/utimensat.c (rpl_utimensat): Work around it. * lib/futimens.c (rpl_futimens): Adjust comment. --- ChangeLog | 6 +++++ lib/futimens.c | 2 +- lib/utimensat.c | 64 +++++++++++++++++++++++++++++++++++++++++++++---- m4/futimens.m4 | 33 ++++++++++++++++++------- m4/utimensat.m4 | 28 ++++++++++++++++------ 5 files changed, 111 insertions(+), 22 deletions(-) diff --git a/ChangeLog b/ChangeLog index 04bdbcc8ac..eb560d5359 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2009-12-19 Eric Blake + futimens, utimensat: work around Linux bug + * m4/futimens.m4 (gl_FUNC_FUTIMENS): Detect ctime bug. + * m4/utimensat.m4 (gl_FUNC_UTIMENSAT): Likewise. + * lib/utimensat.c (rpl_utimensat): Work around it. + * lib/futimens.c (rpl_futimens): Adjust comment. + utimens: work around Linux ctime bug * lib/utimens.c (detect_ctime_bug): New helper function. (update_timespec): Differentiate between workaround needed for diff --git a/lib/futimens.c b/lib/futimens.c index 031a464359..e93ffc470a 100644 --- a/lib/futimens.c +++ b/lib/futimens.c @@ -32,6 +32,6 @@ futimens (int fd, struct timespec const times[2]) { /* fdutimens also works around bugs in native futimens, when running with glibc compiled against newer headers but on a Linux kernel - older than 2.6.26. */ + older than 2.6.32. */ return fdutimens (NULL, fd, times); } diff --git a/lib/utimensat.c b/lib/utimensat.c index 3808439f55..03e65bfae1 100644 --- a/lib/utimensat.c +++ b/lib/utimensat.c @@ -23,6 +23,8 @@ #include #include +#include "stat-time.h" +#include "timespec.h" #include "utimens.h" #if HAVE_UTIMENSAT @@ -31,10 +33,11 @@ /* If we have a native utimensat, but are compiling this file, then utimensat was defined to rpl_utimensat by our replacement - sys/stat.h. We assume the native version might fail with ENOSYS - (as is the case when using newer glibc but older Linux kernel). In - this scenario, rpl_utimensat checks whether the native version is - usable, and local_utimensat provides the fallback manipulation. */ + sys/stat.h. We assume the native version might fail with ENOSYS, + or succeed without properly affecting ctime (as is the case when + using newer glibc but older Linux kernel). In this scenario, + rpl_utimensat checks whether the native version is usable, and + local_utimensat provides the fallback manipulation. */ static int local_utimensat (int, char const *, struct timespec const[2], int); # define AT_FUNC_NAME local_utimensat @@ -45,10 +48,30 @@ int rpl_utimensat (int fd, char const *file, struct timespec const times[2], int flag) { + /* See comments in utimens.c for details. */ static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no. */ + static int utimensat_ctime_really; /* 0 = unknown, 1 = yes, -1 = no. */ if (0 <= utimensat_works_really) { - int result = utimensat (fd, file, times, flag); + int result; + struct stat st1; + struct stat st2; + struct timespec ts[2]; + /* Linux kernel 2.6.32 has a bug where mtime of UTIME_OMIT fails + to change ctime. */ + if (utimensat_ctime_really <= 0 && times + && times[0].tv_nsec != UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT) + { + if (fstatat (fd, file, &st1, flag)) + return -1; + if (utimensat_ctime_really < 0) + { + ts[0] = times[0]; + ts[1] = get_stat_mtime (&st1); + times = ts; + } + } + result = utimensat (fd, file, times, flag); /* Linux kernel 2.6.25 has a bug where it returns EINVAL for UTIME_NOW or UTIME_OMIT with non-zero tv_sec, which local_utimensat works around. Meanwhile, EINVAL for a bad @@ -59,6 +82,37 @@ rpl_utimensat (int fd, char const *file, struct timespec const times[2], if (result == 0 || (errno != ENOSYS && errno != EINVAL)) { utimensat_works_really = 1; + if (result == 0 && utimensat_ctime_really == 0 && times + && times[0].tv_nsec != UTIME_OMIT + && times[1].tv_nsec == UTIME_OMIT) + { + /* Perform a followup [l]stat. See detect_ctime_bug in + utimens.c for more details. */ + struct timespec now; + if (fstatat (fd, file, &st2, flag)) + return -1; + if (st1.st_ctime != st2.st_ctime + || get_stat_ctime_ns (&st1) != get_stat_ctime_ns (&st2)) + { + utimensat_ctime_really = 1; + return result; + } + if (times[0].tv_nsec == UTIME_NOW) + now = get_stat_atime (&st2); + else + gettime (&now); + if (now.tv_sec < st2.st_ctime + || 2 < now.tv_sec - st2.st_ctime + || (get_stat_ctime_ns (&st2) + && now.tv_sec - st2.st_ctime < 2 + && (20000000 < (1000000000 * (now.tv_sec - st2.st_ctime) + + now.tv_nsec + - get_stat_ctime_ns (&st2))))) + utimensat_ctime_really = -1; + ts[0] = times[0]; + ts[1] = get_stat_mtime (&st2); + result = utimensat (fd, file, ts, flag); + } return result; } } diff --git a/m4/futimens.m4 b/m4/futimens.m4 index 0547e7ad54..ba5d6b666f 100644 --- a/m4/futimens.m4 +++ b/m4/futimens.m4 @@ -1,4 +1,4 @@ -# serial 1 +# serial 2 # See if we need to provide futimens replacement. dnl Copyright (C) 2009 Free Software Foundation, Inc. @@ -22,17 +22,32 @@ AC_DEFUN([gl_FUNC_FUTIMENS], [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ #include #include +#include +]], [[struct timespec ts[2] = { { 1, UTIME_OMIT }, { 1, UTIME_NOW } }; + int fd = creat ("conftest.file", 0600); + struct stat st; + if (fd < 0) return 1; + if (futimens (AT_FDCWD, NULL)) return 2; + if (futimens (fd, ts)) return 3; + sleep (1); + ts[0].tv_nsec = UTIME_NOW; + ts[1].tv_nsec = UTIME_OMIT; + if (futimens (fd, ts)) return 4; + if (fstat (fd, &st)) return 5; + if (st.st_ctime < st.st_atime) return 6; + ]])], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #ifdef __linux__ -/* The Linux kernel added futimens in 2.6.22, but it had bugs until 2.6.26. - Always replace futimens to support older kernels. */ +/* The Linux kernel added futimens in 2.6.22, but has bugs with UTIME_OMIT + in 2.6.32. Always replace futimens to support older kernels. */ choke me #endif -]], [[struct timespec ts[2] = { { 1, UTIME_OMIT }, { 1, UTIME_OMIT } }; - if (futimens (AT_FDCWD, NULL)) return 1; - return futimens (open (".", O_RDONLY), ts);]])], - [gl_cv_func_futimens_works=yes], - [gl_cv_func_futimens_works="needs runtime check"], - [gl_cv_func_futimens_works="guessing no"])]) + ]])], + [gl_cv_func_futimens_works=yes], + [gl_cv_func_futimens_works="needs runtime check"])], + [gl_cv_func_futimens_works=no], + [gl_cv_func_futimens_works="guessing no"]) + rm -f conftest.file]) if test "$gl_cv_func_futimens_works" != yes; then REPLACE_FUTIMENS=1 AC_LIBOBJ([futimens]) diff --git a/m4/utimensat.m4 b/m4/utimensat.m4 index 2a6033f2c0..21e1ea526d 100644 --- a/m4/utimensat.m4 +++ b/m4/utimensat.m4 @@ -1,4 +1,4 @@ -# serial 1 +# serial 2 # See if we need to provide utimensat replacement. dnl Copyright (C) 2009 Free Software Foundation, Inc. @@ -22,15 +22,29 @@ AC_DEFUN([gl_FUNC_UTIMENSAT], [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ #include #include +#include +]], [[struct timespec ts[2] = { { 1, UTIME_OMIT }, { 1, UTIME_NOW } }; + struct stat st; + const char *f = "conftest.file"; + if (close (creat (f, 0600))) return 1; + if (utimensat (AT_FDCWD, f, NULL, AT_SYMLINK_NOFOLLOW)) return 2; + if (utimensat (AT_FDCWD, f, ts, 0)) return 3; + sleep (1); + ts[0].tv_nsec = UTIME_NOW; + ts[1].tv_nsec = UTIME_OMIT; + if (utimensat (AT_FDCWD, f, ts, 0)) return 4; + if (stat (f, &st)) return 5; + if (st.st_ctime < st.st_atime) return 6;]])], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #ifdef __linux__ -/* The Linux kernel added utimensat in 2.6.22, but it had bugs until 2.6.26. - Always replace utimensat to support older kernels. */ +/* The Linux kernel added utimensat in 2.6.22, but has bugs with UTIME_OMIT + in 2.6.32. Always replace utimensat to support older kernels. */ choke me #endif -]], [[struct timespec ts[2] = { { 1, UTIME_OMIT }, { 1, UTIME_OMIT } }; - return utimensat (AT_FDCWD, ".", NULL, AT_SYMLINK_NOFOLLOW);]])], - [gl_cv_func_utimensat_works=yes], - [gl_cv_func_utimensat_works="needs runtime check"], + ]])], + [gl_cv_func_utimensat_works=yes], + [gl_cv_func_utimensat_works="needs runtime check"])], + [gl_cv_func_utimensat_works=no], [gl_cv_func_utimensat_works="guessing no"])]) if test "$gl_cv_func_utimensat_works" != yes; then REPLACE_UTIMENSAT=1 -- 2.30.2