2009-10-16 Eric Blake <ebb9@byu.net>
+ utimensat: new module
+ * modules/utimensat: New file.
+ * lib/utimensat.c (utimensat): Likewise.
+ * m4/utimensat.m4 (gl_FUNC_UTIMENSAT): Likewise.
+ * lib/utimens.c (utimensat): Avoid recursion into rpl_utimensat,
+ so we can work around Linux bugs.
+ * m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses.
+ * modules/sys_stat (Makefile.am): Substitute them.
+ * lib/sys_stat.in.h (utimensat): Declare it.
+ * MODULES.html.sh (systems lacking POSIX:2008): Mention module.
+ * doc/posix-functions/utimensat.texi (utimensat): Likewise.
+ * modules/utimensat-tests: New test.
+ * tests/test-utimensat.c: Likewise.
+
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.
func_module unistd
func_module unlink
func_module utime
+ func_module utimensat
func_module vasnprintf-posix
func_module vasprintf-posix
func_module vfprintf-posix
POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/utimensat.html}
-Gnulib module: ---
+Gnulib module: utimensat
Portability problems fixed by Gnulib:
@itemize
-@end itemize
-
-Portability problems not fixed by Gnulib:
-@itemize
@item
This function is missing on some platforms:
glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
the @code{tv_sec} argument to be 0, and don't necessarily handle all
file permissions in the manner required by POSIX:
Linux kernel 2.6.25.
+@end itemize
+
+Portability problems not fixed by Gnulib:
+@itemize
@item
On some platforms, timestamps of symbolic links cannot be modified, so
-this function fails with @code{ENOSYS} if passed the flag
-@code{AT_SYMLINK_NOFOLLOW}.
+the replacement fails with @code{ENOSYS} if passed the flag
+@code{AT_SYMLINK_NOFOLLOW} on a symlink.
@item
The mere act of using @code{lstat} modifies the access time of
symlinks on some platforms, so @code{utimensat} with
#endif
+#if @GNULIB_UTIMENSAT@
+# if @REPLACE_UTIMENSAT@
+# undef utimensat
+# define utimensat rpl_utimensat
+# endif
+# if !@HAVE_UTIMENSAT@ || @REPLACE_UTIMENSAT@
+ extern int utimensat (int fd, char const *name,
+ struct timespec const times[2], int flag);
+# endif
+#elif defined GNULIB_POSIXCHECK
+# undef utimensat
+# define utimensat(d,n,t,f) \
+ (GL_LINK_WARNING ("utimensat is not portable - " \
+ "use gnulib module utimensat for portability"), \
+ utimensat (d, n, t, f))
+#endif
+
+
#ifdef __cplusplus
}
#endif
};
#endif
-/* Avoid recursion with rpl_futimens. */
+/* Avoid recursion with rpl_futimens or rpl_utimensat. */
#undef futimens
+#undef utimensat
#if HAVE_UTIMENSAT || HAVE_FUTIMENS
/* Cache variable for whether syscall works; used to avoid calling the
--- /dev/null
+/* Set the access and modification time of a file relative to directory fd.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+
+#include "utimens.h"
+
+#if HAVE_UTIMENSAT
+
+# undef 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. */
+
+static int local_utimensat (int, char const *, struct timespec const[2], int);
+# define AT_FUNC_NAME local_utimensat
+
+/* Like utimensat, but work around native bugs. */
+
+int
+rpl_utimensat (int fd, char const *file, struct timespec const times[2],
+ int flag)
+{
+ static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no. */
+ if (0 <= utimensat_works_really)
+ {
+ int 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
+ flag is indeterminate whether the native utimensat works, but
+ local_utimensat will also reject it. */
+ if (result == -1 && errno == EINVAL && (flag & ~AT_SYMLINK_NOFOLLOW))
+ return result;
+ if (result == 0 || (errno != ENOSYS && errno != EINVAL))
+ {
+ utimensat_works_really = 1;
+ return result;
+ }
+ }
+ /* No point in trying openat/futimens, since on Linux, futimens is
+ implemented with the same syscall as utimensat. Only avoid the
+ native utimensat due to an ENOSYS failure; an EINVAL error was
+ data-dependent, and the next caller may pass valid data. */
+ if (0 <= utimensat_works_really && errno == ENOSYS)
+ utimensat_works_really = -1;
+ return local_utimensat (fd, file, times, flag);
+}
+
+#else /* !HAVE_UTIMENSAT */
+
+# define AT_FUNC_NAME utimensat
+
+#endif /* !HAVE_UTIMENSAT */
+
+/* Set the access and modification time stamps of FILE to be
+ TIMESPEC[0] and TIMESPEC[1], respectively; relative to directory
+ FD. If flag is AT_SYMLINK_NOFOLLOW, change the times of a symlink,
+ or fail with ENOSYS if not possible. If TIMESPEC is null, set the
+ time stamps to the current time. If possible, do it without
+ changing the working directory. Otherwise, resort to using
+ save_cwd/fchdir, then utimens/restore_cwd. If either the save_cwd
+ or the restore_cwd fails, then give a diagnostic and exit nonzero.
+ Return 0 on success, -1 (setting errno) on failure. */
+
+/* AT_FUNC_NAME is now utimensat or local_utimensat. */
+#define AT_FUNC_F1 lutimens
+#define AT_FUNC_F2 utimens
+#define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
+#define AT_FUNC_POST_FILE_PARAM_DECLS , struct timespec const ts[2], int flag
+#define AT_FUNC_POST_FILE_ARGS , ts
+#include "at-func.c"
+#undef AT_FUNC_NAME
+#undef AT_FUNC_F1
+#undef AT_FUNC_F2
+#undef AT_FUNC_USE_F1_COND
+#undef AT_FUNC_POST_FILE_PARAM_DECLS
+#undef AT_FUNC_POST_FILE_ARGS
-# sys_stat_h.m4 serial 18 -*- Autoconf -*-
+# sys_stat_h.m4 serial 19 -*- Autoconf -*-
dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
AC_DEFUN([gl_SYS_STAT_H_DEFAULTS],
[
AC_REQUIRE([gl_UNISTD_H_DEFAULTS]) dnl for REPLACE_FCHDIR
- GNULIB_FCHMODAT=0; AC_SUBST([GNULIB_FCHMODAT])
- GNULIB_FSTATAT=0; AC_SUBST([GNULIB_FSTATAT])
- GNULIB_FUTIMENS=0; AC_SUBST([GNULIB_FUTIMENS])
- GNULIB_LCHMOD=0; AC_SUBST([GNULIB_LCHMOD])
- GNULIB_LSTAT=0; AC_SUBST([GNULIB_LSTAT])
- GNULIB_MKDIRAT=0; AC_SUBST([GNULIB_MKDIRAT])
- GNULIB_MKFIFOAT=0; AC_SUBST([GNULIB_MKFIFOAT])
- GNULIB_MKNODAT=0; AC_SUBST([GNULIB_MKNODAT])
- GNULIB_STAT=0; AC_SUBST([GNULIB_STAT])
+ GNULIB_FCHMODAT=0; AC_SUBST([GNULIB_FCHMODAT])
+ GNULIB_FSTATAT=0; AC_SUBST([GNULIB_FSTATAT])
+ GNULIB_FUTIMENS=0; AC_SUBST([GNULIB_FUTIMENS])
+ GNULIB_LCHMOD=0; AC_SUBST([GNULIB_LCHMOD])
+ GNULIB_LSTAT=0; AC_SUBST([GNULIB_LSTAT])
+ GNULIB_MKDIRAT=0; AC_SUBST([GNULIB_MKDIRAT])
+ GNULIB_MKFIFOAT=0; AC_SUBST([GNULIB_MKFIFOAT])
+ GNULIB_MKNODAT=0; AC_SUBST([GNULIB_MKNODAT])
+ GNULIB_STAT=0; AC_SUBST([GNULIB_STAT])
+ GNULIB_UTIMENSAT=0; AC_SUBST([GNULIB_UTIMENSAT])
dnl Assume proper GNU behavior unless another module says otherwise.
- HAVE_FCHMODAT=1; AC_SUBST([HAVE_FCHMODAT])
- HAVE_FSTATAT=1; AC_SUBST([HAVE_FSTATAT])
- HAVE_FUTIMENS=1; AC_SUBST([HAVE_FUTIMENS])
- HAVE_LCHMOD=1; AC_SUBST([HAVE_LCHMOD])
- HAVE_LSTAT=1; AC_SUBST([HAVE_LSTAT])
- HAVE_MKDIRAT=1; AC_SUBST([HAVE_MKDIRAT])
- HAVE_MKFIFOAT=1; AC_SUBST([HAVE_MKFIFOAT])
- HAVE_MKNODAT=1; AC_SUBST([HAVE_MKNODAT])
- REPLACE_FSTAT=0; AC_SUBST([REPLACE_FSTAT])
- REPLACE_FSTATAT=0; AC_SUBST([REPLACE_FSTATAT])
- REPLACE_FUTIMENS=0; AC_SUBST([REPLACE_FUTIMENS])
- REPLACE_LSTAT=0; AC_SUBST([REPLACE_LSTAT])
- REPLACE_MKDIR=0; AC_SUBST([REPLACE_MKDIR])
- REPLACE_STAT=0; AC_SUBST([REPLACE_STAT])
+ HAVE_FCHMODAT=1; AC_SUBST([HAVE_FCHMODAT])
+ HAVE_FSTATAT=1; AC_SUBST([HAVE_FSTATAT])
+ HAVE_FUTIMENS=1; AC_SUBST([HAVE_FUTIMENS])
+ HAVE_LCHMOD=1; AC_SUBST([HAVE_LCHMOD])
+ HAVE_LSTAT=1; AC_SUBST([HAVE_LSTAT])
+ HAVE_MKDIRAT=1; AC_SUBST([HAVE_MKDIRAT])
+ HAVE_MKFIFOAT=1; AC_SUBST([HAVE_MKFIFOAT])
+ HAVE_MKNODAT=1; AC_SUBST([HAVE_MKNODAT])
+ HAVE_UTIMENSAT=1; AC_SUBST([HAVE_UTIMENSAT])
+ REPLACE_FSTAT=0; AC_SUBST([REPLACE_FSTAT])
+ REPLACE_FSTATAT=0; AC_SUBST([REPLACE_FSTATAT])
+ REPLACE_FUTIMENS=0; AC_SUBST([REPLACE_FUTIMENS])
+ REPLACE_LSTAT=0; AC_SUBST([REPLACE_LSTAT])
+ REPLACE_MKDIR=0; AC_SUBST([REPLACE_MKDIR])
+ REPLACE_STAT=0; AC_SUBST([REPLACE_STAT])
+ REPLACE_UTIMENSAT=0; AC_SUBST([REPLACE_UTIMENSAT])
])
--- /dev/null
+# serial 1
+# See if we need to provide utimensat replacement.
+
+dnl Copyright (C) 2009 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Written by Eric Blake.
+
+AC_DEFUN([gl_FUNC_UTIMENSAT],
+[
+ AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
+ AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+ AC_CHECK_FUNCS_ONCE([utimensat])
+ if test $ac_cv_func_utimensat = no; then
+ HAVE_UTIMENSAT=0
+ AC_LIBOBJ([utimensat])
+ else
+ AC_CACHE_CHECK([whether utimensat works],
+ [gl_cv_func_utimensat_works],
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <fcntl.h>
+#include <sys/stat.h>
+#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. */
+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="guessing no"])])
+ if test "$gl_cv_func_utimensat_works" != yes; then
+ REPLACE_UTIMENSAT=1
+ AC_LIBOBJ([utimensat])
+ fi
+ fi
+])
-e 's|@''GNULIB_MKFIFOAT''@|$(GNULIB_MKFIFOAT)|g' \
-e 's|@''GNULIB_MKNODAT''@|$(GNULIB_MKNODAT)|g' \
-e 's|@''GNULIB_STAT''@|$(GNULIB_STAT)|g' \
+ -e 's|@''GNULIB_UTIMENSAT''@|$(GNULIB_UTIMENSAT)|g' \
-e 's|@''HAVE_FCHMODAT''@|$(HAVE_FCHMODAT)|g' \
-e 's|@''HAVE_FSTATAT''@|$(HAVE_FSTATAT)|g' \
-e 's|@''HAVE_FUTIMENS''@|$(HAVE_FUTIMENS)|g' \
-e 's|@''HAVE_MKDIRAT''@|$(HAVE_MKDIRAT)|g' \
-e 's|@''HAVE_MKFIFOAT''@|$(HAVE_MKFIFOAT)|g' \
-e 's|@''HAVE_MKNODAT''@|$(HAVE_MKNODAT)|g' \
+ -e 's|@''HAVE_UTIMENSAT''@|$(HAVE_UTIMENSAT)|g' \
-e 's|@''REPLACE_FSTAT''@|$(REPLACE_FSTAT)|g' \
-e 's|@''REPLACE_FSTATAT''@|$(REPLACE_FSTATAT)|g' \
-e 's|@''REPLACE_FUTIMENS''@|$(REPLACE_FUTIMENS)|g' \
-e 's|@''REPLACE_LSTAT''@|$(REPLACE_LSTAT)|g' \
-e 's|@''REPLACE_MKDIR''@|$(REPLACE_MKDIR)|g' \
-e 's|@''REPLACE_STAT''@|$(REPLACE_STAT)|g' \
+ -e 's|@''REPLACE_UTIMENSAT''@|$(REPLACE_UTIMENSAT)|g' \
-e '/definition of GL_LINK_WARNING/r $(LINK_WARNING_H)' \
< $(srcdir)/sys_stat.in.h; \
} > $@-t && \
--- /dev/null
+Description:
+Set file access and modification times of a file relative to a directory fd.
+
+Files:
+lib/utimensat.c
+m4/utimensat.m4
+
+Depends-on:
+openat
+sys_stat
+utimens
+
+configure.ac:
+gl_FUNC_UTIMENSAT
+gl_SYS_STAT_MODULE_INDICATOR([utimensat])
+
+Makefile.am:
+
+Include:
+<sys/stat.h>
+
+Link:
+$(LIB_CLOCK_GETTIME)
+
+License:
+GPL
+
+Maintainer:
+Eric Blake
--- /dev/null
+Files:
+tests/test-lutimens.h
+tests/test-utimens.h
+tests/test-utimens-common.h
+tests/test-utimensat.c
+
+Depends-on:
+progname
+timespec
+utimecmp
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([usleep])
+
+Makefile.am:
+TESTS += test-utimensat
+check_PROGRAMS += test-utimensat
+test_utimensat_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) @LIBINTL@
--- /dev/null
+/* Tests of utimensat.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009. */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "stat-time.h"
+#include "timespec.h"
+#include "utimecmp.h"
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-utimensat.t"
+
+#include "test-lutimens.h"
+#include "test-utimens.h"
+
+static int dfd = AT_FDCWD;
+
+/* Wrap utimensat to behave like utimens. */
+static int
+do_utimensat (char const *name, struct timespec const times[2])
+{
+ return utimensat (dfd, name, times, 0);
+}
+
+/* Wrap utimensat to behave like lutimens. */
+static int
+do_lutimensat (char const *name, struct timespec const times[2])
+{
+ return utimensat (dfd, name, times, AT_SYMLINK_NOFOLLOW);
+}
+
+int
+main ()
+{
+ int result1; /* Skip because of no symlink support. */
+ int result2; /* Skip because of no lutimens support. */
+ int fd;
+
+ /* Clean up any trash from prior testsuite runs. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ /* Basic tests. */
+ result1 = test_utimens (do_utimensat, true);
+ result2 = test_lutimens (do_lutimensat, result1 == 0);
+ dfd = open (".", O_RDONLY);
+ ASSERT (0 <= dfd);
+ ASSERT (test_utimens (do_utimensat, false) == result1);
+ ASSERT (test_lutimens (do_lutimensat, false) == result2);
+ /* We expect 0/0, 0/77, or 77/77, but not 77/0. */
+ ASSERT (result1 <= result2);
+
+ /* Directory-relative tests. */
+ ASSERT (mkdir (BASE "dir", 0700) == 0);
+ ASSERT (chdir (BASE "dir") == 0);
+ fd = creat ("file", 0600);
+ ASSERT (0 <= fd);
+ errno = 0;
+ ASSERT (utimensat (fd, ".", NULL, 0) == -1);
+ ASSERT (errno == ENOTDIR);
+ {
+ struct timespec ts[2] = { { Y2K, 0 }, { Y2K, 0 } };
+ struct stat st;
+ ASSERT (utimensat (dfd, BASE "dir/file", ts, AT_SYMLINK_NOFOLLOW) == 0);
+ ASSERT (stat ("file", &st) == 0);
+ ASSERT (st.st_atime == Y2K);
+ ASSERT (get_stat_atime_ns (&st) == 0);
+ ASSERT (st.st_mtime == Y2K);
+ ASSERT (get_stat_mtime_ns (&st) == 0);
+ }
+ ASSERT (close (fd) == 0);
+ ASSERT (close (dfd) == 0);
+ errno = 0;
+ ASSERT (utimensat (dfd, ".", NULL, 0) == -1);
+ ASSERT (errno == EBADF);
+
+ /* Cleanup. */
+ ASSERT (chdir ("..") == 0);
+ ASSERT (unlink (BASE "dir/file") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+ return result1 | result2;
+}