aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Eggert2020-03-07 12:04:05 -0800
committerPaul Eggert2020-03-07 12:15:43 -0800
commit5d4cf1fef85bc24bc4cd9705ebb14150263ad707 (patch)
treeaf696ed3ba7d2d0ab31951eba9482443d36c1456
parent9f4b260c2b98ea05a02e0ab7213156ce2e60e5a9 (diff)
downloademacs-5d4cf1fef85bc24bc4cd9705ebb14150263ad707.tar.gz
emacs-5d4cf1fef85bc24bc4cd9705ebb14150263ad707.zip
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).
-rwxr-xr-xadmin/merge-gnulib4
-rw-r--r--doc/lispref/files.texi10
-rw-r--r--etc/NEWS4
-rw-r--r--lib/futimens.c37
-rw-r--r--lib/gnulib.mk.in28
-rw-r--r--lib/utimensat.c160
-rw-r--r--lisp/files.el7
-rw-r--r--lisp/gnus/gnus-cloud.el4
-rw-r--r--lisp/net/tramp-adb.el6
-rw-r--r--lisp/net/tramp-gvfs.el4
-rw-r--r--lisp/net/tramp-sh.el3
-rw-r--r--lisp/net/tramp-smb.el3
-rw-r--r--lisp/net/tramp-sudoedit.el3
-rw-r--r--lisp/tar-mode.el2
-rw-r--r--m4/futimens.m465
-rw-r--r--m4/gnulib-comp.m438
-rw-r--r--m4/utimensat.m469
-rw-r--r--src/fileio.c51
-rw-r--r--src/sysdep.c15
-rw-r--r--src/systime.h3
-rw-r--r--src/w32.c15
-rw-r--r--test/lisp/filenotify-tests.el8
-rw-r--r--test/lisp/files-tests.el4
-rw-r--r--test/lisp/net/tramp-tests.el3
24 files changed, 476 insertions, 70 deletions
diff --git a/admin/merge-gnulib b/admin/merge-gnulib
index 557119441e4..768e5051f0b 100755
--- a/admin/merge-gnulib
+++ b/admin/merge-gnulib
@@ -34,7 +34,7 @@ GNULIB_MODULES='
34 d-type diffseq dosname double-slash-root dtoastr dtotimespec dup2 34 d-type diffseq dosname double-slash-root dtoastr dtotimespec dup2
35 environ execinfo explicit_bzero faccessat 35 environ execinfo explicit_bzero faccessat
36 fchmodat fcntl fcntl-h fdopendir 36 fchmodat fcntl fcntl-h fdopendir
37 filemode filevercmp flexmember fpieee fstatat fsusage fsync 37 filemode filevercmp flexmember fpieee fstatat fsusage fsync futimens
38 getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog 38 getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog
39 ieee754-h ignore-value intprops largefile lstat 39 ieee754-h ignore-value intprops largefile lstat
40 manywarnings memmem-simple mempcpy memrchr minmax mkostemp mktime nstrftime 40 manywarnings memmem-simple mempcpy memrchr minmax mkostemp mktime nstrftime
@@ -43,7 +43,7 @@ GNULIB_MODULES='
43 sig2str socklen stat-time std-gnu11 stdalign stddef stdio 43 sig2str socklen stat-time std-gnu11 stdalign stddef stdio
44 stpcpy strnlen strtoimax symlink sys_stat sys_time 44 stpcpy strnlen strtoimax symlink sys_stat sys_time
45 tempname time time_r time_rz timegm timer-time timespec-add timespec-sub 45 tempname time time_r time_rz timegm timer-time timespec-add timespec-sub
46 update-copyright unlocked-io utimens 46 update-copyright unlocked-io utimensat
47 vla warnings 47 vla warnings
48' 48'
49 49
diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi
index a69a4e5dd38..b3ad9b99649 100644
--- a/doc/lispref/files.texi
+++ b/doc/lispref/files.texi
@@ -1909,11 +1909,19 @@ omitted or @code{nil}, it defaults to 0, i.e., no access rights at
1909all. 1909all.
1910@end defun 1910@end defun
1911 1911
1912@defun set-file-times filename &optional time 1912@defun set-file-times filename &optional time flag
1913This function sets the access and modification times of @var{filename} 1913This function sets the access and modification times of @var{filename}
1914to @var{time}. The return value is @code{t} if the times are successfully 1914to @var{time}. The return value is @code{t} if the times are successfully
1915set, otherwise it is @code{nil}. @var{time} defaults to the current 1915set, otherwise it is @code{nil}. @var{time} defaults to the current
1916time and must be a time value (@pxref{Time of Day}). 1916time and must be a time value (@pxref{Time of Day}).
1917
1918By default this function follows symbolic links. However, if the
1919optional argument @var{flag} is the symbol @code{nofollow}, this
1920function does not follow @var{filename} if it is a symbolic link;
1921this can help prevent inadvertently changing the times of a file
1922somewhere else. On platforms that do not support changing times
1923on a symbolic link, this function signals an error when @var{filename}
1924is a symbolic link and @var{flag} is @code{nofollow}.
1917@end defun 1925@end defun
1918 1926
1919@defun set-file-extended-attributes filename attribute-alist 1927@defun set-file-extended-attributes filename attribute-alist
diff --git a/etc/NEWS b/etc/NEWS
index fcdf6dbe249..47b87afbc60 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -225,8 +225,8 @@ called when the function object is garbage-collected. Use
225'set_function_finalizer' to set the finalizer and 225'set_function_finalizer' to set the finalizer and
226'get_function_finalizer' to retrieve it. 226'get_function_finalizer' to retrieve it.
227 227
228** 'file-modes' and 'set-file-modes' now have an optional argument 228** 'file-modes', 'set-file-modes', and 'set-file-times' now have an
229specifying whether to follow symbolic links. 229optional argument specifying whether to follow symbolic links.
230 230
231** 'parse-time-string' can now parse ISO 8601 format strings, 231** 'parse-time-string' can now parse ISO 8601 format strings,
232such as "2020-01-15T16:12:21-08:00". 232such as "2020-01-15T16:12:21-08:00".
diff --git a/lib/futimens.c b/lib/futimens.c
new file mode 100644
index 00000000000..83fb27cb6aa
--- /dev/null
+++ b/lib/futimens.c
@@ -0,0 +1,37 @@
1/* Set the access and modification time of an open fd.
2 Copyright (C) 2009-2020 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17/* written by Eric Blake */
18
19#include <config.h>
20
21#include <sys/stat.h>
22
23#include "utimens.h"
24
25/* Set the access and modification timestamps of FD to be
26 TIMESPEC[0] and TIMESPEC[1], respectively.
27 Fail with ENOSYS on systems without futimes (or equivalent).
28 If TIMESPEC is null, set the timestamps to the current time.
29 Return 0 on success, -1 (setting errno) on failure. */
30int
31futimens (int fd, struct timespec const times[2])
32{
33 /* fdutimens also works around bugs in native futimens, when running
34 with glibc compiled against newer headers but on a Linux kernel
35 older than 2.6.32. */
36 return fdutimens (fd, NULL, times);
37}
diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in
index d4dc6a3df33..e90d2e39049 100644
--- a/lib/gnulib.mk.in
+++ b/lib/gnulib.mk.in
@@ -106,6 +106,7 @@
106# fstatat \ 106# fstatat \
107# fsusage \ 107# fsusage \
108# fsync \ 108# fsync \
109# futimens \
109# getloadavg \ 110# getloadavg \
110# getopt-gnu \ 111# getopt-gnu \
111# gettime \ 112# gettime \
@@ -155,7 +156,7 @@
155# timespec-sub \ 156# timespec-sub \
156# unlocked-io \ 157# unlocked-io \
157# update-copyright \ 158# update-copyright \
158# utimens \ 159# utimensat \
159# vla \ 160# vla \
160# warnings 161# warnings
161 162
@@ -1087,6 +1088,7 @@ gl_GNULIB_ENABLED_lchmod = @gl_GNULIB_ENABLED_lchmod@
1087gl_GNULIB_ENABLED_malloca = @gl_GNULIB_ENABLED_malloca@ 1088gl_GNULIB_ENABLED_malloca = @gl_GNULIB_ENABLED_malloca@
1088gl_GNULIB_ENABLED_open = @gl_GNULIB_ENABLED_open@ 1089gl_GNULIB_ENABLED_open = @gl_GNULIB_ENABLED_open@
1089gl_GNULIB_ENABLED_strtoll = @gl_GNULIB_ENABLED_strtoll@ 1090gl_GNULIB_ENABLED_strtoll = @gl_GNULIB_ENABLED_strtoll@
1091gl_GNULIB_ENABLED_utimens = @gl_GNULIB_ENABLED_utimens@
1090gl_LIBOBJS = @gl_LIBOBJS@ 1092gl_LIBOBJS = @gl_LIBOBJS@
1091gl_LTLIBOBJS = @gl_LTLIBOBJS@ 1093gl_LTLIBOBJS = @gl_LTLIBOBJS@
1092gltests_LIBOBJS = @gltests_LIBOBJS@ 1094gltests_LIBOBJS = @gltests_LIBOBJS@
@@ -1733,6 +1735,17 @@ EXTRA_libgnu_a_SOURCES += fsync.c
1733endif 1735endif
1734## end gnulib module fsync 1736## end gnulib module fsync
1735 1737
1738## begin gnulib module futimens
1739ifeq (,$(OMIT_GNULIB_MODULE_futimens))
1740
1741
1742EXTRA_DIST += futimens.c
1743
1744EXTRA_libgnu_a_SOURCES += futimens.c
1745
1746endif
1747## end gnulib module futimens
1748
1736## begin gnulib module getdtablesize 1749## begin gnulib module getdtablesize
1737ifeq (,$(OMIT_GNULIB_MODULE_getdtablesize)) 1750ifeq (,$(OMIT_GNULIB_MODULE_getdtablesize))
1738 1751
@@ -3375,13 +3388,26 @@ endif
3375## begin gnulib module utimens 3388## begin gnulib module utimens
3376ifeq (,$(OMIT_GNULIB_MODULE_utimens)) 3389ifeq (,$(OMIT_GNULIB_MODULE_utimens))
3377 3390
3391ifneq (,$(gl_GNULIB_ENABLED_utimens))
3378libgnu_a_SOURCES += utimens.c 3392libgnu_a_SOURCES += utimens.c
3379 3393
3394endif
3380EXTRA_DIST += utimens.h 3395EXTRA_DIST += utimens.h
3381 3396
3382endif 3397endif
3383## end gnulib module utimens 3398## end gnulib module utimens
3384 3399
3400## begin gnulib module utimensat
3401ifeq (,$(OMIT_GNULIB_MODULE_utimensat))
3402
3403
3404EXTRA_DIST += at-func.c utimensat.c
3405
3406EXTRA_libgnu_a_SOURCES += at-func.c utimensat.c
3407
3408endif
3409## end gnulib module utimensat
3410
3385## begin gnulib module verify 3411## begin gnulib module verify
3386ifeq (,$(OMIT_GNULIB_MODULE_verify)) 3412ifeq (,$(OMIT_GNULIB_MODULE_verify))
3387 3413
diff --git a/lib/utimensat.c b/lib/utimensat.c
new file mode 100644
index 00000000000..63788d56480
--- /dev/null
+++ b/lib/utimensat.c
@@ -0,0 +1,160 @@
1/* Set the access and modification time of a file relative to directory fd.
2 Copyright (C) 2009-2020 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17/* written by Eric Blake */
18
19#include <config.h>
20
21/* Specification. */
22#include <sys/stat.h>
23
24#include <errno.h>
25#include <fcntl.h>
26#include <stdlib.h>
27
28#include "stat-time.h"
29#include "timespec.h"
30#include "utimens.h"
31
32#if HAVE_UTIMENSAT
33
34# undef utimensat
35
36/* If we have a native utimensat, but are compiling this file, then
37 utimensat was defined to rpl_utimensat by our replacement
38 sys/stat.h. We assume the native version might fail with ENOSYS,
39 or succeed without properly affecting ctime (as is the case when
40 using newer glibc but older Linux kernel). In this scenario,
41 rpl_utimensat checks whether the native version is usable, and
42 local_utimensat provides the fallback manipulation. */
43
44static int local_utimensat (int, char const *, struct timespec const[2], int);
45# define AT_FUNC_NAME local_utimensat
46
47/* Like utimensat, but work around native bugs. */
48
49int
50rpl_utimensat (int fd, char const *file, struct timespec const times[2],
51 int flag)
52{
53# if defined __linux__ || defined __sun
54 struct timespec ts[2];
55# endif
56
57 /* See comments in utimens.c for details. */
58 static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no. */
59 if (0 <= utimensat_works_really)
60 {
61 int result;
62# if defined __linux__ || defined __sun
63 struct stat st;
64 /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
65 systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
66 but work if both times are either explicitly specified or
67 UTIME_NOW. Work around it with a preparatory [l]stat prior
68 to calling utimensat; fortunately, there is not much timing
69 impact due to the extra syscall even on file systems where
70 UTIME_OMIT would have worked.
71
72 The same bug occurs in Solaris 11.1 (Apr 2013).
73
74 FIXME: Simplify this in 2024, when these file system bugs are
75 no longer common on Gnulib target platforms. */
76 if (times && (times[0].tv_nsec == UTIME_OMIT
77 || times[1].tv_nsec == UTIME_OMIT))
78 {
79 if (fstatat (fd, file, &st, flag))
80 return -1;
81 if (times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT)
82 return 0;
83 if (times[0].tv_nsec == UTIME_OMIT)
84 ts[0] = get_stat_atime (&st);
85 else
86 ts[0] = times[0];
87 if (times[1].tv_nsec == UTIME_OMIT)
88 ts[1] = get_stat_mtime (&st);
89 else
90 ts[1] = times[1];
91 times = ts;
92 }
93# ifdef __hppa__
94 /* Linux kernel 2.6.22.19 on hppa does not reject invalid tv_nsec
95 values. */
96 else if (times
97 && ((times[0].tv_nsec != UTIME_NOW
98 && ! (0 <= times[0].tv_nsec
99 && times[0].tv_nsec < TIMESPEC_HZ))
100 || (times[1].tv_nsec != UTIME_NOW
101 && ! (0 <= times[1].tv_nsec
102 && times[1].tv_nsec < TIMESPEC_HZ))))
103 {
104 errno = EINVAL;
105 return -1;
106 }
107# endif
108# endif
109 result = utimensat (fd, file, times, flag);
110 /* Linux kernel 2.6.25 has a bug where it returns EINVAL for
111 UTIME_NOW or UTIME_OMIT with non-zero tv_sec, which
112 local_utimensat works around. Meanwhile, EINVAL for a bad
113 flag is indeterminate whether the native utimensat works, but
114 local_utimensat will also reject it. */
115 if (result == -1 && errno == EINVAL && (flag & ~AT_SYMLINK_NOFOLLOW))
116 return result;
117 if (result == 0 || (errno != ENOSYS && errno != EINVAL))
118 {
119 utimensat_works_really = 1;
120 return result;
121 }
122 }
123 /* No point in trying openat/futimens, since on Linux, futimens is
124 implemented with the same syscall as utimensat. Only avoid the
125 native utimensat due to an ENOSYS failure; an EINVAL error was
126 data-dependent, and the next caller may pass valid data. */
127 if (0 <= utimensat_works_really && errno == ENOSYS)
128 utimensat_works_really = -1;
129 return local_utimensat (fd, file, times, flag);
130}
131
132#else /* !HAVE_UTIMENSAT */
133
134# define AT_FUNC_NAME utimensat
135
136#endif /* !HAVE_UTIMENSAT */
137
138/* Set the access and modification timestamps of FILE to be
139 TIMESPEC[0] and TIMESPEC[1], respectively; relative to directory
140 FD. If flag is AT_SYMLINK_NOFOLLOW, change the times of a symlink,
141 or fail with ENOSYS if not possible. If TIMESPEC is null, set the
142 timestamps to the current time. If possible, do it without
143 changing the working directory. Otherwise, resort to using
144 save_cwd/fchdir, then utimens/restore_cwd. If either the save_cwd
145 or the restore_cwd fails, then give a diagnostic and exit nonzero.
146 Return 0 on success, -1 (setting errno) on failure. */
147
148/* AT_FUNC_NAME is now utimensat or local_utimensat. */
149#define AT_FUNC_F1 lutimens
150#define AT_FUNC_F2 utimens
151#define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
152#define AT_FUNC_POST_FILE_PARAM_DECLS , struct timespec const ts[2], int flag
153#define AT_FUNC_POST_FILE_ARGS , ts
154#include "at-func.c"
155#undef AT_FUNC_NAME
156#undef AT_FUNC_F1
157#undef AT_FUNC_F2
158#undef AT_FUNC_USE_F1_COND
159#undef AT_FUNC_POST_FILE_PARAM_DECLS
160#undef AT_FUNC_POST_FILE_ARGS
diff --git a/lisp/files.el b/lisp/files.el
index 2e7694d7677..8ce0187f5b7 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -5944,9 +5944,10 @@ into NEWNAME instead."
5944 ;; Set directory attributes. 5944 ;; Set directory attributes.
5945 (let ((modes (file-modes directory)) 5945 (let ((modes (file-modes directory))
5946 (times (and keep-time (file-attribute-modification-time 5946 (times (and keep-time (file-attribute-modification-time
5947 (file-attributes directory))))) 5947 (file-attributes directory))))
5948 (if modes (set-file-modes newname modes (unless follow 'nofollow))) 5948 (follow-flag (unless follow 'nofollow)))
5949 (if times (set-file-times newname times)))))) 5949 (if modes (set-file-modes newname modes follow-flag))
5950 (if times (set-file-times newname times follow-flag))))))
5950 5951
5951 5952
5952;; At time of writing, only info uses this. 5953;; At time of writing, only info uses this.
diff --git a/lisp/gnus/gnus-cloud.el b/lisp/gnus/gnus-cloud.el
index 4d8764bacca..da6231d7330 100644
--- a/lisp/gnus/gnus-cloud.el
+++ b/lisp/gnus/gnus-cloud.el
@@ -285,8 +285,8 @@ Use old data if FORCE-OLDER is not nil."
285 (insert new-contents) 285 (insert new-contents)
286 (when (file-exists-p file-name) 286 (when (file-exists-p file-name)
287 (rename-file file-name (car (find-backup-file-name file-name)))) 287 (rename-file file-name (car (find-backup-file-name file-name))))
288 (write-region (point-min) (point-max) file-name) 288 (write-region (point-min) (point-max) file-name nil nil nil 'excl)
289 (set-file-times file-name (parse-iso8601-time-string date)))) 289 (set-file-times file-name (parse-iso8601-time-string date) 'nofollow)))
290 290
291(defun gnus-cloud-file-covered-p (file-name) 291(defun gnus-cloud-file-covered-p (file-name)
292 (let ((matched nil)) 292 (let ((matched nil))
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index 2c9674fa36f..7ee740f93cb 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -674,8 +674,9 @@ But handle the case, if the \"test\" command is not available."
674 (tramp-adb-send-command-and-check 674 (tramp-adb-send-command-and-check
675 v (format "chmod %o %s" mode localname))))) 675 v (format "chmod %o %s" mode localname)))))
676 676
677(defun tramp-adb-handle-set-file-times (filename &optional time) 677(defun tramp-adb-handle-set-file-times (filename &optional time flag)
678 "Like `set-file-times' for Tramp files." 678 "Like `set-file-times' for Tramp files."
679 flag ;; FIXME: Support 'nofollow'.
679 (with-parsed-tramp-file-name filename nil 680 (with-parsed-tramp-file-name filename nil
680 (tramp-flush-file-properties v localname) 681 (tramp-flush-file-properties v localname)
681 (let ((time (if (or (null time) 682 (let ((time (if (or (null time)
@@ -777,7 +778,8 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
777 (set-file-times 778 (set-file-times
778 newname 779 newname
779 (tramp-compat-file-attribute-modification-time 780 (tramp-compat-file-attribute-modification-time
780 (file-attributes filename)))))) 781 (file-attributes filename))
782 (unless ok-if-already-exists 'nofollow)))))
781 783
782(defun tramp-adb-handle-rename-file 784(defun tramp-adb-handle-rename-file
783 (filename newname &optional ok-if-already-exists) 785 (filename newname &optional ok-if-already-exists)
diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el
index 3ce7bbbd4a3..1ad57c59a5b 100644
--- a/lisp/net/tramp-gvfs.el
+++ b/lisp/net/tramp-gvfs.el
@@ -1571,7 +1571,7 @@ If FILE-SYSTEM is non-nil, return file system attributes."
1571 (tramp-gvfs-url-file-name (tramp-make-tramp-file-name v)) 1571 (tramp-gvfs-url-file-name (tramp-make-tramp-file-name v))
1572 "unix::mode" (number-to-string mode)))) 1572 "unix::mode" (number-to-string mode))))
1573 1573
1574(defun tramp-gvfs-handle-set-file-times (filename &optional time) 1574(defun tramp-gvfs-handle-set-file-times (filename &optional time flag)
1575 "Like `set-file-times' for Tramp files." 1575 "Like `set-file-times' for Tramp files."
1576 (with-parsed-tramp-file-name filename nil 1576 (with-parsed-tramp-file-name filename nil
1577 (tramp-flush-file-properties v localname) 1577 (tramp-flush-file-properties v localname)
@@ -1582,7 +1582,7 @@ If FILE-SYSTEM is non-nil, return file system attributes."
1582 (current-time) 1582 (current-time)
1583 time))) 1583 time)))
1584 (tramp-gvfs-send-command 1584 (tramp-gvfs-send-command
1585 v "gvfs-set-attribute" "-t" "uint64" 1585 v "gvfs-set-attribute" (if flag "-nt" "-t") "uint64"
1586 (tramp-gvfs-url-file-name (tramp-make-tramp-file-name v)) 1586 (tramp-gvfs-url-file-name (tramp-make-tramp-file-name v))
1587 "time::modified" (format-time-string "%s" time))))) 1587 "time::modified" (format-time-string "%s" time)))))
1588 1588
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index 84b8191bd3d..560941c4d5b 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -1495,11 +1495,12 @@ of."
1495 mode (tramp-shell-quote-argument localname)) 1495 mode (tramp-shell-quote-argument localname))
1496 "Error while changing file's mode %s" filename)))) 1496 "Error while changing file's mode %s" filename))))
1497 1497
1498(defun tramp-sh-handle-set-file-times (filename &optional time) 1498(defun tramp-sh-handle-set-file-times (filename &optional time flag)
1499 "Like `set-file-times' for Tramp files." 1499 "Like `set-file-times' for Tramp files."
1500 (with-parsed-tramp-file-name filename nil 1500 (with-parsed-tramp-file-name filename nil
1501 (when (tramp-get-remote-touch v) 1501 (when (tramp-get-remote-touch v)
1502 (tramp-flush-file-properties v localname) 1502 (tramp-flush-file-properties v localname)
1503 flag ;; FIXME: Support 'nofollow'.
1503 (let ((time 1504 (let ((time
1504 (if (or (null time) 1505 (if (or (null time)
1505 (tramp-compat-time-equal-p time tramp-time-doesnt-exist) 1506 (tramp-compat-time-equal-p time tramp-time-doesnt-exist)
diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el
index 42954cbda3d..d91362c879c 100644
--- a/lisp/net/tramp-smb.el
+++ b/lisp/net/tramp-smb.el
@@ -619,7 +619,8 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
619 (set-file-times 619 (set-file-times
620 newname 620 newname
621 (tramp-compat-file-attribute-modification-time 621 (tramp-compat-file-attribute-modification-time
622 (file-attributes filename)))))) 622 (file-attributes filename))
623 (unless ok-if-already-exists 'nofollow)))))
623 624
624(defun tramp-smb-handle-delete-directory (directory &optional recursive _trash) 625(defun tramp-smb-handle-delete-directory (directory &optional recursive _trash)
625 "Like `delete-directory' for Tramp files." 626 "Like `delete-directory' for Tramp files."
diff --git a/lisp/net/tramp-sudoedit.el b/lisp/net/tramp-sudoedit.el
index 7d8c8a90618..c054f405e3d 100644
--- a/lisp/net/tramp-sudoedit.el
+++ b/lisp/net/tramp-sudoedit.el
@@ -523,10 +523,11 @@ the result will be a local, non-Tramp, file name."
523 (string-to-number (match-string 2))) 523 (string-to-number (match-string 2)))
524 (string-to-number (match-string 3))))))))) 524 (string-to-number (match-string 3)))))))))
525 525
526(defun tramp-sudoedit-handle-set-file-times (filename &optional time) 526(defun tramp-sudoedit-handle-set-file-times (filename &optional time flag)
527 "Like `set-file-times' for Tramp files." 527 "Like `set-file-times' for Tramp files."
528 (with-parsed-tramp-file-name filename nil 528 (with-parsed-tramp-file-name filename nil
529 (tramp-flush-file-properties v localname) 529 (tramp-flush-file-properties v localname)
530 flag ;; FIXME: Support 'nofollow'.
530 (let ((time 531 (let ((time
531 (if (or (null time) 532 (if (or (null time)
532 (tramp-compat-time-equal-p time tramp-time-doesnt-exist) 533 (tramp-compat-time-equal-p time tramp-time-doesnt-exist)
diff --git a/lisp/tar-mode.el b/lisp/tar-mode.el
index 97d883eebd9..a3c1715b1e1 100644
--- a/lisp/tar-mode.el
+++ b/lisp/tar-mode.el
@@ -1056,7 +1056,7 @@ extracted file."
1056 (write-region start end to-file nil nil nil t)) 1056 (write-region start end to-file nil nil nil t))
1057 (when (and tar-copy-preserve-time 1057 (when (and tar-copy-preserve-time
1058 date) 1058 date)
1059 (set-file-times to-file date))) 1059 (set-file-times to-file date 'nofollow)))
1060 (message "Copied tar entry %s to %s" name to-file))) 1060 (message "Copied tar entry %s to %s" name to-file)))
1061 1061
1062(defun tar-new-entry (filename &optional index) 1062(defun tar-new-entry (filename &optional index)
diff --git a/m4/futimens.m4 b/m4/futimens.m4
new file mode 100644
index 00000000000..dc5cfa94119
--- /dev/null
+++ b/m4/futimens.m4
@@ -0,0 +1,65 @@
1# serial 8
2# See if we need to provide futimens replacement.
3
4dnl Copyright (C) 2009-2020 Free Software Foundation, Inc.
5dnl This file is free software; the Free Software Foundation
6dnl gives unlimited permission to copy and/or distribute it,
7dnl with or without modifications, as long as this notice is preserved.
8
9# Written by Eric Blake.
10
11AC_DEFUN([gl_FUNC_FUTIMENS],
12[
13 AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
14 AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
15 AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
16 AC_CHECK_FUNCS_ONCE([futimens])
17 if test $ac_cv_func_futimens = no; then
18 HAVE_FUTIMENS=0
19 else
20 AC_CACHE_CHECK([whether futimens works],
21 [gl_cv_func_futimens_works],
22 [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
23#include <fcntl.h>
24#include <sys/stat.h>
25#include <unistd.h>
26#include <errno.h>
27]], [[struct timespec ts[2];
28 int fd = creat ("conftest.file", 0600);
29 struct stat st;
30 if (fd < 0) return 1;
31 ts[0].tv_sec = 1;
32 ts[0].tv_nsec = UTIME_OMIT;
33 ts[1].tv_sec = 1;
34 ts[1].tv_nsec = UTIME_NOW;
35 errno = 0;
36 if (futimens (AT_FDCWD, NULL) == 0) return 2;
37 if (errno != EBADF) return 3;
38 if (futimens (fd, ts)) return 4;
39 sleep (1);
40 ts[0].tv_nsec = UTIME_NOW;
41 ts[1].tv_nsec = UTIME_OMIT;
42 if (futimens (fd, ts)) return 5;
43 if (fstat (fd, &st)) return 6;
44 if (st.st_ctime < st.st_atime) return 7;
45 ]])],
46 [gl_cv_func_futimens_works=yes],
47 [gl_cv_func_futimens_works=no],
48 [case "$host_os" in
49 # Guess no on glibc systems.
50 *-gnu* | gnu*) gl_cv_func_futimens_works="guessing no" ;;
51 # Guess no on musl systems.
52 *-musl*) gl_cv_func_futimens_works="guessing no" ;;
53 # Guess yes otherwise.
54 *) gl_cv_func_futimens_works="guessing yes" ;;
55 esac
56 ])
57 rm -f conftest.file])
58 case "$gl_cv_func_futimens_works" in
59 *yes) ;;
60 *)
61 REPLACE_FUTIMENS=1
62 ;;
63 esac
64 fi
65])
diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4
index 1465ce811b8..3228aa42b57 100644
--- a/m4/gnulib-comp.m4
+++ b/m4/gnulib-comp.m4
@@ -95,6 +95,7 @@ AC_DEFUN([gl_EARLY],
95 # Code from module fstatat: 95 # Code from module fstatat:
96 # Code from module fsusage: 96 # Code from module fsusage:
97 # Code from module fsync: 97 # Code from module fsync:
98 # Code from module futimens:
98 # Code from module getdtablesize: 99 # Code from module getdtablesize:
99 # Code from module getgroups: 100 # Code from module getgroups:
100 # Code from module getloadavg: 101 # Code from module getloadavg:
@@ -179,6 +180,7 @@ AC_DEFUN([gl_EARLY],
179 # Code from module unlocked-io: 180 # Code from module unlocked-io:
180 # Code from module update-copyright: 181 # Code from module update-copyright:
181 # Code from module utimens: 182 # Code from module utimens:
183 # Code from module utimensat:
182 # Code from module vararrays: 184 # Code from module vararrays:
183 # Code from module verify: 185 # Code from module verify:
184 # Code from module vla: 186 # Code from module vla:
@@ -297,6 +299,11 @@ AC_DEFUN([gl_INIT],
297 gl_PREREQ_FSYNC 299 gl_PREREQ_FSYNC
298 fi 300 fi
299 gl_UNISTD_MODULE_INDICATOR([fsync]) 301 gl_UNISTD_MODULE_INDICATOR([fsync])
302 gl_FUNC_FUTIMENS
303 if test $HAVE_FUTIMENS = 0 || test $REPLACE_FUTIMENS = 1; then
304 AC_LIBOBJ([futimens])
305 fi
306 gl_SYS_STAT_MODULE_INDICATOR([futimens])
300 gl_GETLOADAVG 307 gl_GETLOADAVG
301 if test $HAVE_GETLOADAVG = 0; then 308 if test $HAVE_GETLOADAVG = 0; then
302 AC_LIBOBJ([getloadavg]) 309 AC_LIBOBJ([getloadavg])
@@ -466,7 +473,11 @@ AC_DEFUN([gl_INIT],
466 gl_TIMESPEC 473 gl_TIMESPEC
467 gl_UNISTD_H 474 gl_UNISTD_H
468 gl_FUNC_GLIBC_UNLOCKED_IO 475 gl_FUNC_GLIBC_UNLOCKED_IO
469 gl_UTIMENS 476 gl_FUNC_UTIMENSAT
477 if test $HAVE_UTIMENSAT = 0 || test $REPLACE_UTIMENSAT = 1; then
478 AC_LIBOBJ([utimensat])
479 fi
480 gl_SYS_STAT_MODULE_INDICATOR([utimensat])
470 AC_C_VARARRAYS 481 AC_C_VARARRAYS
471 gl_gnulib_enabled_260941c0e5dc67ec9e87d1fb321c300b=false 482 gl_gnulib_enabled_260941c0e5dc67ec9e87d1fb321c300b=false
472 gl_gnulib_enabled_cloexec=false 483 gl_gnulib_enabled_cloexec=false
@@ -485,6 +496,7 @@ AC_DEFUN([gl_INIT],
485 gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7=false 496 gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7=false
486 gl_gnulib_enabled_6099e9737f757db36c47fa9d9f02e88c=false 497 gl_gnulib_enabled_6099e9737f757db36c47fa9d9f02e88c=false
487 gl_gnulib_enabled_strtoll=false 498 gl_gnulib_enabled_strtoll=false
499 gl_gnulib_enabled_utimens=false
488 gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec=false 500 gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec=false
489 func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b () 501 func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b ()
490 { 502 {
@@ -663,6 +675,13 @@ AC_DEFUN([gl_INIT],
663 gl_gnulib_enabled_strtoll=true 675 gl_gnulib_enabled_strtoll=true
664 fi 676 fi
665 } 677 }
678 func_gl_gnulib_m4code_utimens ()
679 {
680 if ! $gl_gnulib_enabled_utimens; then
681 gl_UTIMENS
682 gl_gnulib_enabled_utimens=true
683 fi
684 }
666 func_gl_gnulib_m4code_682e609604ccaac6be382e4ee3a4eaec () 685 func_gl_gnulib_m4code_682e609604ccaac6be382e4ee3a4eaec ()
667 { 686 {
668 if ! $gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec; then 687 if ! $gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec; then
@@ -705,6 +724,9 @@ AC_DEFUN([gl_INIT],
705 if test $HAVE_FSTATAT = 0 || test $REPLACE_FSTATAT = 1; then 724 if test $HAVE_FSTATAT = 0 || test $REPLACE_FSTATAT = 1; then
706 func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 725 func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7
707 fi 726 fi
727 if test $HAVE_FUTIMENS = 0 || test $REPLACE_FUTIMENS = 1; then
728 func_gl_gnulib_m4code_utimens
729 fi
708 if test $REPLACE_GETOPT = 1; then 730 if test $REPLACE_GETOPT = 1; then
709 func_gl_gnulib_m4code_be453cec5eecf5731a274f2de7f2db36 731 func_gl_gnulib_m4code_be453cec5eecf5731a274f2de7f2db36
710 fi 732 fi
@@ -729,6 +751,15 @@ AC_DEFUN([gl_INIT],
729 if test $HAVE_TIMEGM = 0 || test $REPLACE_TIMEGM = 1; then 751 if test $HAVE_TIMEGM = 0 || test $REPLACE_TIMEGM = 1; then
730 func_gl_gnulib_m4code_5264294aa0a5557541b53c8c741f7f31 752 func_gl_gnulib_m4code_5264294aa0a5557541b53c8c741f7f31
731 fi 753 fi
754 if test $HAVE_UTIMENSAT = 0 || test $REPLACE_UTIMENSAT = 1; then
755 func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b
756 fi
757 if test $HAVE_UTIMENSAT = 0 || test $REPLACE_UTIMENSAT = 1; then
758 func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7
759 fi
760 if test $HAVE_UTIMENSAT = 0 || test $REPLACE_UTIMENSAT = 1; then
761 func_gl_gnulib_m4code_utimens
762 fi
732 m4_pattern_allow([^gl_GNULIB_ENABLED_]) 763 m4_pattern_allow([^gl_GNULIB_ENABLED_])
733 AM_CONDITIONAL([gl_GNULIB_ENABLED_260941c0e5dc67ec9e87d1fb321c300b], [$gl_gnulib_enabled_260941c0e5dc67ec9e87d1fb321c300b]) 764 AM_CONDITIONAL([gl_GNULIB_ENABLED_260941c0e5dc67ec9e87d1fb321c300b], [$gl_gnulib_enabled_260941c0e5dc67ec9e87d1fb321c300b])
734 AM_CONDITIONAL([gl_GNULIB_ENABLED_cloexec], [$gl_gnulib_enabled_cloexec]) 765 AM_CONDITIONAL([gl_GNULIB_ENABLED_cloexec], [$gl_gnulib_enabled_cloexec])
@@ -747,6 +778,7 @@ AC_DEFUN([gl_INIT],
747 AM_CONDITIONAL([gl_GNULIB_ENABLED_03e0aaad4cb89ca757653bd367a6ccb7], [$gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7]) 778 AM_CONDITIONAL([gl_GNULIB_ENABLED_03e0aaad4cb89ca757653bd367a6ccb7], [$gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7])
748 AM_CONDITIONAL([gl_GNULIB_ENABLED_6099e9737f757db36c47fa9d9f02e88c], [$gl_gnulib_enabled_6099e9737f757db36c47fa9d9f02e88c]) 779 AM_CONDITIONAL([gl_GNULIB_ENABLED_6099e9737f757db36c47fa9d9f02e88c], [$gl_gnulib_enabled_6099e9737f757db36c47fa9d9f02e88c])
749 AM_CONDITIONAL([gl_GNULIB_ENABLED_strtoll], [$gl_gnulib_enabled_strtoll]) 780 AM_CONDITIONAL([gl_GNULIB_ENABLED_strtoll], [$gl_gnulib_enabled_strtoll])
781 AM_CONDITIONAL([gl_GNULIB_ENABLED_utimens], [$gl_gnulib_enabled_utimens])
750 AM_CONDITIONAL([gl_GNULIB_ENABLED_682e609604ccaac6be382e4ee3a4eaec], [$gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec]) 782 AM_CONDITIONAL([gl_GNULIB_ENABLED_682e609604ccaac6be382e4ee3a4eaec], [$gl_gnulib_enabled_682e609604ccaac6be382e4ee3a4eaec])
751 # End of code from modules 783 # End of code from modules
752 m4_ifval(gl_LIBSOURCES_LIST, [ 784 m4_ifval(gl_LIBSOURCES_LIST, [
@@ -956,6 +988,7 @@ AC_DEFUN([gl_FILE_LIST], [
956 lib/fsync.c 988 lib/fsync.c
957 lib/ftoastr.c 989 lib/ftoastr.c
958 lib/ftoastr.h 990 lib/ftoastr.h
991 lib/futimens.c
959 lib/get-permissions.c 992 lib/get-permissions.c
960 lib/getdtablesize.c 993 lib/getdtablesize.c
961 lib/getgroups.c 994 lib/getgroups.c
@@ -1063,6 +1096,7 @@ AC_DEFUN([gl_FILE_LIST], [
1063 lib/unlocked-io.h 1096 lib/unlocked-io.h
1064 lib/utimens.c 1097 lib/utimens.c
1065 lib/utimens.h 1098 lib/utimens.h
1099 lib/utimensat.c
1066 lib/verify.h 1100 lib/verify.h
1067 lib/vla.h 1101 lib/vla.h
1068 lib/warn-on-use.h 1102 lib/warn-on-use.h
@@ -1103,6 +1137,7 @@ AC_DEFUN([gl_FILE_LIST], [
1103 m4/fstatat.m4 1137 m4/fstatat.m4
1104 m4/fsusage.m4 1138 m4/fsusage.m4
1105 m4/fsync.m4 1139 m4/fsync.m4
1140 m4/futimens.m4
1106 m4/getdtablesize.m4 1141 m4/getdtablesize.m4
1107 m4/getgroups.m4 1142 m4/getgroups.m4
1108 m4/getloadavg.m4 1143 m4/getloadavg.m4
@@ -1184,6 +1219,7 @@ AC_DEFUN([gl_FILE_LIST], [
1184 m4/unistd_h.m4 1219 m4/unistd_h.m4
1185 m4/unlocked-io.m4 1220 m4/unlocked-io.m4
1186 m4/utimens.m4 1221 m4/utimens.m4
1222 m4/utimensat.m4
1187 m4/utimes.m4 1223 m4/utimes.m4
1188 m4/vararrays.m4 1224 m4/vararrays.m4
1189 m4/warn-on-use.m4 1225 m4/warn-on-use.m4
diff --git a/m4/utimensat.m4 b/m4/utimensat.m4
new file mode 100644
index 00000000000..2bc1bfebb5d
--- /dev/null
+++ b/m4/utimensat.m4
@@ -0,0 +1,69 @@
1# serial 6
2# See if we need to provide utimensat replacement.
3
4dnl Copyright (C) 2009-2020 Free Software Foundation, Inc.
5dnl This file is free software; the Free Software Foundation
6dnl gives unlimited permission to copy and/or distribute it,
7dnl with or without modifications, as long as this notice is preserved.
8
9# Written by Eric Blake.
10
11AC_DEFUN([gl_FUNC_UTIMENSAT],
12[
13 AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
14 AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
15 AC_CHECK_FUNCS_ONCE([utimensat])
16 if test $ac_cv_func_utimensat = no; then
17 HAVE_UTIMENSAT=0
18 else
19 AC_CACHE_CHECK([whether utimensat works],
20 [gl_cv_func_utimensat_works],
21 [AC_RUN_IFELSE(
22 [AC_LANG_PROGRAM([[
23#include <fcntl.h>
24#include <sys/stat.h>
25#include <unistd.h>
26]], [[int result = 0;
27 const char *f = "conftest.file";
28 if (close (creat (f, 0600)))
29 return 1;
30 /* Test whether the AT_SYMLINK_NOFOLLOW flag is supported. */
31 {
32 if (utimensat (AT_FDCWD, f, NULL, AT_SYMLINK_NOFOLLOW))
33 result |= 2;
34 }
35 /* Test whether UTIME_NOW and UTIME_OMIT work. */
36 {
37 struct timespec ts[2];
38 ts[0].tv_sec = 1;
39 ts[0].tv_nsec = UTIME_OMIT;
40 ts[1].tv_sec = 1;
41 ts[1].tv_nsec = UTIME_NOW;
42 if (utimensat (AT_FDCWD, f, ts, 0))
43 result |= 4;
44 }
45 sleep (1);
46 {
47 struct stat st;
48 struct timespec ts[2];
49 ts[0].tv_sec = 1;
50 ts[0].tv_nsec = UTIME_NOW;
51 ts[1].tv_sec = 1;
52 ts[1].tv_nsec = UTIME_OMIT;
53 if (utimensat (AT_FDCWD, f, ts, 0))
54 result |= 8;
55 if (stat (f, &st))
56 result |= 16;
57 else if (st.st_ctime < st.st_atime)
58 result |= 32;
59 }
60 return result;
61 ]])],
62 [gl_cv_func_utimensat_works=yes],
63 [gl_cv_func_utimensat_works=no],
64 [gl_cv_func_utimensat_works="guessing yes"])])
65 if test "$gl_cv_func_utimensat_works" = no; then
66 REPLACE_UTIMENSAT=1
67 fi
68 fi
69])
diff --git a/src/fileio.c b/src/fileio.c
index 2532f5233c4..82fd7989206 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -2253,9 +2253,8 @@ permissions. */)
2253 2253
2254 if (!NILP (keep_time)) 2254 if (!NILP (keep_time))
2255 { 2255 {
2256 struct timespec atime = get_stat_atime (&st); 2256 struct timespec ts[] = { get_stat_atime (&st), get_stat_mtime (&st) };
2257 struct timespec mtime = get_stat_mtime (&st); 2257 if (futimens (ofd, ts) != 0)
2258 if (set_file_times (ofd, SSDATA (encoded_newname), atime, mtime) != 0)
2259 xsignal2 (Qfile_date_error, 2258 xsignal2 (Qfile_date_error,
2260 build_string ("Cannot set file date"), newname); 2259 build_string ("Cannot set file date"), newname);
2261 } 2260 }
@@ -3430,39 +3429,41 @@ The value is an integer. */)
3430} 3429}
3431 3430
3432 3431
3433DEFUN ("set-file-times", Fset_file_times, Sset_file_times, 1, 2, 0, 3432DEFUN ("set-file-times", Fset_file_times, Sset_file_times, 1, 3, 0,
3434 doc: /* Set times of file FILENAME to TIMESTAMP. 3433 doc: /* Set times of file FILENAME to TIMESTAMP.
3435Set both access and modification times. 3434If optional FLAG is `nofollow', do not follow FILENAME if it is a
3436Return t on success, else nil. 3435symbolic link. Set both access and modification times. Return t on
3437Use the current time if TIMESTAMP is nil. TIMESTAMP is in the format of 3436success, else nil. Use the current time if TIMESTAMP is nil.
3438`current-time'. */) 3437TIMESTAMP is in the format of `current-time'. */)
3439 (Lisp_Object filename, Lisp_Object timestamp) 3438 (Lisp_Object filename, Lisp_Object timestamp, Lisp_Object flag)
3440{ 3439{
3441 Lisp_Object absname, encoded_absname; 3440 int nofollow = symlink_nofollow_flag (flag);
3442 Lisp_Object handler;
3443 struct timespec t = lisp_time_argument (timestamp);
3444 3441
3445 absname = Fexpand_file_name (filename, BVAR (current_buffer, directory)); 3442 struct timespec ts[2];
3443 if (!NILP (timestamp))
3444 ts[0] = ts[1] = lisp_time_argument (timestamp);
3445 else
3446 ts[0].tv_nsec = ts[1].tv_nsec = UTIME_NOW;
3446 3447
3447 /* If the file name has special constructs in it, 3448 /* If the file name has special constructs in it,
3448 call the corresponding file name handler. */ 3449 call the corresponding file name handler. */
3449 handler = Ffind_file_name_handler (absname, Qset_file_times); 3450 Lisp_Object
3451 absname = Fexpand_file_name (filename, BVAR (current_buffer, directory)),
3452 handler = Ffind_file_name_handler (absname, Qset_file_times);
3450 if (!NILP (handler)) 3453 if (!NILP (handler))
3451 return call3 (handler, Qset_file_times, absname, timestamp); 3454 return call4 (handler, Qset_file_times, absname, timestamp, flag);
3452 3455
3453 encoded_absname = ENCODE_FILE (absname); 3456 Lisp_Object encoded_absname = ENCODE_FILE (absname);
3454 3457
3455 { 3458 if (utimensat (AT_FDCWD, SSDATA (encoded_absname), ts, nofollow) != 0)
3456 if (set_file_times (-1, SSDATA (encoded_absname), t, t) != 0) 3459 {
3457 {
3458#ifdef MSDOS 3460#ifdef MSDOS
3459 /* Setting times on a directory always fails. */ 3461 /* Setting times on a directory always fails. */
3460 if (file_directory_p (encoded_absname)) 3462 if (file_directory_p (encoded_absname))
3461 return Qnil; 3463 return Qnil;
3462#endif 3464#endif
3463 report_file_error ("Setting file times", absname); 3465 report_file_error ("Setting file times", absname);
3464 } 3466 }
3465 }
3466 3467
3467 return Qt; 3468 return Qt;
3468} 3469}
diff --git a/src/sysdep.c b/src/sysdep.c
index e8e8bbfb502..149d80f19ec 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -2752,21 +2752,6 @@ emacs_perror (char const *message)
2752 errno = err; 2752 errno = err;
2753} 2753}
2754 2754
2755/* Set the access and modification time stamps of FD (a.k.a. FILE) to be
2756 ATIME and MTIME, respectively.
2757 FD must be either negative -- in which case it is ignored --
2758 or a file descriptor that is open on FILE.
2759 If FD is nonnegative, then FILE can be NULL. */
2760int
2761set_file_times (int fd, const char *filename,
2762 struct timespec atime, struct timespec mtime)
2763{
2764 struct timespec timespec[2];
2765 timespec[0] = atime;
2766 timespec[1] = mtime;
2767 return fdutimens (fd, filename, timespec);
2768}
2769
2770/* Rename directory SRCFD's entry SRC to directory DSTFD's entry DST. 2755/* Rename directory SRCFD's entry SRC to directory DSTFD's entry DST.
2771 This is like renameat except that it fails if DST already exists, 2756 This is like renameat except that it fails if DST already exists,
2772 or if this operation is not supported atomically. Return 0 if 2757 or if this operation is not supported atomically. Return 0 if
diff --git a/src/systime.h b/src/systime.h
index 00ca4a1c58d..b59a3d1c690 100644
--- a/src/systime.h
+++ b/src/systime.h
@@ -67,9 +67,6 @@ timespec_valid_p (struct timespec t)
67 return t.tv_nsec >= 0; 67 return t.tv_nsec >= 0;
68} 68}
69 69
70/* defined in sysdep.c */
71extern int set_file_times (int, const char *, struct timespec, struct timespec);
72
73/* defined in keyboard.c */ 70/* defined in keyboard.c */
74extern void set_waiting_for_input (struct timespec *); 71extern void set_waiting_for_input (struct timespec *);
75 72
diff --git a/src/w32.c b/src/w32.c
index cf1a3b37678..40f286ad6cf 100644
--- a/src/w32.c
+++ b/src/w32.c
@@ -3189,6 +3189,21 @@ fdutimens (int fd, char const *file, struct timespec const timespec[2])
3189 } 3189 }
3190} 3190}
3191 3191
3192/* Set the access and modification time stamps of FD (a.k.a. FILE) to be
3193 ATIME and MTIME, respectively.
3194 FD must be either negative -- in which case it is ignored --
3195 or a file descriptor that is open on FILE.
3196 If FD is nonnegative, then FILE can be NULL. */
3197static int
3198set_file_times (int fd, const char *filename,
3199 struct timespec atime, struct timespec mtime)
3200{
3201 struct timespec timespec[2];
3202 timespec[0] = atime;
3203 timespec[1] = mtime;
3204 return fdutimens (fd, filename, timespec);
3205}
3206
3192 3207
3193/* ------------------------------------------------------------------------- */ 3208/* ------------------------------------------------------------------------- */
3194/* IO support and wrapper functions for the Windows API. */ 3209/* IO support and wrapper functions for the Windows API. */
diff --git a/test/lisp/filenotify-tests.el b/test/lisp/filenotify-tests.el
index 39156fbb5dc..a184fabb9ff 100644
--- a/test/lisp/filenotify-tests.el
+++ b/test/lisp/filenotify-tests.el
@@ -771,9 +771,9 @@ delivered."
771 (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1) 771 (copy-file file-notify--test-tmpfile file-notify--test-tmpfile1)
772 ;; The next two events shall not be visible. 772 ;; The next two events shall not be visible.
773 (file-notify--test-read-event) 773 (file-notify--test-read-event)
774 (set-file-modes file-notify--test-tmpfile 000) 774 (set-file-modes file-notify--test-tmpfile 000 'nofollow)
775 (file-notify--test-read-event) 775 (file-notify--test-read-event)
776 (set-file-times file-notify--test-tmpfile '(0 0)) 776 (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow)
777 (file-notify--test-read-event) 777 (file-notify--test-read-event)
778 (delete-directory file-notify--test-tmpdir 'recursive)) 778 (delete-directory file-notify--test-tmpdir 'recursive))
779 (file-notify-rm-watch file-notify--test-desc) 779 (file-notify-rm-watch file-notify--test-desc)
@@ -864,9 +864,9 @@ delivered."
864 (write-region 864 (write-region
865 "any text" nil file-notify--test-tmpfile nil 'no-message) 865 "any text" nil file-notify--test-tmpfile nil 'no-message)
866 (file-notify--test-read-event) 866 (file-notify--test-read-event)
867 (set-file-modes file-notify--test-tmpfile 000) 867 (set-file-modes file-notify--test-tmpfile 000 'nofollow)
868 (file-notify--test-read-event) 868 (file-notify--test-read-event)
869 (set-file-times file-notify--test-tmpfile '(0 0)) 869 (set-file-times file-notify--test-tmpfile '(0 0) 'nofollow)
870 (file-notify--test-read-event) 870 (file-notify--test-read-event)
871 (delete-file file-notify--test-tmpfile)) 871 (delete-file file-notify--test-tmpfile))
872 (file-notify-rm-watch file-notify--test-desc) 872 (file-notify-rm-watch file-notify--test-desc)
diff --git a/test/lisp/files-tests.el b/test/lisp/files-tests.el
index ac56a7732f2..05d9ceebf1d 100644
--- a/test/lisp/files-tests.el
+++ b/test/lisp/files-tests.el
@@ -1003,9 +1003,9 @@ unquoted file names."
1003 1003
1004(ert-deftest files-tests-file-name-non-special-set-file-times () 1004(ert-deftest files-tests-file-name-non-special-set-file-times ()
1005 (files-tests--with-temp-non-special (tmpfile nospecial) 1005 (files-tests--with-temp-non-special (tmpfile nospecial)
1006 (set-file-times nospecial)) 1006 (set-file-times nospecial nil 'nofollow))
1007 (files-tests--with-temp-non-special-and-file-name-handler (tmpfile nospecial) 1007 (files-tests--with-temp-non-special-and-file-name-handler (tmpfile nospecial)
1008 (should-error (set-file-times nospecial)))) 1008 (should-error (set-file-times nospecial nil 'nofollow))))
1009 1009
1010(ert-deftest files-tests-file-name-non-special-set-visited-file-modtime () 1010(ert-deftest files-tests-file-name-non-special-set-visited-file-modtime ()
1011 (files-tests--with-temp-non-special (tmpfile nospecial) 1011 (files-tests--with-temp-non-special (tmpfile nospecial)
diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el
index be0f418c943..dcf376e70b4 100644
--- a/test/lisp/net/tramp-tests.el
+++ b/test/lisp/net/tramp-tests.el
@@ -3743,7 +3743,8 @@ This tests also `make-symbolic-link', `file-truename' and `add-name-to-file'."
3743 (file-attributes tmp-name1)))) 3743 (file-attributes tmp-name1))))
3744 ;; Skip the test, if the remote handler is not able to set 3744 ;; Skip the test, if the remote handler is not able to set
3745 ;; the correct time. 3745 ;; the correct time.
3746 (skip-unless (set-file-times tmp-name1 (seconds-to-time 1))) 3746 (skip-unless (set-file-times tmp-name1 (seconds-to-time 1)
3747 'nofollow))
3747 ;; Dumb remote shells without perl(1) or stat(1) are not 3748 ;; Dumb remote shells without perl(1) or stat(1) are not
3748 ;; able to return the date correctly. They say "don't know". 3749 ;; able to return the date correctly. They say "don't know".
3749 (unless (tramp-compat-time-equal-p 3750 (unless (tramp-compat-time-equal-p