Hi Eric, Paul, I'm seeing a test failure in test-utimens and test-futimens on a Linux/x86 machine, and have a hard time understanding the reason.
The test failure is occurring on a Linux 2.4.21 x86 machine (kaoru.il.thewrittenword.com) with gcc 3.2.3. test-utimens.h:101: assertion failed FAIL: test-utimens test-futimens.h:108: assertion failed FAIL: test-futimens On this machine, autoconf has determined that checking whether the utimes function works... yes and defined HAVE_WORKING_UTIMES to 1 accordingly. (Side note here: On a machine with a very similar configuration, same kernel version, same gcc version, but with a significative difference between NFS server time and NFS client time - ca. 7 minutes -, the autoconf test in m4/utimes.m4 gave the result checking whether the utimes function works... no because at m4/utimes.m4:63 the values were now = 1289514192 sbuf.st_atime = 1289513839 sbuf.st_mtime = 1289513839 therefore now - sbuf.st_atime <= 2 is false and now - sbuf.st_mtime <= 2 is also false. As a consequence, on this other machine HAVE_WORKING_UTIMES does not get defined, and the tests pass. It therefore looks to me like the code in m4/utimes.m4 lines 57..64 is unreliable. End of side note.) Analyzing the test-utimens failure: When I add a printf statement before test-utimens.h:101 /* Set both times. */ { struct timespec ts[2] = { { Y2K, BILLION / 2 - 1 }, { Y2K, BILLION - 1 } }; ASSERT (func (BASE "file", ts) == 0); ASSERT (stat (BASE "file", &st2) == 0); ASSERT (st2.st_atime == Y2K); ASSERT (0 <= get_stat_atime_ns (&st2)); ASSERT (get_stat_atime_ns (&st2) < BILLION / 2); fprintf (stderr, "%lu %lu %lu\n", (unsigned long) st2.st_atime, (unsigned long) st2.st_mtime, (unsigned long) Y2K); ASSERT (st2.st_mtime == Y2K); two sets of values get printed: 946684800 946684800 946684800 946684800 946684801 946684800 The first line is from test_utimens (utimens, true); The second line is from test_utimens (do_fdutimens, true); and fails. Let's concentrate on this second call. do_fdutimens invokes fdutimens. Here's where the functions are defined: $ nm ./test-utimens | grep utime 0804aa7c t do_fdutimens 0804aa64 t do_futimens 0804b548 T fdutimens U futimes@@GLIBC_2.3 0804b720 T lutimens 08048eb0 t test_futimens 0804955c t test_lutimens 0804a168 t test_utimens 0804adb0 T utimecmp 0804b708 T utimens U utimes@@GLIBC_2.0 So, it uses fdutimens() from gnulib and futimes() from glibc. Adding HAVE_BUGGY_NFS_TIME_STAMPS=1 does not help. Here are the corresponding library calls (ltrace): open64("test-utimens.tfile", 1, 027777721520) = 3 __errno_location() = 0xb75e3060 fsync(3, 0xbfffa1e0, 320, 0x804b74d, 1) = 0 futimes(3, 0xbfffa1e0, 320, 0x804b74d, 1) = 0 close(3) = 0 __xstat64(3, "test-utimens.tfile", 0xbfffa370) = 0 At the moment of the futimes() function gets called, here are its arguments: Breakpoint 1, fdutimens (fd=6, file=0x804f8c9 "test-utimens.tfile", timespec=0x0) at utimens.c:337 337 if (futimes (fd, t) == 0) (gdb) print t[0] $2 = {tv_sec = 946684800, tv_usec = 499999} (gdb) print t[1] $3 = {tv_sec = 946684800, tv_usec = 999999} So, you can see, the times passed have been truncated (not rounded) to microsecond resolution. And the corresponding system calls (strace): open("test-utimens.tfile", O_WRONLY|O_LARGEFILE) = 3 fsync(3) = 0 utime("/proc/self/fd/3", [2000/01/01-00:00:00, 2000/01/01-00:00:01]) = 0 close(3) = 0 stat64("test-utimens.tfile", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0 As you can see, the futimes() call resulted in an utime() call - and the mtime has been *rounded* to the nearest second. This comes from code at glibc/sysdeps/unix/sysv/linux/futimes.c. The rounding in there is present since the very first version of this file. Questions: - Is glibc's futimes() implementation correct? Is futimes() allowed to round up by as much as half a second? - If not, shouldn't gnulib work around it? - If yes, is the code that invokes futimes in lib/utimens.c correct? - Is the test correct, or should it allow a rounded-up mtime? Bruno ------------------ For reference, the relevant part of the preprocessed code of lib/utimens.c on this platform: static int validate_timespec (struct timespec timespec[2]) { int result = 0; int utime_omit_count = 0; ((void) ((timespec) ? 0 : (__assert_fail ("timespec", "utimens.c", 89, __PRETTY_FUNCTION__), 0))); if ((timespec[0].tv_nsec != (-1) && timespec[0].tv_nsec != (-2) && (timespec[0].tv_nsec < 0 || 1000000000 <= timespec[0].tv_nsec)) || (timespec[1].tv_nsec != (-1) && timespec[1].tv_nsec != (-2) && (timespec[1].tv_nsec < 0 || 1000000000 <= timespec[1].tv_nsec))) { (*__errno_location ()) = 22; return -1; } if (timespec[0].tv_nsec == (-1) || timespec[0].tv_nsec == (-2)) { timespec[0].tv_sec = 0; result = 1; if (timespec[0].tv_nsec == (-2)) utime_omit_count++; } if (timespec[1].tv_nsec == (-1) || timespec[1].tv_nsec == (-2)) { timespec[1].tv_sec = 0; result = 1; if (timespec[1].tv_nsec == (-2)) utime_omit_count++; } return result + (utime_omit_count == 1); } int fdutimens (int fd, char const *file, struct timespec const timespec[2]) { struct timespec adjusted_timespec[2]; struct timespec *ts = timespec ? adjusted_timespec : ((void *)0); int adjustment_needed = 0; struct stat st; if (ts) { adjusted_timespec[0] = timespec[0]; adjusted_timespec[1] = timespec[1]; adjustment_needed = validate_timespec (ts); } if (adjustment_needed < 0) return -1; if (!file) { if (fd < 0) { (*__errno_location ()) = 9; return -1; } if (dup2 (fd, fd) != fd) return -1; } # 208 "utimens.c" if (fd < 0) sync (); else fsync (fd); # 291 "utimens.c" if (adjustment_needed || (0 && fd < 0)) { if (adjustment_needed != 3 && (fd < 0 ? stat (file, &st) : fstat (fd, &st))) return -1; if (ts && update_timespec (&st, &ts)) return 0; } { struct timeval timeval[2]; struct timeval *t; if (ts) { timeval[0].tv_sec = ts[0].tv_sec; timeval[0].tv_usec = ts[0].tv_nsec / 1000; timeval[1].tv_sec = ts[1].tv_sec; timeval[1].tv_usec = ts[1].tv_nsec / 1000; t = timeval; } else t = ((void *)0); if (fd < 0) { } else { # 337 "utimens.c" if (futimes (fd, t) == 0) return 0; } if (!file) { return -1; } return utimes (file, t); # 370 "utimens.c" } }