This is a companion to the recent set-file-modes patch.
It adds support for a ‘nofollow’ flag to set-file-times (Bug#39773).
Like the set-file-modes patch, it needs work in the w32 port.
* admin/merge-gnulib (GNULIB_MODULES): Add futimens, utimensat.
Remove utimens.
* doc/lispref/files.texi (Changing Files):
* etc/NEWS: Mention the change.
* lib/gnulib.mk.in, m4/gnulib-comp.m4: Regenerate.
* lisp/files.el (copy-directory):
* lisp/gnus/gnus-cloud.el (gnus-cloud-replace-file):
* lisp/net/tramp-adb.el (tramp-adb-handle-copy-file):
* lisp/net/tramp-smb.el (tramp-smb-handle-copy-file):
* lisp/tar-mode.el (tar-copy):
* test/lisp/filenotify-tests.el (file-notify-test03-events):
* test/lisp/files-tests.el:
(files-tests-file-name-non-special-set-file-times):
* test/lisp/net/tramp-tests.el (tramp-test22-file-times):
When setting file times, avoid following symbolic links
when the file is not supposed to be a symbolic link.
* lib/futimens.c, lib/utimensat.c, m4/futimens.m4, m4/utimensat.m4:
New files, copied from Gnulib.
* lisp/gnus/gnus-cloud.el (gnus-cloud-replace-file):
When creating a file that is not supposed to exist already,
use the excl flag to check this.
* lisp/net/tramp-adb.el (tramp-adb-handle-set-file-times):
* lisp/net/tramp-sh.el (tramp-sh-handle-set-file-times):
* lisp/net/tramp-sudoedit.el (tramp-sudoedit-handle-set-file-times):
Accept an optional FLAG arg that is currently ignored,
and add a FIXME comment for it.
* lisp/net/tramp-gvfs.el (tramp-gvfs-handle-set-file-times):
* src/fileio.c (Fset_file_times):
Support an optional FLAG arg.
* src/fileio.c (Fcopy_file): Use futimens instead of set_file_times,
as it’s simpler and is a POSIX API.
* src/sysdep.c (set_file_times): Move from here ...
* src/w32.c (set_file_times): ... to here, and make it static,
since it is now used only in w32.c. Presumably w32.c should also
add support for futimens and utimensat (the POSIX APIs, which
Emacs now uses) and it can remove fdutimens (the Gnulib API,
which Emacs no longer uses).
d-type diffseq dosname double-slash-root dtoastr dtotimespec dup2
environ execinfo explicit_bzero faccessat
fchmodat fcntl fcntl-h fdopendir
- filemode filevercmp flexmember fpieee fstatat fsusage fsync
+ filemode filevercmp flexmember fpieee fstatat fsusage fsync futimens
getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog
ieee754-h ignore-value intprops largefile lstat
manywarnings memmem-simple mempcpy memrchr minmax mkostemp mktime nstrftime
sig2str socklen stat-time std-gnu11 stdalign stddef stdio
stpcpy strnlen strtoimax symlink sys_stat sys_time
tempname time time_r time_rz timegm timer-time timespec-add timespec-sub
- update-copyright unlocked-io utimens
+ update-copyright unlocked-io utimensat
vla warnings
'
all.
@end defun
-@defun set-file-times filename &optional time
+@defun set-file-times filename &optional time flag
This function sets the access and modification times of @var{filename}
to @var{time}. The return value is @code{t} if the times are successfully
set, otherwise it is @code{nil}. @var{time} defaults to the current
time and must be a time value (@pxref{Time of Day}).
+
+By default this function follows symbolic links. However, if the
+optional argument @var{flag} is the symbol @code{nofollow}, this
+function does not follow @var{filename} if it is a symbolic link;
+this can help prevent inadvertently changing the times of a file
+somewhere else. On platforms that do not support changing times
+on a symbolic link, this function signals an error when @var{filename}
+is a symbolic link and @var{flag} is @code{nofollow}.
@end defun
@defun set-file-extended-attributes filename attribute-alist
'set_function_finalizer' to set the finalizer and
'get_function_finalizer' to retrieve it.
-** 'file-modes' and 'set-file-modes' now have an optional argument
-specifying whether to follow symbolic links.
+** 'file-modes', 'set-file-modes', and 'set-file-times' now have an
+optional argument specifying whether to follow symbolic links.
** 'parse-time-string' can now parse ISO 8601 format strings,
such as "2020-01-15T16:12:21-08:00".
--- /dev/null
+/* Set the access and modification time of an open fd.
+ Copyright (C) 2009-2020 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 <https://www.gnu.org/licenses/>. */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+#include <sys/stat.h>
+
+#include "utimens.h"
+
+/* Set the access and modification timestamps of FD to be
+ TIMESPEC[0] and TIMESPEC[1], respectively.
+ Fail with ENOSYS on systems without futimes (or equivalent).
+ If TIMESPEC is null, set the timestamps to the current time.
+ Return 0 on success, -1 (setting errno) on failure. */
+int
+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.32. */
+ return fdutimens (fd, NULL, times);
+}
# fstatat \
# fsusage \
# fsync \
+# futimens \
# getloadavg \
# getopt-gnu \
# gettime \
# timespec-sub \
# unlocked-io \
# update-copyright \
-# utimens \
+# utimensat \
# vla \
# warnings
gl_GNULIB_ENABLED_malloca = @gl_GNULIB_ENABLED_malloca@
gl_GNULIB_ENABLED_open = @gl_GNULIB_ENABLED_open@
gl_GNULIB_ENABLED_strtoll = @gl_GNULIB_ENABLED_strtoll@
+gl_GNULIB_ENABLED_utimens = @gl_GNULIB_ENABLED_utimens@
gl_LIBOBJS = @gl_LIBOBJS@
gl_LTLIBOBJS = @gl_LTLIBOBJS@
gltests_LIBOBJS = @gltests_LIBOBJS@
endif
## end gnulib module fsync
+## begin gnulib module futimens
+ifeq (,$(OMIT_GNULIB_MODULE_futimens))
+
+
+EXTRA_DIST += futimens.c
+
+EXTRA_libgnu_a_SOURCES += futimens.c
+
+endif
+## end gnulib module futimens
+
## begin gnulib module getdtablesize
ifeq (,$(OMIT_GNULIB_MODULE_getdtablesize))
## begin gnulib module utimens
ifeq (,$(OMIT_GNULIB_MODULE_utimens))
+ifneq (,$(gl_GNULIB_ENABLED_utimens))
libgnu_a_SOURCES += utimens.c
+endif
EXTRA_DIST += utimens.h
endif
## end gnulib module utimens
+## begin gnulib module utimensat
+ifeq (,$(OMIT_GNULIB_MODULE_utimensat))
+
+
+EXTRA_DIST += at-func.c utimensat.c
+
+EXTRA_libgnu_a_SOURCES += at-func.c utimensat.c
+
+endif
+## end gnulib module utimensat
+
## begin gnulib module verify
ifeq (,$(OMIT_GNULIB_MODULE_verify))
--- /dev/null
+/* Set the access and modification time of a file relative to directory fd.
+ Copyright (C) 2009-2020 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 <https://www.gnu.org/licenses/>. */
+
+/* written by Eric Blake */
+
+#include <config.h>
+
+/* Specification. */
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+
+#include "stat-time.h"
+#include "timespec.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,
+ 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
+
+/* Like utimensat, but work around native bugs. */
+
+int
+rpl_utimensat (int fd, char const *file, struct timespec const times[2],
+ int flag)
+{
+# if defined __linux__ || defined __sun
+ struct timespec ts[2];
+# endif
+
+ /* See comments in utimens.c for details. */
+ static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no. */
+ if (0 <= utimensat_works_really)
+ {
+ int result;
+# if defined __linux__ || defined __sun
+ struct stat st;
+ /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
+ systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
+ but work if both times are either explicitly specified or
+ UTIME_NOW. Work around it with a preparatory [l]stat prior
+ to calling utimensat; fortunately, there is not much timing
+ impact due to the extra syscall even on file systems where
+ UTIME_OMIT would have worked.
+
+ The same bug occurs in Solaris 11.1 (Apr 2013).
+
+ FIXME: Simplify this in 2024, when these file system bugs are
+ no longer common on Gnulib target platforms. */
+ if (times && (times[0].tv_nsec == UTIME_OMIT
+ || times[1].tv_nsec == UTIME_OMIT))
+ {
+ if (fstatat (fd, file, &st, flag))
+ return -1;
+ if (times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT)
+ return 0;
+ if (times[0].tv_nsec == UTIME_OMIT)
+ ts[0] = get_stat_atime (&st);
+ else
+ ts[0] = times[0];
+ if (times[1].tv_nsec == UTIME_OMIT)
+ ts[1] = get_stat_mtime (&st);
+ else
+ ts[1] = times[1];
+ times = ts;
+ }
+# ifdef __hppa__
+ /* Linux kernel 2.6.22.19 on hppa does not reject invalid tv_nsec
+ values. */
+ else if (times
+ && ((times[0].tv_nsec != UTIME_NOW
+ && ! (0 <= times[0].tv_nsec
+ && times[0].tv_nsec < TIMESPEC_HZ))
+ || (times[1].tv_nsec != UTIME_NOW
+ && ! (0 <= times[1].tv_nsec
+ && times[1].tv_nsec < TIMESPEC_HZ))))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+# endif
+# endif
+ 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 timestamps 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
+ timestamps 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
;; Set directory attributes.
(let ((modes (file-modes directory))
(times (and keep-time (file-attribute-modification-time
- (file-attributes directory)))))
- (if modes (set-file-modes newname modes (unless follow 'nofollow)))
- (if times (set-file-times newname times))))))
+ (file-attributes directory))))
+ (follow-flag (unless follow 'nofollow)))
+ (if modes (set-file-modes newname modes follow-flag))
+ (if times (set-file-times newname times follow-flag))))))
\f
;; At time of writing, only info uses this.
(insert new-contents)
(when (file-exists-p file-name)
(rename-file file-name (car (find-backup-file-name file-name))))
- (write-region (point-min) (point-max) file-name)
- (set-file-times file-name (parse-iso8601-time-string date))))
+ (write-region (point-min) (point-max) file-name nil nil nil 'excl)
+ (set-file-times file-name (parse-iso8601-time-string date) 'nofollow)))
(defun gnus-cloud-file-covered-p (file-name)
(let ((matched nil))
(tramp-adb-send-command-and-check
v (format "chmod %o %s" mode localname)))))
-(defun tramp-adb-handle-set-file-times (filename &optional time)
+(defun tramp-adb-handle-set-file-times (filename &optional time flag)
"Like `set-file-times' for Tramp files."
+ flag ;; FIXME: Support 'nofollow'.
(with-parsed-tramp-file-name filename nil
(tramp-flush-file-properties v localname)
(let ((time (if (or (null time)
(set-file-times
newname
(tramp-compat-file-attribute-modification-time
- (file-attributes filename))))))
+ (file-attributes filename))
+ (unless ok-if-already-exists 'nofollow)))))
(defun tramp-adb-handle-rename-file
(filename newname &optional ok-if-already-exists)
(tramp-gvfs-url-file-name (tramp-make-tramp-file-name v))
"unix::mode" (number-to-string mode))))
-(defun tramp-gvfs-handle-set-file-times (filename &optional time)
+(defun tramp-gvfs-handle-set-file-times (filename &optional time flag)
"Like `set-file-times' for Tramp files."
(with-parsed-tramp-file-name filename nil
(tramp-flush-file-properties v localname)
(current-time)
time)))
(tramp-gvfs-send-command
- v "gvfs-set-attribute" "-t" "uint64"
+ v "gvfs-set-attribute" (if flag "-nt" "-t") "uint64"
(tramp-gvfs-url-file-name (tramp-make-tramp-file-name v))
"time::modified" (format-time-string "%s" time)))))
mode (tramp-shell-quote-argument localname))
"Error while changing file's mode %s" filename))))
-(defun tramp-sh-handle-set-file-times (filename &optional time)
+(defun tramp-sh-handle-set-file-times (filename &optional time flag)
"Like `set-file-times' for Tramp files."
(with-parsed-tramp-file-name filename nil
(when (tramp-get-remote-touch v)
(tramp-flush-file-properties v localname)
+ flag ;; FIXME: Support 'nofollow'.
(let ((time
(if (or (null time)
(tramp-compat-time-equal-p time tramp-time-doesnt-exist)
(set-file-times
newname
(tramp-compat-file-attribute-modification-time
- (file-attributes filename))))))
+ (file-attributes filename))
+ (unless ok-if-already-exists 'nofollow)))))
(defun tramp-smb-handle-delete-directory (directory &optional recursive _trash)
"Like `delete-directory' for Tramp files."
(string-to-number (match-string 2)))
(string-to-number (match-string 3)))))))))
-(defun tramp-sudoedit-handle-set-file-times (filename &optional time)
+(defun tramp-sudoedit-handle-set-file-times (filename &optional time flag)
"Like `set-file-times' for Tramp files."
(with-parsed-tramp-file-name filename nil
(tramp-flush-file-properties v localname)
+ flag ;; FIXME: Support 'nofollow'.
(let ((time
(if (or (null time)
(tramp-compat-time-equal-p time tramp-time-doesnt-exist)
(write-region start end to-file nil nil nil t))
(when (and tar-copy-preserve-time
date)
- (set-file-times to-file date)))
+ (set-file-times to-file date 'nofollow)))
(message "Copied tar entry %s to %s" name to-file)))
(defun tar-new-entry (filename &optional index)
--- /dev/null
+# serial 8
+# See if we need to provide futimens replacement.
+
+dnl Copyright (C) 2009-2020 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_FUTIMENS],
+[
+ AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+ AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
+ AC_CHECK_FUNCS_ONCE([futimens])
+ if test $ac_cv_func_futimens = no; then
+ HAVE_FUTIMENS=0
+ else
+ AC_CACHE_CHECK([whether futimens works],
+ [gl_cv_func_futimens_works],
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+]], [[struct timespec ts[2];
+ int fd = creat ("conftest.file", 0600);
+ struct stat st;
+ if (fd < 0) return 1;
+ ts[0].tv_sec = 1;
+ ts[0].tv_nsec = UTIME_OMIT;
+ ts[1].tv_sec = 1;
+ ts[1].tv_nsec = UTIME_NOW;
+ errno = 0;
+ if (futimens (AT_FDCWD, NULL) == 0) return 2;
+ if (errno != EBADF) return 3;
+ if (futimens (fd, ts)) return 4;
+ sleep (1);
+ ts[0].tv_nsec = UTIME_NOW;
+ ts[1].tv_nsec = UTIME_OMIT;
+ if (futimens (fd, ts)) return 5;
+ if (fstat (fd, &st)) return 6;
+ if (st.st_ctime < st.st_atime) return 7;
+ ]])],
+ [gl_cv_func_futimens_works=yes],
+ [gl_cv_func_futimens_works=no],
+ [case "$host_os" in
+ # Guess no on glibc systems.
+ *-gnu* | gnu*) gl_cv_func_futimens_works="guessing no" ;;
+ # Guess no on musl systems.
+ *-musl*) gl_cv_func_futimens_works="guessing no" ;;
+ # Guess yes otherwise.
+ *) gl_cv_func_futimens_works="guessing yes" ;;
+ esac
+ ])
+ rm -f conftest.file])
+ case "$gl_cv_func_futimens_works" in
+ *yes) ;;
+ *)
+ REPLACE_FUTIMENS=1
+ ;;
+ esac
+ fi
+])
# Code from module fstatat:
# Code from module fsusage:
# Code from module fsync:
+ # Code from module futimens:
# Code from module getdtablesize:
# Code from module getgroups:
# Code from module getloadavg:
# Code from module unlocked-io:
# Code from module update-copyright:
# Code from module utimens:
+ # Code from module utimensat:
# Code from module vararrays:
# Code from module verify:
# Code from module vla:
gl_PREREQ_FSYNC
fi
gl_UNISTD_MODULE_INDICATOR([fsync])
+ gl_FUNC_FUTIMENS
+ if test $HAVE_FUTIMENS = 0 || test $REPLACE_FUTIMENS = 1; then
+ AC_LIBOBJ([futimens])
+ fi
+ gl_SYS_STAT_MODULE_INDICATOR([futimens])
gl_GETLOADAVG
if test $HAVE_GETLOADAVG = 0; then
AC_LIBOBJ([getloadavg])
gl_TIMESPEC
gl_UNISTD_H
gl_FUNC_GLIBC_UNLOCKED_IO
- gl_UTIMENS
+ gl_FUNC_UTIMENSAT
+ if test $HAVE_UTIMENSAT = 0 || test $REPLACE_UTIMENSAT = 1; then
+ AC_LIBOBJ([utimensat])
+ fi
+ gl_SYS_STAT_MODULE_INDICATOR([utimensat])
AC_C_VARARRAYS
gl_gnulib_enabled_260941c0e5dc67ec9e87d1fb321c300b=false
gl_gnulib_enabled_cloexec=false
gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7=false
gl_gnulib_enabled_6099e9737f757db36c47fa9d9f02e88c=false
gl_gnulib_enabled_strtoll=false
+ gl_gnulib_enabled_utimens=false
gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec=false
func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b ()
{
gl_gnulib_enabled_strtoll=true
fi
}
+ func_gl_gnulib_m4code_utimens ()
+ {
+ if ! $gl_gnulib_enabled_utimens; then
+ gl_UTIMENS
+ gl_gnulib_enabled_utimens=true
+ fi
+ }
func_gl_gnulib_m4code_682e609604ccaac6be382e4ee3a4eaec ()
{
if ! $gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec; then
if test $HAVE_FSTATAT = 0 || test $REPLACE_FSTATAT = 1; then
func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7
fi
+ if test $HAVE_FUTIMENS = 0 || test $REPLACE_FUTIMENS = 1; then
+ func_gl_gnulib_m4code_utimens
+ fi
if test $REPLACE_GETOPT = 1; then
func_gl_gnulib_m4code_be453cec5eecf5731a274f2de7f2db36
fi
if test $HAVE_TIMEGM = 0 || test $REPLACE_TIMEGM = 1; then
func_gl_gnulib_m4code_5264294aa0a5557541b53c8c741f7f31
fi
+ if test $HAVE_UTIMENSAT = 0 || test $REPLACE_UTIMENSAT = 1; then
+ func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b
+ fi
+ if test $HAVE_UTIMENSAT = 0 || test $REPLACE_UTIMENSAT = 1; then
+ func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7
+ fi
+ if test $HAVE_UTIMENSAT = 0 || test $REPLACE_UTIMENSAT = 1; then
+ func_gl_gnulib_m4code_utimens
+ fi
m4_pattern_allow([^gl_GNULIB_ENABLED_])
AM_CONDITIONAL([gl_GNULIB_ENABLED_260941c0e5dc67ec9e87d1fb321c300b], [$gl_gnulib_enabled_260941c0e5dc67ec9e87d1fb321c300b])
AM_CONDITIONAL([gl_GNULIB_ENABLED_cloexec], [$gl_gnulib_enabled_cloexec])
AM_CONDITIONAL([gl_GNULIB_ENABLED_03e0aaad4cb89ca757653bd367a6ccb7], [$gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7])
AM_CONDITIONAL([gl_GNULIB_ENABLED_6099e9737f757db36c47fa9d9f02e88c], [$gl_gnulib_enabled_6099e9737f757db36c47fa9d9f02e88c])
AM_CONDITIONAL([gl_GNULIB_ENABLED_strtoll], [$gl_gnulib_enabled_strtoll])
+ AM_CONDITIONAL([gl_GNULIB_ENABLED_utimens], [$gl_gnulib_enabled_utimens])
AM_CONDITIONAL([gl_GNULIB_ENABLED_682e609604ccaac6be382e4ee3a4eaec], [$gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec])
# End of code from modules
m4_ifval(gl_LIBSOURCES_LIST, [
lib/fsync.c
lib/ftoastr.c
lib/ftoastr.h
+ lib/futimens.c
lib/get-permissions.c
lib/getdtablesize.c
lib/getgroups.c
lib/unlocked-io.h
lib/utimens.c
lib/utimens.h
+ lib/utimensat.c
lib/verify.h
lib/vla.h
lib/warn-on-use.h
m4/fstatat.m4
m4/fsusage.m4
m4/fsync.m4
+ m4/futimens.m4
m4/getdtablesize.m4
m4/getgroups.m4
m4/getloadavg.m4
m4/unistd_h.m4
m4/unlocked-io.m4
m4/utimens.m4
+ m4/utimensat.m4
m4/utimes.m4
m4/vararrays.m4
m4/warn-on-use.m4
--- /dev/null
+# serial 6
+# See if we need to provide utimensat replacement.
+
+dnl Copyright (C) 2009-2020 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
+ 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>
+#include <unistd.h>
+]], [[int result = 0;
+ const char *f = "conftest.file";
+ if (close (creat (f, 0600)))
+ return 1;
+ /* Test whether the AT_SYMLINK_NOFOLLOW flag is supported. */
+ {
+ if (utimensat (AT_FDCWD, f, NULL, AT_SYMLINK_NOFOLLOW))
+ result |= 2;
+ }
+ /* Test whether UTIME_NOW and UTIME_OMIT work. */
+ {
+ struct timespec ts[2];
+ ts[0].tv_sec = 1;
+ ts[0].tv_nsec = UTIME_OMIT;
+ ts[1].tv_sec = 1;
+ ts[1].tv_nsec = UTIME_NOW;
+ if (utimensat (AT_FDCWD, f, ts, 0))
+ result |= 4;
+ }
+ sleep (1);
+ {
+ struct stat st;
+ struct timespec ts[2];
+ ts[0].tv_sec = 1;
+ ts[0].tv_nsec = UTIME_NOW;
+ ts[1].tv_sec = 1;
+ ts[1].tv_nsec = UTIME_OMIT;
+ if (utimensat (AT_FDCWD, f, ts, 0))
+ result |= 8;
+ if (stat (f, &st))
+ result |= 16;
+ else if (st.st_ctime < st.st_atime)
+ result |= 32;
+ }
+ return result;
+ ]])],
+ [gl_cv_func_utimensat_works=yes],
+ [gl_cv_func_utimensat_works=no],
+ [gl_cv_func_utimensat_works="guessing yes"])])
+ if test "$gl_cv_func_utimensat_works" = no; then
+ REPLACE_UTIMENSAT=1
+ fi
+ fi
+])
if (!NILP (keep_time))
{
- struct timespec atime = get_stat_atime (&st);
- struct timespec mtime = get_stat_mtime (&st);
- if (set_file_times (ofd, SSDATA (encoded_newname), atime, mtime) != 0)
+ struct timespec ts[] = { get_stat_atime (&st), get_stat_mtime (&st) };
+ if (futimens (ofd, ts) != 0)
xsignal2 (Qfile_date_error,
build_string ("Cannot set file date"), newname);
}
}
\f
-DEFUN ("set-file-times", Fset_file_times, Sset_file_times, 1, 2, 0,
+DEFUN ("set-file-times", Fset_file_times, Sset_file_times, 1, 3, 0,
doc: /* Set times of file FILENAME to TIMESTAMP.
-Set both access and modification times.
-Return t on success, else nil.
-Use the current time if TIMESTAMP is nil. TIMESTAMP is in the format of
-`current-time'. */)
- (Lisp_Object filename, Lisp_Object timestamp)
+If optional FLAG is `nofollow', do not follow FILENAME if it is a
+symbolic link. Set both access and modification times. Return t on
+success, else nil. Use the current time if TIMESTAMP is nil.
+TIMESTAMP is in the format of `current-time'. */)
+ (Lisp_Object filename, Lisp_Object timestamp, Lisp_Object flag)
{
- Lisp_Object absname, encoded_absname;
- Lisp_Object handler;
- struct timespec t = lisp_time_argument (timestamp);
+ int nofollow = symlink_nofollow_flag (flag);
- absname = Fexpand_file_name (filename, BVAR (current_buffer, directory));
+ struct timespec ts[2];
+ if (!NILP (timestamp))
+ ts[0] = ts[1] = lisp_time_argument (timestamp);
+ else
+ ts[0].tv_nsec = ts[1].tv_nsec = UTIME_NOW;
/* If the file name has special constructs in it,
call the corresponding file name handler. */
- handler = Ffind_file_name_handler (absname, Qset_file_times);
+ Lisp_Object
+ absname = Fexpand_file_name (filename, BVAR (current_buffer, directory)),
+ handler = Ffind_file_name_handler (absname, Qset_file_times);
if (!NILP (handler))
- return call3 (handler, Qset_file_times, absname, timestamp);
+ return call4 (handler, Qset_file_times, absname, timestamp, flag);
- encoded_absname = ENCODE_FILE (absname);
+ Lisp_Object encoded_absname = ENCODE_FILE (absname);
- {
- if (set_file_times (-1, SSDATA (encoded_absname), t, t) != 0)
- {
+ if (utimensat (AT_FDCWD, SSDATA (encoded_absname), ts, nofollow) != 0)
+ {
#ifdef MSDOS
- /* Setting times on a directory always fails. */
- if (file_directory_p (encoded_absname))
- return Qnil;
+ /* Setting times on a directory always fails. */
+ if (file_directory_p (encoded_absname))
+ return Qnil;
#endif
- report_file_error ("Setting file times", absname);
- }
- }
+ report_file_error ("Setting file times", absname);
+ }
return Qt;
}
errno = err;
}
\f
-/* Set the access and modification time stamps of FD (a.k.a. FILE) to be
- ATIME and MTIME, respectively.
- FD must be either negative -- in which case it is ignored --
- or a file descriptor that is open on FILE.
- If FD is nonnegative, then FILE can be NULL. */
-int
-set_file_times (int fd, const char *filename,
- struct timespec atime, struct timespec mtime)
-{
- struct timespec timespec[2];
- timespec[0] = atime;
- timespec[1] = mtime;
- return fdutimens (fd, filename, timespec);
-}
-
/* Rename directory SRCFD's entry SRC to directory DSTFD's entry DST.
This is like renameat except that it fails if DST already exists,
or if this operation is not supported atomically. Return 0 if
return t.tv_nsec >= 0;
}
-/* defined in sysdep.c */
-extern int set_file_times (int, const char *, struct timespec, struct timespec);
-
/* defined in keyboard.c */
extern void set_waiting_for_input (struct timespec *);
}
}
+/* Set the access and modification time stamps of FD (a.k.a. FILE) to be
+ ATIME and MTIME, respectively.
+ FD must be either negative -- in which case it is ignored --
+ or a file descriptor that is open on FILE.
+ If FD is nonnegative, then FILE can be NULL. */
+static int
+set_file_times (int fd, const char *filename,
+ struct timespec atime, struct timespec mtime)
+{
+ struct timespec timespec[2];
+ timespec[0] = atime;
+ timespec[1] = mtime;
+ return fdutimens (fd, filename, timespec);
+}
+
/* ------------------------------------------------------------------------- */
/* IO support and wrapper functions for the Windows API. */
(copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
;; The next two events shall not be visible.
(file-notify--test-read-event)
- (set-file-modes file-notify--test-tmpfile 000)
+ (set-file-modes file-notify--test-tmpfile 000 'nofollow)
(file-notify--test-read-event)
- (set-file-times file-notify--test-tmpfile '(0 0))
+ (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow)
(file-notify--test-read-event)
(delete-directory file-notify--test-tmpdir 'recursive))
(file-notify-rm-watch file-notify--test-desc)
(write-region
"any text" nil file-notify--test-tmpfile nil 'no-message)
(file-notify--test-read-event)
- (set-file-modes file-notify--test-tmpfile 000)
+ (set-file-modes file-notify--test-tmpfile 000 'nofollow)
(file-notify--test-read-event)
- (set-file-times file-notify--test-tmpfile '(0 0))
+ (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow)
(file-notify--test-read-event)
(delete-file file-notify--test-tmpfile))
(file-notify-rm-watch file-notify--test-desc)
(ert-deftest files-tests-file-name-non-special-set-file-times ()
(files-tests--with-temp-non-special (tmpfile nospecial)
- (set-file-times nospecial))
+ (set-file-times nospecial nil 'nofollow))
(files-tests--with-temp-non-special-and-file-name-handler (tmpfile nospecial)
- (should-error (set-file-times nospecial))))
+ (should-error (set-file-times nospecial nil 'nofollow))))
(ert-deftest files-tests-file-name-non-special-set-visited-file-modtime ()
(files-tests--with-temp-non-special (tmpfile nospecial)
(file-attributes tmp-name1))))
;; Skip the test, if the remote handler is not able to set
;; the correct time.
- (skip-unless (set-file-times tmp-name1 (seconds-to-time 1)))
+ (skip-unless (set-file-times tmp-name1 (seconds-to-time 1)
+ 'nofollow))
;; Dumb remote shells without perl(1) or stat(1) are not
;; able to return the date correctly. They say "don't know".
(unless (tramp-compat-time-equal-p