2009-12-19 Eric Blake <ebb9@byu.net>
+ 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
{
/* 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);
}
#include <errno.h>
#include <fcntl.h>
+#include "stat-time.h"
+#include "timespec.h"
#include "utimens.h"
#if HAVE_UTIMENSAT
/* 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
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
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;
}
}
-# serial 1
+# serial 2
# See if we need to provide futimens replacement.
dnl Copyright (C) 2009 Free Software Foundation, Inc.
[AC_RUN_IFELSE([AC_LANG_PROGRAM([[
#include <fcntl.h>
#include <sys/stat.h>
+#include <unistd.h>
+]], [[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])
-# serial 1
+# serial 2
# See if we need to provide utimensat replacement.
dnl Copyright (C) 2009 Free Software Foundation, Inc.
[AC_RUN_IFELSE([AC_LANG_PROGRAM([[
#include <fcntl.h>
#include <sys/stat.h>
+#include <unistd.h>
+]], [[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