]> git.eshelyaron.com Git - emacs.git/commitdiff
Add ‘nofollow’ flag to set-file-times
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 7 Mar 2020 20:04:05 +0000 (12:04 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Sat, 7 Mar 2020 20:15:43 +0000 (12:15 -0800)
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).

24 files changed:
admin/merge-gnulib
doc/lispref/files.texi
etc/NEWS
lib/futimens.c [new file with mode: 0644]
lib/gnulib.mk.in
lib/utimensat.c [new file with mode: 0644]
lisp/files.el
lisp/gnus/gnus-cloud.el
lisp/net/tramp-adb.el
lisp/net/tramp-gvfs.el
lisp/net/tramp-sh.el
lisp/net/tramp-smb.el
lisp/net/tramp-sudoedit.el
lisp/tar-mode.el
m4/futimens.m4 [new file with mode: 0644]
m4/gnulib-comp.m4
m4/utimensat.m4 [new file with mode: 0644]
src/fileio.c
src/sysdep.c
src/systime.h
src/w32.c
test/lisp/filenotify-tests.el
test/lisp/files-tests.el
test/lisp/net/tramp-tests.el

index 557119441e47ffb4ac01bcedc8f8e94bf5199c92..768e5051f0b083e7dbbea0a86fcd766ad537db0e 100755 (executable)
@@ -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
 '
 
index a69a4e5dd38cb11f4b7c3822c0cb0bd67b865ab4..b3ad9b99649eb3798f7a461a287e71f312c96758 100644 (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
index fcdf6dbe249b80b3d043b6be1e8feca7d125b44d..47b87afbc606ae6b1ff0b5f02ca71e5b36872d74 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -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".
diff --git a/lib/futimens.c b/lib/futimens.c
new file mode 100644 (file)
index 0000000..83fb27c
--- /dev/null
@@ -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);
+}
index d4dc6a3df33635e4a0ca81497e1c297ead62eec1..e90d2e39049156c41fd1784437c1534561712216 100644 (file)
 #  fstatat \
 #  fsusage \
 #  fsync \
+#  futimens \
 #  getloadavg \
 #  getopt-gnu \
 #  gettime \
 #  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))
 
diff --git a/lib/utimensat.c b/lib/utimensat.c
new file mode 100644 (file)
index 0000000..63788d5
--- /dev/null
@@ -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
index 2e7694d7677b43b4282b5e57dbc742728ff34c25..8ce0187f5b7982c04f144c6b0bd9562d74b02e28 100644 (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))))))
 
 \f
 ;; At time of writing, only info uses this.
index 4d8764bacca5e9102fa28ad33c879362e2851c00..da6231d73300e2f1d8de60becc5f346a1429f635 100644 (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))
index 2c9674fa36f0242eb301145851ed441502e15271..7ee740f93cb73c138150b4d228bd8cab26a05b10 100644 (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)
index 3ce7bbbd4a3cb9601c998748d4b81c366ff86cff..1ad57c59a5b97704b609ff5846ff9484aaa0fe7d 100644 (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)))))
 
index 84b8191bd3dfc7e402699436c0344265ebd983e6..560941c4d5b2d5faeec1f79e6b358e35cd1e897f 100644 (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)
index 42954cbda3deee99d4f3b92769ce0c49119c9ad4..d91362c879c4c0f4542cd07cfd6fbde270850d42 100644 (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."
index 7d8c8a906188b3df1147ed3715f8cb09fda136f9..c054f405e3dfa73d5395b186261385929b3283bf 100644 (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)
index 97d883eebd9ea12329ad94982ad3c366b12c7d61..a3c1715b1e176bb5f0897d56e29b1c314164cbf6 100644 (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)
diff --git a/m4/futimens.m4 b/m4/futimens.m4
new file mode 100644 (file)
index 0000000..dc5cfa9
--- /dev/null
@@ -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
+])
index 1465ce811b8a65372cc9367bcbfafede379c77a5..3228aa42b5751036f2c8865f5724c842aedc05cf 100644 (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
diff --git a/m4/utimensat.m4 b/m4/utimensat.m4
new file mode 100644 (file)
index 0000000..2bc1bfe
--- /dev/null
@@ -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
+])
index 2532f5233c4e3bf2d0ce6f2459e865b2129363b9..82fd79892060afeadeef61d90135d22138bddcd2 100644 (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.  */)
 }
 \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;
 }
index e8e8bbfb5024d3e8793c1025def1485f7869f955..149d80f19ec5f5fbc750f043202b19e260d998c4 100644 (file)
@@ -2752,21 +2752,6 @@ emacs_perror (char const *message)
   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
index 00ca4a1c58d94f50efd140f6df0883093ff8168a..b59a3d1c690b5c8f82acd70c0bc23f9e5e137feb 100644 (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 *);
 
index cf1a3b37678b552f482e276dbfea530b3f1f84e1..40f286ad6cf1c3adbcfc7f3f57284aec4b992b37 100644 (file)
--- a/src/w32.c
+++ b/src/w32.c
@@ -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. */
index 39156fbb5dc6371d09f739e5bc560b2d76bf5f29..a184fabb9fff061791548aa71cf27faef67fc6e5 100644 (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)
index ac56a7732f2d6b57b95cbc142b769ef9cc212a5f..05d9ceebf1d87294430da417daefe12eb344007d 100644 (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)
index be0f418c943239b21ad7a52fce6fac84876011bf..dcf376e70b43ceccb078f5a42ed9f16fa05a9183 100644 (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