1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-05 22:20:24 -08:00

Add ‘nofollow’ flag to set-file-times

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).
This commit is contained in:
Paul Eggert 2020-03-07 12:04:05 -08:00
parent 9f4b260c2b
commit 5d4cf1fef8
24 changed files with 476 additions and 70 deletions

View file

@ -34,7 +34,7 @@ GNULIB_MODULES='
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
@ -43,7 +43,7 @@ GNULIB_MODULES='
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
'

View file

@ -1909,11 +1909,19 @@ omitted or @code{nil}, it defaults to 0, i.e., no access rights at
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

View file

@ -225,8 +225,8 @@ called when the function object is garbage-collected. Use
'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".

37
lib/futimens.c Normal file
View file

@ -0,0 +1,37 @@
/* 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);
}

View file

@ -106,6 +106,7 @@
# fstatat \
# fsusage \
# fsync \
# futimens \
# getloadavg \
# getopt-gnu \
# gettime \
@ -155,7 +156,7 @@
# timespec-sub \
# unlocked-io \
# update-copyright \
# utimens \
# utimensat \
# vla \
# warnings
@ -1087,6 +1088,7 @@ gl_GNULIB_ENABLED_lchmod = @gl_GNULIB_ENABLED_lchmod@
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@
@ -1733,6 +1735,17 @@ EXTRA_libgnu_a_SOURCES += fsync.c
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))
@ -3375,13 +3388,26 @@ endif
## 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))

160
lib/utimensat.c Normal file
View file

@ -0,0 +1,160 @@
/* 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

View file

@ -5944,9 +5944,10 @@ into NEWNAME instead."
;; 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))))))
;; At time of writing, only info uses this.

View file

@ -285,8 +285,8 @@ Use old data if FORCE-OLDER is not nil."
(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))

View file

@ -674,8 +674,9 @@ But handle the case, if the \"test\" command is not available."
(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)
@ -777,7 +778,8 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
(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)

View file

@ -1571,7 +1571,7 @@ If FILE-SYSTEM is non-nil, return file system attributes."
(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)
@ -1582,7 +1582,7 @@ If FILE-SYSTEM is non-nil, return file system attributes."
(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)))))

View file

@ -1495,11 +1495,12 @@ of."
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)

View file

@ -619,7 +619,8 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
(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."

View file

@ -523,10 +523,11 @@ the result will be a local, non-Tramp, file name."
(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)

View file

@ -1056,7 +1056,7 @@ extracted file."
(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)

65
m4/futimens.m4 Normal file
View file

@ -0,0 +1,65 @@
# 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
])

View file

@ -95,6 +95,7 @@ AC_DEFUN([gl_EARLY],
# 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:
@ -179,6 +180,7 @@ AC_DEFUN([gl_EARLY],
# 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:
@ -297,6 +299,11 @@ AC_DEFUN([gl_INIT],
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])
@ -466,7 +473,11 @@ AC_DEFUN([gl_INIT],
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
@ -485,6 +496,7 @@ AC_DEFUN([gl_INIT],
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 ()
{
@ -663,6 +675,13 @@ AC_DEFUN([gl_INIT],
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
@ -705,6 +724,9 @@ AC_DEFUN([gl_INIT],
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
@ -729,6 +751,15 @@ AC_DEFUN([gl_INIT],
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])
@ -747,6 +778,7 @@ AC_DEFUN([gl_INIT],
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, [
@ -956,6 +988,7 @@ AC_DEFUN([gl_FILE_LIST], [
lib/fsync.c
lib/ftoastr.c
lib/ftoastr.h
lib/futimens.c
lib/get-permissions.c
lib/getdtablesize.c
lib/getgroups.c
@ -1063,6 +1096,7 @@ AC_DEFUN([gl_FILE_LIST], [
lib/unlocked-io.h
lib/utimens.c
lib/utimens.h
lib/utimensat.c
lib/verify.h
lib/vla.h
lib/warn-on-use.h
@ -1103,6 +1137,7 @@ AC_DEFUN([gl_FILE_LIST], [
m4/fstatat.m4
m4/fsusage.m4
m4/fsync.m4
m4/futimens.m4
m4/getdtablesize.m4
m4/getgroups.m4
m4/getloadavg.m4
@ -1184,6 +1219,7 @@ AC_DEFUN([gl_FILE_LIST], [
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

69
m4/utimensat.m4 Normal file
View file

@ -0,0 +1,69 @@
# 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
])

View file

@ -2253,9 +2253,8 @@ permissions. */)
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);
}
@ -3430,39 +3429,41 @@ The value is an integer. */)
}
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;
}

View file

@ -2752,21 +2752,6 @@ emacs_perror (char const *message)
errno = err;
}
/* 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

View file

@ -67,9 +67,6 @@ timespec_valid_p (struct timespec t)
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 *);

View file

@ -3189,6 +3189,21 @@ fdutimens (int fd, char const *file, struct timespec const timespec[2])
}
}
/* 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. */

View file

@ -771,9 +771,9 @@ delivered."
(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)
@ -864,9 +864,9 @@ delivered."
(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)

View file

@ -1003,9 +1003,9 @@ unquoted file names."
(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)

View file

@ -3743,7 +3743,8 @@ This tests also `make-symbolic-link', `file-truename' and `add-name-to-file'."
(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