Package: emacs;
Reported by: Paul Eggert <eggert <at> cs.ucla.edu>
Date: Thu, 20 Feb 2020 00:35:01 UTC
Severity: normal
Tags: patch
Done: Paul Eggert <eggert <at> cs.ucla.edu>
Bug is archived. No further changes may be made.
To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 39683 in the body.
You can then email your comments to 39683 AT debbugs.gnu.org in the normal way.
Toggle the display of automated, internal messages from the tracker.
View this report as an mbox folder, status mbox, maintainer mbox
bug-gnu-emacs <at> gnu.org
:bug#39683
; Package emacs
.
(Thu, 20 Feb 2020 00:35:02 GMT) Full text and rfc822 format available.Paul Eggert <eggert <at> cs.ucla.edu>
:bug-gnu-emacs <at> gnu.org
.
(Thu, 20 Feb 2020 00:35:02 GMT) Full text and rfc822 format available.Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):
From: Paul Eggert <eggert <at> cs.ucla.edu> To: bug-gnu-emacs <at> gnu.org Cc: Paul Eggert <eggert <at> cs.ucla.edu> Subject: [PATCH] Add NOFOLLOW flag to set-file-modes etc. Date: Wed, 19 Feb 2020 16:34:00 -0800
This avoids some race conditions. For example, if some other program changes a file to a symlink between the time Emacs creates the file and the time it changes the file’s permissions, using the new flag prevents Emacs from inadvertently changing the permissions of a victim in some completely unrelated directory. * admin/merge-gnulib (GNULIB_MODULES): Add fchmodat. * lib/chmodat.c, lib/fchmodat.c, lib/lchmod.c, m4/fchmodat.m4: * m4/lchmod.m4: New files, copied from Gnulib. * doc/lispref/files.texi (Testing Accessibility, Changing Files): * doc/lispref/os.texi (File Notifications): * etc/NEWS: Adjust documentation accordingly. * lib/gnulib.mk.in: Regenerate. * lisp/dired-aux.el (dired-do-chmod): * lisp/doc-view.el (doc-view-make-safe-dir): * lisp/emacs-lisp/autoload.el (autoload--save-buffer): * lisp/emacs-lisp/bytecomp.el (byte-compile-file): * lisp/eshell/em-pred.el (eshell-pred-file-mode): * lisp/files.el (backup-buffer-copy, copy-directory): * lisp/gnus/mail-source.el (mail-source-movemail): * lisp/gnus/mm-decode.el (mm-display-external): * lisp/gnus/nnmail.el (nnmail-write-region): * lisp/net/tramp-adb.el (tramp-adb-handle-file-local-copy) (tramp-adb-handle-write-region): * lisp/net/tramp-sh.el (tramp-do-copy-or-rename-file-directly): * lisp/net/tramp-sudoedit.el (tramp-sudoedit-handle-write-region): * lisp/net/tramp.el (tramp-handle-write-region) (tramp-make-tramp-temp-file): * lisp/server.el (server-ensure-safe-dir): * lisp/url/url-util.el (url-make-private-file): When getting or setting file modes, avoid following symbolic links when the file is not supposed to be a symbolic link. * lisp/doc-view.el (doc-view-make-safe-dir): Omit no-longer-needed separate symlink test. * lisp/gnus/gnus-util.el (gnus-set-file-modes): * lisp/net/tramp-gvfs.el (tramp-gvfs-handle-set-file-modes): * src/fileio.c (Ffile_modes, Fset_file_modes): Support new optional arg NOFOLLOW. * lisp/net/ange-ftp.el (ange-ftp-set-file-modes): * lisp/net/tramp-adb.el (tramp-adb-handle-set-file-modes): * lisp/net/tramp-sh.el (tramp-sh-handle-set-file-modes): * lisp/net/tramp-smb.el (tramp-smb-handle-set-file-modes): * lisp/net/tramp-sudoedit.el (tramp-sudoedit-handle-set-file-modes): * lisp/net/tramp.el (tramp-handle-file-modes): Accept an optional NOFOLLOW arg that is currently ignored, and add a FIXME comment for it. * m4/gnulib-comp.m4: Regenerate. --- admin/merge-gnulib | 2 +- doc/lispref/files.texi | 26 +++++-- doc/lispref/os.texi | 2 +- etc/NEWS | 3 + lib/chmodat.c | 3 + lib/fchmodat.c | 145 ++++++++++++++++++++++++++++++++++++ lib/gnulib.mk.in | 27 +++++++ lib/lchmod.c | 138 ++++++++++++++++++++++++++++++++++ lisp/dired-aux.el | 3 +- lisp/doc-view.el | 4 +- lisp/emacs-lisp/autoload.el | 2 +- lisp/emacs-lisp/bytecomp.el | 2 +- lisp/eshell/em-pred.el | 2 +- lisp/files.el | 12 ++- lisp/gnus/gnus-util.el | 4 +- lisp/gnus/mail-source.el | 2 +- lisp/gnus/mm-decode.el | 2 +- lisp/gnus/nnmail.el | 2 +- lisp/net/ange-ftp.el | 3 +- lisp/net/tramp-adb.el | 8 +- lisp/net/tramp-gvfs.el | 4 +- lisp/net/tramp-sh.el | 9 ++- lisp/net/tramp-smb.el | 3 +- lisp/net/tramp-sudoedit.el | 5 +- lisp/net/tramp.el | 7 +- lisp/server.el | 2 +- lisp/url/url-util.el | 2 +- m4/fchmodat.m4 | 82 ++++++++++++++++++++ m4/gnulib-comp.m4 | 37 +++++++++ m4/lchmod.m4 | 84 +++++++++++++++++++++ src/fileio.c | 39 +++++----- 31 files changed, 606 insertions(+), 60 deletions(-) create mode 100644 lib/chmodat.c create mode 100644 lib/fchmodat.c create mode 100644 lib/lchmod.c create mode 100644 m4/fchmodat.m4 create mode 100644 m4/lchmod.m4 diff --git a/admin/merge-gnulib b/admin/merge-gnulib index 48c81e61e2..557119441e 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -33,7 +33,7 @@ GNULIB_MODULES= crypto/md5-buffer crypto/sha1-buffer crypto/sha256-buffer crypto/sha512-buffer d-type diffseq dosname double-slash-root dtoastr dtotimespec dup2 environ execinfo explicit_bzero faccessat - fcntl fcntl-h fdopendir + fchmodat fcntl fcntl-h fdopendir filemode filevercmp flexmember fpieee fstatat fsusage fsync getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog ieee754-h ignore-value intprops largefile lstat diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi index a93da39f17..b3fae0b2a5 100644 --- a/doc/lispref/files.texi +++ b/doc/lispref/files.texi @@ -928,7 +928,7 @@ Testing Accessibility This function does not follow symbolic links. @end defun -@defun file-modes filename +@defun file-modes filename nofollow @cindex mode bits @cindex file permissions @cindex permissions, file @@ -946,12 +946,18 @@ Testing Accessibility has read, write, and execute permission, the @acronym{SUID} bit is set for both others and group, and the sticky bit is set. +By default this function follows symbolic links. However, if the +optional argument @var{nofollow} is @code{t}, this function does not +follow @var{filename} if it is a symbolic link; this can help prevent +inadvertently obtaining the mode bits of a file somewhere else, and is +more consistent with @code{file-attributes} (@pxref{File Attributes}). + @xref{Changing Files}, for the @code{set-file-modes} function, which can be used to set these permissions. @example @group -(file-modes "~/junk/diffs") +(file-modes "~/junk/diffs" t) @result{} 492 ; @r{Decimal integer.} @end group @group @@ -960,7 +966,7 @@ Testing Accessibility @end group @group -(set-file-modes "~/junk/diffs" #o666) +(set-file-modes "~/junk/diffs" #o666 t) @result{} nil @end group @@ -1801,9 +1807,17 @@ Changing Files @cindex file permissions, setting @cindex permissions, file @cindex file modes, setting -@deffn Command set-file-modes filename mode +@deffn Command set-file-modes filename mode nofollow This function sets the @dfn{file mode} (or @dfn{permissions}) of -@var{filename} to @var{mode}. This function follows symbolic links. +@var{filename} to @var{mode}. + +By default this function follows symbolic links. However, if the +optional argument @var{nofollow} is @code{t}, this function does not +follow @var{filename} if it is a symbolic link; this can help prevent +inadvertently changing the mode bits of a file somewhere else. On +platforms that do not support changing mode bits on a symbolic link, +this function signals an error when @var{filename} is a symbolic link +and @var{nofollow} is @code{t}. If called non-interactively, @var{mode} must be an integer. Only the lowest 12 bits of the integer are used; on most systems, only the @@ -1811,7 +1825,7 @@ Changing Files octal numbers to enter @var{mode}. For example, @example -(set-file-modes #o644) +(set-file-modes "myfile" #o644 t) @end example @noindent diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index a034ccdcd5..991c50a63b 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -3127,7 +3127,7 @@ File Notifications @end group @group -(set-file-modes "/tmp/foo" (default-file-modes)) +(set-file-modes "/tmp/foo" (default-file-modes) t) @result{} Event (35025468 attribute-changed "/tmp/foo") @end group @end example diff --git a/etc/NEWS b/etc/NEWS index 1a51a90636..6e55bb6e93 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -190,6 +190,9 @@ 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. + ** 'parse-time-string' can now parse ISO 8601 format strings, such as "2020-01-15T16:12:21-08:00". diff --git a/lib/chmodat.c b/lib/chmodat.c new file mode 100644 index 0000000000..3c69689928 --- /dev/null +++ b/lib/chmodat.c @@ -0,0 +1,3 @@ +#include <config.h> +#define FCHMODAT_INLINE _GL_EXTERN_INLINE +#include "openat.h" diff --git a/lib/fchmodat.c b/lib/fchmodat.c new file mode 100644 index 0000000000..bb48b44f53 --- /dev/null +++ b/lib/fchmodat.c @@ -0,0 +1,145 @@ +/* Change the protections of file relative to an open directory. + Copyright (C) 2006, 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 Jim Meyering and Paul Eggert */ + +/* If the user's config.h happens to include <sys/stat.h>, let it include only + the system's <sys/stat.h> here, so that orig_fchmodat doesn't recurse to + rpl_fchmodat. */ +#define __need_system_sys_stat_h +#include <config.h> + +/* Specification. */ +#include <sys/stat.h> +#undef __need_system_sys_stat_h + +#if HAVE_FCHMODAT +static int +orig_fchmodat (int dir, char const *file, mode_t mode, int flags) +{ + return fchmodat (dir, file, mode, flags); +} +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef __osf__ +/* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc + eliminates this include because of the preliminary #include <sys/stat.h> + above. */ +# include "sys/stat.h" +#else +# include <sys/stat.h> +#endif + +#include <intprops.h> + +/* Invoke chmod or lchmod on FILE, using mode MODE, in the directory + open on descriptor FD. If possible, do it without changing the + working directory. Otherwise, resort to using save_cwd/fchdir, + then (chmod|lchmod)/restore_cwd. If either the save_cwd or the + restore_cwd fails, then give a diagnostic and exit nonzero. + Note that an attempt to use a FLAG value of AT_SYMLINK_NOFOLLOW + on a system without lchmod support causes this function to fail. */ + +#if HAVE_FCHMODAT +int +fchmodat (int dir, char const *file, mode_t mode, int flags) +{ +# if NEED_FCHMODAT_NONSYMLINK_FIX + if (flags == AT_SYMLINK_NOFOLLOW) + { + struct stat st; + +# if defined O_PATH && defined AT_EMPTY_PATH \ + && (defined __linux__ || defined __ANDROID__) + /* Open a file descriptor with O_NOFOLLOW, to make sure we don't + follow symbolic links, if /proc is mounted. O_PATH is used to + avoid a failure if the file is not readable. + Cf. <https://sourceware.org/bugzilla/show_bug.cgi?id=14578> */ + int fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC); + if (fd < 0) + return fd; + + /* Up to Linux 5.3 at least, when FILE refers to a symbolic link, the + chmod call below will change the permissions of the symbolic link + - which is undersired - and on many file systems (ext4, btrfs, jfs, + xfs, ..., but not reiserfs) fail with error EOPNOTSUPP - which is + misleading. Therefore test for a symbolic link explicitly. + Use fstatat because fstat does not work on O_PATH descriptors + before Linux 3.6. */ + if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0) + { + int stat_errno = errno; + close (fd); + errno = stat_errno; + return -1; + } + if (S_ISLNK (st.st_mode)) + { + close (fd); + errno = EOPNOTSUPP; + return -1; + } + + static char const fmt[] = "/proc/self/fd/%d"; + char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; + sprintf (buf, fmt, fd); + int chmod_result = chmod (buf, mode); + int chmod_errno = errno; + close (fd); + if (chmod_result == 0) + return chmod_result; + if (chmod_errno != ENOENT) + { + errno = chmod_errno; + return chmod_result; + } + /* /proc is not mounted. */ + /* Fall back on orig_fchmodat, despite the race. */ + return orig_fchmodat (dir, file, mode, 0); +# elif (defined __linux__ || defined __ANDROID__) || !HAVE_LCHMOD + int fstatat_result = fstatat (dir, file, &st, AT_SYMLINK_NOFOLLOW); + if (fstatat_result != 0) + return fstatat_result; + if (S_ISLNK (st.st_mode)) + { + errno = EOPNOTSUPP; + return -1; + } + /* Fall back on orig_fchmodat, despite the race. */ + return orig_fchmodat (dir, file, mode, 0); +# else + return orig_fchmodat (dir, file, mode, 0); +# endif + } +# endif + + return orig_fchmodat (dir, file, mode, flags); +} +#else +# define AT_FUNC_NAME fchmodat +# define AT_FUNC_F1 lchmod +# define AT_FUNC_F2 chmod +# define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW +# define AT_FUNC_POST_FILE_PARAM_DECLS , mode_t mode, int flag +# define AT_FUNC_POST_FILE_ARGS , mode +# include "at-func.c" +#endif diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in index 6775db0001..d6ebf42fc6 100644 --- a/lib/gnulib.mk.in +++ b/lib/gnulib.mk.in @@ -95,6 +95,7 @@ # execinfo \ # explicit_bzero \ # faccessat \ +# fchmodat \ # fcntl \ # fcntl-h \ # fdopendir \ @@ -1083,6 +1084,7 @@ gl_GNULIB_ENABLED_dirfd = @gl_GNULIB_ENABLED_dirfd@ gl_GNULIB_ENABLED_euidaccess = @gl_GNULIB_ENABLED_euidaccess@ gl_GNULIB_ENABLED_getdtablesize = @gl_GNULIB_ENABLED_getdtablesize@ gl_GNULIB_ENABLED_getgroups = @gl_GNULIB_ENABLED_getgroups@ +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@ @@ -1587,6 +1589,18 @@ EXTRA_libgnu_a_SOURCES += at-func.c faccessat.c endif ## end gnulib module faccessat +## begin gnulib module fchmodat +ifeq (,$(OMIT_GNULIB_MODULE_fchmodat)) + +libgnu_a_SOURCES += chmodat.c + +EXTRA_DIST += at-func.c fchmodat.c + +EXTRA_libgnu_a_SOURCES += at-func.c fchmodat.c + +endif +## end gnulib module fchmodat + ## begin gnulib module fcntl ifeq (,$(OMIT_GNULIB_MODULE_fcntl)) @@ -1937,6 +1951,19 @@ EXTRA_DIST += inttypes.in.h endif ## end gnulib module inttypes-incomplete +## begin gnulib module lchmod +ifeq (,$(OMIT_GNULIB_MODULE_lchmod)) + +ifneq (,$(gl_GNULIB_ENABLED_lchmod)) + +endif +EXTRA_DIST += lchmod.c + +EXTRA_libgnu_a_SOURCES += lchmod.c + +endif +## end gnulib module lchmod + ## begin gnulib module libc-config ifeq (,$(OMIT_GNULIB_MODULE_libc-config)) diff --git a/lib/lchmod.c b/lib/lchmod.c new file mode 100644 index 0000000000..57f75da8cf --- /dev/null +++ b/lib/lchmod.c @@ -0,0 +1,138 @@ +/* Implement lchmod on platforms where it does not work correctly. + + Copyright 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 Paul Eggert */ + +#include <config.h> + +/* If the user's config.h happens to include <sys/stat.h>, let it include only + the system's <sys/stat.h> here, so that orig_fchmodat doesn't recurse to + rpl_fchmodat. */ +#define __need_system_sys_stat_h +#include <config.h> + +/* Specification. */ +#include <sys/stat.h> +#undef __need_system_sys_stat_h + +#if HAVE_LCHMOD +static inline int +orig_lchmod (char const *file, mode_t mode) +{ + return lchmod (file, mode); +} +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> + +#ifdef __osf__ +/* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc + eliminates this include because of the preliminary #include <sys/stat.h> + above. */ +# include "sys/stat.h" +#else +# include <sys/stat.h> +#endif + +#include <intprops.h> + +/* Work like chmod, except when FILE is a symbolic link. + In that case, on systems where permissions on symbolic links are unsupported + (such as Linux), set errno to EOPNOTSUPP and return -1. */ + +int +lchmod (char const *file, mode_t mode) +{ +#if HAVE_FCHMODAT + /* Gnulib's fchmodat contains the workaround. No need to duplicate it + here. */ + return fchmodat (AT_FDCWD, file, mode, AT_SYMLINK_NOFOLLOW); +#elif NEED_LCHMOD_NONSYMLINK_FIX +# if defined AT_FDCWD && defined O_PATH && defined AT_EMPTY_PATH \ + && (defined __linux__ || defined __ANDROID__) + /* Open a file descriptor with O_NOFOLLOW, to make sure we don't + follow symbolic links, if /proc is mounted. O_PATH is used to + avoid a failure if the file is not readable. + Cf. <https://sourceware.org/bugzilla/show_bug.cgi?id=14578> */ + int fd = openat (AT_FDCWD, file, O_PATH | O_NOFOLLOW | O_CLOEXEC); + if (fd < 0) + return fd; + + /* Up to Linux 5.3 at least, when FILE refers to a symbolic link, the + chmod call below will change the permissions of the symbolic link + - which is undersired - and on many file systems (ext4, btrfs, jfs, + xfs, ..., but not reiserfs) fail with error EOPNOTSUPP - which is + misleading. Therefore test for a symbolic link explicitly. + Use fstatat because fstat does not work on O_PATH descriptors + before Linux 3.6. */ + struct stat st; + if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0) + { + int stat_errno = errno; + close (fd); + errno = stat_errno; + return -1; + } + if (S_ISLNK (st.st_mode)) + { + close (fd); + errno = EOPNOTSUPP; + return -1; + } + + static char const fmt[] = "/proc/self/fd/%d"; + char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; + sprintf (buf, fmt, fd); + int chmod_result = chmod (buf, mode); + int chmod_errno = errno; + close (fd); + if (chmod_result == 0) + return chmod_result; + if (chmod_errno != ENOENT) + { + errno = chmod_errno; + return chmod_result; + } + /* /proc is not mounted. */ + /* Fall back on chmod, despite the race. */ + return chmod (file, mode); +# elif HAVE_LSTAT +# if (defined __linux__ || defined __ANDROID__) || !HAVE_LCHMOD + struct stat st; + int lstat_result = lstat (file, &st); + if (lstat_result != 0) + return lstat_result; + if (S_ISLNK (st.st_mode)) + { + errno = EOPNOTSUPP; + return -1; + } + /* Fall back on chmod, despite the race. */ + return chmod (file, mode); +# else /* GNU/kFreeBSD, GNU/Hurd, macOS, FreeBSD, NetBSD, HP-UX */ + return orig_lchmod (file, mode); +# endif +# else /* native Windows */ + return chmod (file, mode); +# endif +#else + return orig_lchmod (file, mode); +#endif +} diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el index 0069c1744d..de5e8bb567 100644 --- a/lisp/dired-aux.el +++ b/lisp/dired-aux.el @@ -409,7 +409,8 @@ dired-do-chmod (set-file-modes file (if num-modes num-modes - (file-modes-symbolic-to-number modes (file-modes file))))) + (file-modes-symbolic-to-number modes (file-modes file t))) + t)) (dired-do-redisplay arg))) ;;;###autoload diff --git a/lisp/doc-view.el b/lisp/doc-view.el index 3788d79725..8ab112e092 100644 --- a/lisp/doc-view.el +++ b/lisp/doc-view.el @@ -683,8 +683,6 @@ doc-view-make-safe-dir ;; time-window of loose permissions otherwise. (with-file-modes #o0700 (make-directory dir)) (file-already-exists - (when (file-symlink-p dir) - (error "Danger: %s points to a symbolic link" dir)) ;; In case it was created earlier with looser rights. ;; We could check the mode info returned by file-attributes, but it's ;; a pain to parse and it may not tell you what we want under @@ -694,7 +692,7 @@ doc-view-make-safe-dir ;; sure we have write-access to the directory and that we own it, thus ;; closing a bunch of security holes. (condition-case error - (set-file-modes dir #o0700) + (set-file-modes dir #o0700 t) (file-error (error (format "Unable to use temporary directory %s: %s" diff --git a/lisp/emacs-lisp/autoload.el b/lisp/emacs-lisp/autoload.el index 785e350e0e..adfe8de274 100644 --- a/lisp/emacs-lisp/autoload.el +++ b/lisp/emacs-lisp/autoload.el @@ -895,7 +895,7 @@ autoload--save-buffer (cons (lambda () (ignore-errors (delete-file tempfile))) kill-emacs-hook))) (unless (= temp-modes desired-modes) - (set-file-modes tempfile desired-modes)) + (set-file-modes tempfile desired-modes t)) (write-region (point-min) (point-max) tempfile nil 1) (backup-buffer) (rename-file tempfile buffer-file-name t)) diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index fce5e4aed6..9b792f04fc 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -2008,7 +2008,7 @@ byte-compile-file (delete-file tempfile))) kill-emacs-hook))) (unless (= temp-modes desired-modes) - (set-file-modes tempfile desired-modes)) + (set-file-modes tempfile desired-modes t)) (write-region (point-min) (point-max) tempfile nil 1) ;; This has the intentional side effect that any ;; hard-links to target-file continue to diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el index 04bf3ff899..a7c670933a 100644 --- a/lisp/eshell/em-pred.el +++ b/lisp/eshell/em-pred.el @@ -478,7 +478,7 @@ eshell-pred-file-type (defsubst eshell-pred-file-mode (mode) "Return a test which tests that MODE pertains to the file." `(lambda (file) - (let ((modes (file-modes file))) + (let ((modes (file-modes file t))) (if modes (logand ,mode modes))))) diff --git a/lisp/files.el b/lisp/files.el index 683f4a8ce7..300049c52e 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -4672,6 +4672,7 @@ backup-buffer-copy ;; Create temp files with strict access rights. It's easy to ;; loosen them later, whereas it's impossible to close the ;; time-window of loose permissions otherwise. + (let (nofollow) (with-file-modes ?\700 (when (condition-case nil ;; Try to overwrite old backup first. @@ -4682,6 +4683,7 @@ backup-buffer-copy (when (file-exists-p to-name) (delete-file to-name)) (copy-file from-name to-name nil t t) + (setq nofollow t) nil) (file-already-exists t)) ;; The file was somehow created by someone else between @@ -4694,7 +4696,7 @@ backup-buffer-copy (with-demoted-errors (set-file-extended-attributes to-name extended-attributes))) (and modes - (set-file-modes to-name (logand modes #o1777))))) + (set-file-modes to-name (logand modes #o1777) nofollow))))) (defvar file-name-version-regexp "\\(?:~\\|\\.~[-[:alnum:]:#@^._]+\\(?:~[[:digit:]]+\\)?~\\)" @@ -5900,7 +5902,8 @@ copy-directory ;; If default-directory is a remote directory, make sure we find its ;; copy-directory handler. (let ((handler (or (find-file-name-handler directory 'copy-directory) - (find-file-name-handler newname 'copy-directory)))) + (find-file-name-handler newname 'copy-directory))) + (follow parents)) (if handler (funcall handler 'copy-directory directory newname keep-time parents copy-contents) @@ -5920,7 +5923,8 @@ copy-directory (or parents (not (file-directory-p newname))) (setq newname (concat newname (file-name-nondirectory directory)))) - (make-directory (directory-file-name newname) parents))) + (make-directory (directory-file-name newname) parents)) + (t (setq follow t))) ;; Copy recursively. (dolist (file @@ -5941,7 +5945,7 @@ copy-directory (let ((modes (file-modes directory)) (times (and keep-time (file-attribute-modification-time (file-attributes directory))))) - (if modes (set-file-modes newname modes)) + (if modes (set-file-modes newname modes (not follow))) (if times (set-file-times newname times)))))) diff --git a/lisp/gnus/gnus-util.el b/lisp/gnus/gnus-util.el index eb0fd2522d..e73d0bc110 100644 --- a/lisp/gnus/gnus-util.el +++ b/lisp/gnus/gnus-util.el @@ -1601,10 +1601,10 @@ gnus-rename-file (file-truename (concat old-dir ".."))))))))) -(defun gnus-set-file-modes (filename mode) +(defun gnus-set-file-modes (filename mode &optional nofollow) "Wrapper for set-file-modes." (ignore-errors - (set-file-modes filename mode))) + (set-file-modes filename mode nofollow))) (defun gnus-rescale-image (image size) "Rescale IMAGE to SIZE if possible. diff --git a/lisp/gnus/mail-source.el b/lisp/gnus/mail-source.el index f5b68789b8..1862d38272 100644 --- a/lisp/gnus/mail-source.el +++ b/lisp/gnus/mail-source.el @@ -695,7 +695,7 @@ mail-source-movemail mail-source-movemail-program nil errors nil from to))))) (when (file-exists-p to) - (set-file-modes to mail-source-default-file-modes)) + (set-file-modes to mail-source-default-file-modes t)) (if (and (or (not (buffer-modified-p errors)) (zerop (buffer-size errors))) (and (numberp result) diff --git a/lisp/gnus/mm-decode.el b/lisp/gnus/mm-decode.el index 2dab278b37..d3477d869e 100644 --- a/lisp/gnus/mm-decode.el +++ b/lisp/gnus/mm-decode.el @@ -948,7 +948,7 @@ mm-display-external ;; The file is deleted after the viewer exists. If the users edits ;; the file, changes will be lost. Set file to read-only to make it ;; clear. - (set-file-modes file #o400) + (set-file-modes file #o400 t) (message "Viewing with %s" method) (cond (needsterm diff --git a/lisp/gnus/nnmail.el b/lisp/gnus/nnmail.el index 6e01b5c4d0..f0591c6b5b 100644 --- a/lisp/gnus/nnmail.el +++ b/lisp/gnus/nnmail.el @@ -1958,7 +1958,7 @@ nnmail-write-region (let ((coding-system-for-write nnmail-file-coding-system) (file-name-coding-system nnmail-pathname-coding-system)) (write-region start end filename append visit lockname) - (set-file-modes filename nnmail-default-file-modes))) + (set-file-modes filename nnmail-default-file-modes t))) ;;; ;;; Status functions diff --git a/lisp/net/ange-ftp.el b/lisp/net/ange-ftp.el index f28394260d..3c720fdcdd 100644 --- a/lisp/net/ange-ftp.el +++ b/lisp/net/ange-ftp.el @@ -4740,7 +4740,8 @@ ange-ftp-call-chmod (setq ange-ftp-ls-cache-file nil) ;Stop confusing Dired. 0) -(defun ange-ftp-set-file-modes (filename mode) +(defun ange-ftp-set-file-modes (filename mode &optional nofollow) + nofollow ;; FIXME: Support the NOFOLLOW argument. (ange-ftp-call-chmod (list (format "%o" mode) filename))) (defun ange-ftp-make-symbolic-link (&rest _arguments) diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el index aa7fe147c2..ae81dfd7c8 100644 --- a/lisp/net/tramp-adb.el +++ b/lisp/net/tramp-adb.el @@ -591,7 +591,8 @@ tramp-adb-handle-file-local-copy (ignore-errors (delete-file tmpfile)) (tramp-error v 'file-error "Cannot make local copy of file `%s'" filename)) - (set-file-modes tmpfile (logior (or (file-modes filename) 0) #o0400))) + (set-file-modes tmpfile (logior (or (file-modes filename) 0) #o0400) + t)) tmpfile))) (defun tramp-adb-handle-file-writable-p (filename) @@ -636,7 +637,7 @@ tramp-adb-handle-write-region (tmpfile (tramp-compat-make-temp-file filename))) (when (and append (file-exists-p filename)) (copy-file filename tmpfile 'ok) - (set-file-modes tmpfile (logior (or (file-modes tmpfile) 0) #o0600))) + (set-file-modes tmpfile (logior (or (file-modes tmpfile) 0) #o0600) t)) (tramp-run-real-handler #'write-region (list start end tmpfile append 'no-message lockname)) (with-tramp-progress-reporter @@ -665,8 +666,9 @@ tramp-adb-handle-write-region (tramp-message v 0 "Wrote %s" filename)) (run-hooks 'tramp-handle-write-region-hook)))) -(defun tramp-adb-handle-set-file-modes (filename mode) +(defun tramp-adb-handle-set-file-modes (filename mode &optional nofollow) "Like `set-file-modes' for Tramp files." + nofollow ;; FIXME: Support the NOFOLLOW flag. (with-parsed-tramp-file-name filename nil (tramp-flush-file-properties v localname) (tramp-adb-send-command-and-check v (format "chmod %o %s" mode localname)))) diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el index 762c4fe4b3..3127901a63 100644 --- a/lisp/net/tramp-gvfs.el +++ b/lisp/net/tramp-gvfs.el @@ -1562,12 +1562,12 @@ tramp-gvfs-handle-rename-file (tramp-run-real-handler #'rename-file (list filename newname ok-if-already-exists)))) -(defun tramp-gvfs-handle-set-file-modes (filename mode) +(defun tramp-gvfs-handle-set-file-modes (filename mode &optional nofollow) "Like `set-file-modes' for Tramp files." (with-parsed-tramp-file-name filename nil (tramp-flush-file-properties v localname) (tramp-gvfs-send-command - v "gvfs-set-attribute" "-t" "uint32" + v "gvfs-set-attribute" (if nofollow "-nt" "-t") "uint32" (tramp-gvfs-url-file-name (tramp-make-tramp-file-name v)) "unix::mode" (number-to-string mode)))) diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index 5a3abc31ea..a1dea4478e 100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@ -1478,10 +1478,11 @@ tramp-sh-handle-verify-visited-file-modtime ;; only if that agrees with the buffer's record. (t (tramp-compat-time-equal-p mt tramp-time-doesnt-exist))))))))) -(defun tramp-sh-handle-set-file-modes (filename mode) +(defun tramp-sh-handle-set-file-modes (filename mode &optional nofollow) "Like `set-file-modes' for Tramp files." (with-parsed-tramp-file-name filename nil (tramp-flush-file-properties v localname) + nofollow ;; FIXME: Support the NOFOLLOW flag. ;; FIXME: extract the proper text from chmod's stderr. (tramp-barf-unless-okay v @@ -2279,7 +2280,7 @@ tramp-do-copy-or-rename-file-directly ;; We must change the ownership as local user. ;; Since this does not work reliable, we also ;; give read permissions. - (set-file-modes tmpfile #o0777) + (set-file-modes tmpfile #o0777 t) (tramp-set-file-uid-gid tmpfile (tramp-get-remote-uid v 'integer) @@ -3221,7 +3222,7 @@ tramp-sh-handle-file-local-copy (delete-file tmpfile2))))) ;; Set proper permissions. - (set-file-modes tmpfile (tramp-default-file-modes filename)) + (set-file-modes tmpfile (tramp-default-file-modes filename) t) ;; Set local user ownership. (tramp-set-file-uid-gid tmpfile)) @@ -3320,7 +3321,7 @@ tramp-sh-handle-write-region ;; handles permissions. ;; Ensure that it is still readable. (when modes - (set-file-modes tmpfile (logior (or modes 0) #o0400))) + (set-file-modes tmpfile (logior (or modes 0) #o0400) t)) ;; This is a bit lengthy due to the different methods ;; possible for file transfer. First, we check whether the diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el index f02be394a7..b4b56b13cb 100644 --- a/lisp/net/tramp-smb.el +++ b/lisp/net/tramp-smb.el @@ -1464,8 +1464,9 @@ tramp-smb-handle-set-file-acl (tramp-flush-connection-property v "process-name") (tramp-flush-connection-property v "process-buffer"))))))) -(defun tramp-smb-handle-set-file-modes (filename mode) +(defun tramp-smb-handle-set-file-modes (filename mode &optional nofollow) "Like `set-file-modes' for Tramp files." + nofollow ;; FIXME: Support the NOFOLLOW flag. (with-parsed-tramp-file-name filename nil (when (tramp-smb-get-cifs-capabilities v) (tramp-flush-file-properties v localname) diff --git a/lisp/net/tramp-sudoedit.el b/lisp/net/tramp-sudoedit.el index f258ad6b93..796a4ac84a 100644 --- a/lisp/net/tramp-sudoedit.el +++ b/lisp/net/tramp-sudoedit.el @@ -463,8 +463,9 @@ tramp-sudoedit-handle-file-readable-p (tramp-sudoedit-send-command v "test" "-r" (tramp-compat-file-name-unquote localname))))) -(defun tramp-sudoedit-handle-set-file-modes (filename mode) +(defun tramp-sudoedit-handle-set-file-modes (filename mode &optional nofollow) "Like `set-file-modes' for Tramp files." + nofollow ;; FIXME: Support the NOFOLLOW flag. (with-parsed-tramp-file-name filename nil (tramp-flush-file-properties v localname) (unless (tramp-sudoedit-send-command @@ -735,7 +736,7 @@ tramp-sudoedit-handle-write-region (file-attributes filename 'integer)) gid)) (tramp-set-file-uid-gid filename uid gid)) - (set-file-modes filename modes))))) + (set-file-modes filename modes (eq mustbenew 'excl)))))) ;; Internal functions. diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index 409e1f7499..430811adcd 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -3179,8 +3179,9 @@ tramp-handle-file-local-copy (copy-file filename tmpfile 'ok-if-already-exists 'keep-time) tmpfile))) -(defun tramp-handle-file-modes (filename) +(defun tramp-handle-file-modes (filename &optional nofollow) "Like `file-modes' for Tramp files." + nofollow ;; FIXME: Support the NOFOLLOW flag. (when-let ((attrs (file-attributes (or (file-truename filename) filename)))) (tramp-mode-string-to-int (tramp-compat-file-attribute-modes attrs)))) @@ -3884,7 +3885,7 @@ tramp-handle-write-region ;; renamed to the backup file. This case `save-buffer' ;; handles permissions. ;; Ensure that it is still readable. - (set-file-modes tmpfile (logior (or modes 0) #o0400)) + (set-file-modes tmpfile (logior (or modes 0) #o0400) t) ;; We say `no-message' here because we don't want the visited file ;; modtime data to be clobbered from the temp file. We call ;; `set-visited-file-modtime' ourselves later on. @@ -4664,7 +4665,7 @@ tramp-make-tramp-temp-file (setq result nil) ;; This creates the file by side effect. (set-file-times result) - (set-file-modes result #o0700))) + (set-file-modes result #o0700 t))) ;; Return the local part. (tramp-file-local-name result))) diff --git a/lisp/server.el b/lisp/server.el index e6d8b1783c..1c26c122eb 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -563,7 +563,7 @@ server-ensure-safe-dir (format "it is not owned by you (owner = %s (%d))" (user-full-name uid) uid)) (w32 nil) ; on NTFS? - ((let ((modes (file-modes dir))) + ((let ((modes (file-modes dir t))) (unless (zerop (logand (or modes 0) #o077)) (format "it is accessible by others (%03o)" modes)))) (t nil)))) diff --git a/lisp/url/url-util.el b/lisp/url/url-util.el index 645011a578..125fb0349d 100644 --- a/lisp/url/url-util.el +++ b/lisp/url/url-util.el @@ -617,7 +617,7 @@ url-make-private-file (file-already-exists (if (file-symlink-p file) (error "Danger: `%s' is a symbolic link" file)) - (set-file-modes file #o0600)))) + (set-file-modes file #o0600 t)))) (autoload 'puny-encode-domain "puny") (autoload 'url-domsuf-cookie-allowed-p "url-domsuf") diff --git a/m4/fchmodat.m4 b/m4/fchmodat.m4 new file mode 100644 index 0000000000..e3f2f04816 --- /dev/null +++ b/m4/fchmodat.m4 @@ -0,0 +1,82 @@ +# fchmodat.m4 serial 4 +dnl Copyright (C) 2004-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 Jim Meyering. + +AC_DEFUN([gl_FUNC_FCHMODAT], +[ + AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS]) + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + AC_CHECK_FUNCS_ONCE([fchmodat lchmod]) + if test $ac_cv_func_fchmodat != yes; then + HAVE_FCHMODAT=0 + else + AC_CACHE_CHECK( + [whether fchmodat+AT_SYMLINK_NOFOLLOW works on non-symlinks], + [gl_cv_func_fchmodat_works], + [dnl This test fails on GNU/Linux with glibc 2.31 (but not on + dnl GNU/kFreeBSD nor GNU/Hurd) and Cygwin 2.9. + AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [ + AC_INCLUDES_DEFAULT[ + #include <fcntl.h> + #ifndef S_IRUSR + #define S_IRUSR 0400 + #endif + #ifndef S_IWUSR + #define S_IWUSR 0200 + #endif + #ifndef S_IRWXU + #define S_IRWXU 0700 + #endif + #ifndef S_IRWXG + #define S_IRWXG 0070 + #endif + #ifndef S_IRWXO + #define S_IRWXO 0007 + #endif + ]], + [[ + int permissive = S_IRWXU | S_IRWXG | S_IRWXO; + int desired = S_IRUSR | S_IWUSR; + static char const f[] = "conftest.fchmodat"; + struct stat st; + if (creat (f, permissive) < 0) + return 1; + if (fchmodat (AT_FDCWD, f, desired, AT_SYMLINK_NOFOLLOW) != 0) + return 1; + if (stat (f, &st) != 0) + return 1; + return ! ((st.st_mode & permissive) == desired); + ]])], + [gl_cv_func_fchmodat_works=yes], + [gl_cv_func_fchmodat_works=no], + [case "$host_os" in + dnl Guess no on Linux with glibc and Cygwin, yes otherwise. + linux-gnu* | cygwin*) gl_cv_func_fchmodat_works="guessing no" ;; + *) gl_cv_func_fchmodat_works="$gl_cross_guess_normal" ;; + esac + ]) + rm -f conftest.fchmodat]) + case $gl_cv_func_fchmodat_works in + *yes) ;; + *) + AC_DEFINE([NEED_FCHMODAT_NONSYMLINK_FIX], [1], + [Define to 1 if fchmodat+AT_SYMLINK_NOFOLLOW does not work right on non-symlinks.]) + REPLACE_FCHMODAT=1 + ;; + esac + fi +]) + +# Prerequisites of lib/fchmodat.c. +AC_DEFUN([gl_PREREQ_FCHMODAT], +[ + AC_CHECK_FUNCS_ONCE([lchmod]) + : +]) diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4 index 48d8030f53..4fb5edb145 100644 --- a/m4/gnulib-comp.m4 +++ b/m4/gnulib-comp.m4 @@ -82,6 +82,7 @@ AC_DEFUN # Code from module extensions: # Code from module extern-inline: # Code from module faccessat: + # Code from module fchmodat: # Code from module fcntl: # Code from module fcntl-h: # Code from module fdopendir: @@ -111,6 +112,7 @@ AC_DEFUN # Code from module inttypes-incomplete: # Code from module largefile: AC_REQUIRE([AC_SYS_LARGEFILE]) + # Code from module lchmod: # Code from module libc-config: # Code from module limits-h: # Code from module localtime-buffer: @@ -250,6 +252,13 @@ AC_DEFUN fi gl_MODULE_INDICATOR([faccessat]) gl_UNISTD_MODULE_INDICATOR([faccessat]) + gl_FUNC_FCHMODAT + if test $HAVE_FCHMODAT = 0 || test $REPLACE_FCHMODAT = 1; then + AC_LIBOBJ([fchmodat]) + gl_PREREQ_FCHMODAT + fi + gl_MODULE_INDICATOR([fchmodat]) dnl for lib/openat.h + gl_SYS_STAT_MODULE_INDICATOR([fchmodat]) gl_FUNC_FCNTL if test $HAVE_FCNTL = 0 || test $REPLACE_FCNTL = 1; then AC_LIBOBJ([fcntl]) @@ -463,6 +472,7 @@ AC_DEFUN gl_gnulib_enabled_getgroups=false gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=false gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1=false + gl_gnulib_enabled_lchmod=false gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467=false gl_gnulib_enabled_2049e887c7e5308faad27b3f894bb8c9=false gl_gnulib_enabled_malloca=false @@ -564,6 +574,18 @@ AC_DEFUN fi fi } + func_gl_gnulib_m4code_lchmod () + { + if ! $gl_gnulib_enabled_lchmod; then + gl_FUNC_LCHMOD + if test $HAVE_LCHMOD = 0 || test $REPLACE_LCHMOD = 1; then + AC_LIBOBJ([lchmod]) + gl_PREREQ_LCHMOD + fi + gl_SYS_STAT_MODULE_INDICATOR([lchmod]) + gl_gnulib_enabled_lchmod=true + fi + } func_gl_gnulib_m4code_21ee726a3540c09237a8e70c0baf7467 () { if ! $gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467; then @@ -655,6 +677,15 @@ AC_DEFUN if test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1; then func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 fi + if test $HAVE_FCHMODAT = 0; then + func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b + fi + if test $HAVE_FCHMODAT = 0; then + func_gl_gnulib_m4code_lchmod + fi + if test $HAVE_FCHMODAT = 0; then + func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 + fi if test $HAVE_FCNTL = 0 || test $REPLACE_FCNTL = 1; then func_gl_gnulib_m4code_getdtablesize fi @@ -703,6 +734,7 @@ AC_DEFUN AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getgroups]) AM_CONDITIONAL([gl_GNULIB_ENABLED_be453cec5eecf5731a274f2de7f2db36], [$gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36]) AM_CONDITIONAL([gl_GNULIB_ENABLED_a9786850e999ae65a836a6041e8e5ed1], [$gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1]) + AM_CONDITIONAL([gl_GNULIB_ENABLED_lchmod], [$gl_gnulib_enabled_lchmod]) AM_CONDITIONAL([gl_GNULIB_ENABLED_21ee726a3540c09237a8e70c0baf7467], [$gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467]) AM_CONDITIONAL([gl_GNULIB_ENABLED_2049e887c7e5308faad27b3f894bb8c9], [$gl_gnulib_enabled_2049e887c7e5308faad27b3f894bb8c9]) AM_CONDITIONAL([gl_GNULIB_ENABLED_malloca], [$gl_gnulib_enabled_malloca]) @@ -879,6 +911,7 @@ AC_DEFUN lib/careadlinkat.c lib/careadlinkat.h lib/cdefs.h + lib/chmodat.c lib/cloexec.c lib/cloexec.h lib/close-stream.c @@ -903,6 +936,7 @@ AC_DEFUN lib/execinfo.in.h lib/explicit_bzero.c lib/faccessat.c + lib/fchmodat.c lib/fcntl.c lib/fcntl.in.h lib/fdopendir.c @@ -941,6 +975,7 @@ AC_DEFUN lib/ignore-value.h lib/intprops.h lib/inttypes.in.h + lib/lchmod.c lib/libc-config.h lib/limits.in.h lib/localtime-buffer.c @@ -1053,6 +1088,7 @@ AC_DEFUN m4/extensions.m4 m4/extern-inline.m4 m4/faccessat.m4 + m4/fchmodat.m4 m4/fcntl-o.m4 m4/fcntl.m4 m4/fcntl_h.m4 @@ -1078,6 +1114,7 @@ AC_DEFUN m4/include_next.m4 m4/inttypes.m4 m4/largefile.m4 + m4/lchmod.m4 m4/limits-h.m4 m4/localtime-buffer.m4 m4/lstat.m4 diff --git a/m4/lchmod.m4 b/m4/lchmod.m4 new file mode 100644 index 0000000000..61e3f11228 --- /dev/null +++ b/m4/lchmod.m4 @@ -0,0 +1,84 @@ +#serial 6 + +dnl Copyright (C) 2005-2006, 2008-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. + +dnl From Paul Eggert. +dnl Provide a replacement for lchmod on hosts that lack a working version. + +AC_DEFUN([gl_FUNC_LCHMOD], +[ + AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS]) + + dnl Persuade glibc <sys/stat.h> to declare lchmod(). + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + + AC_CHECK_FUNCS_ONCE([fchmodat lchmod lstat]) + if test "$ac_cv_func_lchmod" = no; then + HAVE_LCHMOD=0 + else + AC_CACHE_CHECK([whether lchmod works on non-symlinks], + [gl_cv_func_lchmod_works], + [AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [ + AC_INCLUDES_DEFAULT[ + #ifndef S_IRUSR + #define S_IRUSR 0400 + #endif + #ifndef S_IWUSR + #define S_IWUSR 0200 + #endif + #ifndef S_IRWXU + #define S_IRWXU 0700 + #endif + #ifndef S_IRWXG + #define S_IRWXG 0070 + #endif + #ifndef S_IRWXO + #define S_IRWXO 0007 + #endif + ]], + [[ + int permissive = S_IRWXU | S_IRWXG | S_IRWXO; + int desired = S_IRUSR | S_IWUSR; + static char const f[] = "conftest.lchmod"; + struct stat st; + if (creat (f, permissive) < 0) + return 1; + if (lchmod (f, desired) != 0) + return 1; + if (stat (f, &st) != 0) + return 1; + return ! ((st.st_mode & permissive) == desired); + ]])], + [gl_cv_func_lchmod_works=yes], + [gl_cv_func_lchmod_works=no], + [case "$host_os" in + dnl Guess no on Linux with glibc, yes otherwise. + linux-gnu*) gl_cv_func_lchmod_works="guessing no" ;; + *) gl_cv_func_lchmod_works="$gl_cross_guess_normal" ;; + esac + ]) + rm -f conftest.lchmod]) + case $gl_cv_func_lchmod_works in + *yes) ;; + *) + AC_DEFINE([NEED_LCHMOD_NONSYMLINK_FIX], [1], + [Define to 1 if lchmod does not work right on non-symlinks.]) + REPLACE_LCHMOD=1 + ;; + esac + fi +]) + +# Prerequisites of lib/lchmod.c. +AC_DEFUN([gl_PREREQ_LCHMOD], +[ + AC_REQUIRE([AC_C_INLINE]) + : +]) diff --git a/src/fileio.c b/src/fileio.c index 87a17eab42..9383f2606b 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -3332,10 +3332,11 @@ DEFUN ("set-file-acl", Fset_file_acl, Sset_file_acl, return Qnil; } -DEFUN ("file-modes", Ffile_modes, Sfile_modes, 1, 1, 0, +DEFUN ("file-modes", Ffile_modes, Sfile_modes, 1, 2, 0, doc: /* Return mode bits of file named FILENAME, as an integer. -Return nil if FILENAME does not exist. */) - (Lisp_Object filename) +Return nil if FILENAME does not exist. If optional NOFOLLOW is t, then +do not follow FILENAME if it is a symbolic link. */) + (Lisp_Object filename, Lisp_Object nofollow) { struct stat st; Lisp_Object absname = expand_and_dir_to_file (filename); @@ -3344,38 +3345,40 @@ DEFUN ("file-modes", Ffile_modes, Sfile_modes, 1, 1, 0, call the corresponding file name handler. */ Lisp_Object handler = Ffind_file_name_handler (absname, Qfile_modes); if (!NILP (handler)) - return call2 (handler, Qfile_modes, absname); + return call3 (handler, Qfile_modes, absname, nofollow); - if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (absname)), &st, 0) != 0) + char *fname = SSDATA (ENCODE_FILE (absname)); + int flags = !NILP (nofollow) ? AT_SYMLINK_NOFOLLOW : 0; + if (emacs_fstatat (AT_FDCWD, fname, &st, flags) != 0) return file_attribute_errno (absname, errno); return make_fixnum (st.st_mode & 07777); } -DEFUN ("set-file-modes", Fset_file_modes, Sset_file_modes, 2, 2, +DEFUN ("set-file-modes", Fset_file_modes, Sset_file_modes, 2, 3, "(let ((file (read-file-name \"File: \"))) \ (list file (read-file-modes nil file)))", doc: /* Set mode bits of file named FILENAME to MODE (an integer). -Only the 12 low bits of MODE are used. +Only the 12 low bits of MODE are used. If optional NOFOLLOW is t, then +do not follow FILENAME if it is a symbolic link. Interactively, mode bits are read by `read-file-modes', which accepts symbolic notation, like the `chmod' command from GNU Coreutils. */) - (Lisp_Object filename, Lisp_Object mode) + (Lisp_Object filename, Lisp_Object mode, Lisp_Object nofollow) { - Lisp_Object absname, encoded_absname; - Lisp_Object handler; - - absname = Fexpand_file_name (filename, BVAR (current_buffer, directory)); CHECK_FIXNUM (mode); + Lisp_Object absname = Fexpand_file_name (filename, + BVAR (current_buffer, directory)); /* If the file name has special constructs in it, call the corresponding file name handler. */ - handler = Ffind_file_name_handler (absname, Qset_file_modes); + Lisp_Object handler = Ffind_file_name_handler (absname, Qset_file_modes); if (!NILP (handler)) - return call3 (handler, Qset_file_modes, absname, mode); - - encoded_absname = ENCODE_FILE (absname); + return call4 (handler, Qset_file_modes, absname, mode, nofollow); - if (chmod (SSDATA (encoded_absname), XFIXNUM (mode) & 07777) < 0) + char *fname = SSDATA (ENCODE_FILE (absname)); + mode_t imode = XFIXNUM (mode) & 07777; + int flags = !NILP (nofollow) ? AT_SYMLINK_NOFOLLOW : 0; + if (fchmodat (AT_FDCWD, fname, imode, flags) < 0) report_file_error ("Doing chmod", absname); return Qnil; @@ -5740,7 +5743,7 @@ auto_save_1 (void) == 0) /* But make sure we can overwrite it later! */ auto_save_mode_bits = (st.st_mode | 0600) & 0777; - else if (modes = Ffile_modes (BVAR (current_buffer, filename)), + else if (modes = Ffile_modes (BVAR (current_buffer, filename), Qnil), FIXNUMP (modes)) /* Remote files don't cooperate with fstatat. */ auto_save_mode_bits = (XFIXNUM (modes) | 0600) & 0777; -- 2.24.1
bug-gnu-emacs <at> gnu.org
:bug#39683
; Package emacs
.
(Thu, 20 Feb 2020 14:53:01 GMT) Full text and rfc822 format available.Message #8 received at 39683 <at> debbugs.gnu.org (full text, mbox):
From: Eli Zaretskii <eliz <at> gnu.org> To: Paul Eggert <eggert <at> cs.ucla.edu> Cc: 39683 <at> debbugs.gnu.org Subject: Re: bug#39683: [PATCH] Add NOFOLLOW flag to set-file-modes etc. Date: Thu, 20 Feb 2020 16:51:53 +0200
> From: Paul Eggert <eggert <at> cs.ucla.edu> > Date: Wed, 19 Feb 2020 16:34:00 -0800 > Cc: Paul Eggert <eggert <at> cs.ucla.edu> > > diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el > index 0069c1744d..de5e8bb567 100644 > --- a/lisp/dired-aux.el > +++ b/lisp/dired-aux.el > @@ -409,7 +409,8 @@ dired-do-chmod > (set-file-modes > file > (if num-modes num-modes > - (file-modes-symbolic-to-number modes (file-modes file))))) > + (file-modes-symbolic-to-number modes (file-modes file t))) Can we please use some descriptive symbol, like 'nofollow, instead of just t? I expect that to save at least some of us an extra read of the doc string each time we see such code. TIA
Paul Eggert <eggert <at> cs.ucla.edu>
:Paul Eggert <eggert <at> cs.ucla.edu>
:Message #13 received at 39683-done <at> debbugs.gnu.org (full text, mbox):
From: Paul Eggert <eggert <at> cs.ucla.edu> To: Eli Zaretskii <eliz <at> gnu.org> Cc: 39683-done <at> debbugs.gnu.org Subject: Re: bug#39683: [PATCH] Add NOFOLLOW flag to set-file-modes etc. Date: Sun, 23 Feb 2020 16:50:09 -0800
[Message part 1 (text/plain, inline)]
On 2/20/20 6:51 AM, Eli Zaretskii wrote: > Can we please use some descriptive symbol, like 'nofollow, instead of > just t? Sure, I did that and installed the attached (revised) patch into master. This also contains some minor improvements, notably one that adds support for the new flag to tramp-handle-file-modes.
[0001-Add-nofollow-flag-to-set-file-modes-etc.patch (text/x-patch, attachment)]
Debbugs Internal Request <help-debbugs <at> gnu.org>
to internal_control <at> debbugs.gnu.org
.
(Mon, 23 Mar 2020 11:24:06 GMT) Full text and rfc822 format available.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.