From unknown Sat Aug 16 18:47:40 2025 X-Loop: help-debbugs@gnu.org Subject: bug#39683: [PATCH] Add NOFOLLOW flag to set-file-modes etc. Resent-From: Paul Eggert Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 20 Feb 2020 00:35:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 39683 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch To: 39683@debbugs.gnu.org Cc: Paul Eggert X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Received: via spool by submit@debbugs.gnu.org id=B.158215886710123 (code B ref -1); Thu, 20 Feb 2020 00:35:01 +0000 Received: (at submit) by debbugs.gnu.org; 20 Feb 2020 00:34:27 +0000 Received: from localhost ([127.0.0.1]:43601 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1j4ZnN-0002dC-VP for submit@debbugs.gnu.org; Wed, 19 Feb 2020 19:34:27 -0500 Received: from lists.gnu.org ([209.51.188.17]:39524) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1j4ZnL-0002d2-Bo for submit@debbugs.gnu.org; Wed, 19 Feb 2020 19:34:25 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:52544) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4ZnG-0003dc-Cz for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:34:23 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-1.5 required=5.0 tests=BAYES_50,RCVD_IN_DNSWL_MED, URIBL_BLOCKED autolearn=disabled version=3.3.2 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j4ZnA-00074u-Hh for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:34:17 -0500 Received: from zimbra.cs.ucla.edu ([131.179.128.68]:42006) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1j4Zn9-00073f-QM for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:34:12 -0500 Received: from localhost (localhost [127.0.0.1]) by zimbra.cs.ucla.edu (Postfix) with ESMTP id 90E001600AB for ; Wed, 19 Feb 2020 16:34:08 -0800 (PST) Received: from zimbra.cs.ucla.edu ([127.0.0.1]) by localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032) with ESMTP id HpxGVqt6ILb8; Wed, 19 Feb 2020 16:34:04 -0800 (PST) Received: from localhost (localhost [127.0.0.1]) by zimbra.cs.ucla.edu (Postfix) with ESMTP id 8773D1600A4; Wed, 19 Feb 2020 16:34:04 -0800 (PST) X-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu Received: from zimbra.cs.ucla.edu ([127.0.0.1]) by localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id 0APMZINEpU7k; Wed, 19 Feb 2020 16:34:04 -0800 (PST) Received: from Penguin.CS.UCLA.EDU (Penguin.CS.UCLA.EDU [131.179.64.200]) by zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 4D7421600A2; Wed, 19 Feb 2020 16:34:04 -0800 (PST) From: Paul Eggert Date: Wed, 19 Feb 2020 16:34:00 -0800 Message-Id: <20200220003400.265566-1-eggert@cs.ucla.edu> X-Mailer: git-send-email 2.24.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 131.179.128.68 X-Spam-Score: 0.3 (/) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.7 (/) 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=E2=80=99s permissions, using th= e 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=3D crypto/md5-buffer crypto/sha1-buffer crypto/sha256-buffer crypto/sha51= 2-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 =20 -@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. =20 +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. =20 @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 =20 @group -(set-file-modes "~/junk/diffs" #o666) +(set-file-modes "~/junk/diffs" #o666 t) @result{} nil @end group =20 @@ -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}. =20 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, =20 @example -(set-file-modes #o644) +(set-file-modes "myfile" #o644 t) @end example =20 @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 =20 @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. =20 +** '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". =20 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 +#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 = . */ + +/* written by Jim Meyering and Paul Eggert */ + +/* If the user's config.h happens to include , let it includ= e only + the system's here, so that orig_fchmodat doesn't recurse= to + rpl_fchmodat. */ +#define __need_system_sys_stat_h +#include + +/* Specification. */ +#include +#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 +#include +#include +#include +#include + +#ifdef __osf__ +/* Write "sys/stat.h" here, not , otherwise OSF/1 5.1 DTK cc + eliminates this include because of the preliminary #include + above. */ +# include "sys/stat.h" +#else +# include +#endif + +#include + +/* 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 =3D=3D 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. = */ + int fd =3D 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 li= nk + - 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) !=3D 0) + { + int stat_errno =3D errno; + close (fd); + errno =3D stat_errno; + return -1; + } + if (S_ISLNK (st.st_mode)) + { + close (fd); + errno =3D EOPNOTSUPP; + return -1; + } + + static char const fmt[] =3D "/proc/self/fd/%d"; + char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; + sprintf (buf, fmt, fd); + int chmod_result =3D chmod (buf, mode); + int chmod_errno =3D errno; + close (fd); + if (chmod_result =3D=3D 0) + return chmod_result; + if (chmod_errno !=3D ENOENT) + { + errno =3D 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 =3D fstatat (dir, file, &st, AT_SYMLINK_NOFOLLO= W); + if (fstatat_result !=3D 0) + return fstatat_result; + if (S_ISLNK (st.st_mode)) + { + errno =3D 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 =3D @gl_GNULIB_ENABLED_dirf= d@ gl_GNULIB_ENABLED_euidaccess =3D @gl_GNULIB_ENABLED_euidaccess@ gl_GNULIB_ENABLED_getdtablesize =3D @gl_GNULIB_ENABLED_getdtablesize@ gl_GNULIB_ENABLED_getgroups =3D @gl_GNULIB_ENABLED_getgroups@ +gl_GNULIB_ENABLED_lchmod =3D @gl_GNULIB_ENABLED_lchmod@ gl_GNULIB_ENABLED_malloca =3D @gl_GNULIB_ENABLED_malloca@ gl_GNULIB_ENABLED_open =3D @gl_GNULIB_ENABLED_open@ gl_GNULIB_ENABLED_strtoll =3D @gl_GNULIB_ENABLED_strtoll@ @@ -1587,6 +1589,18 @@ EXTRA_libgnu_a_SOURCES +=3D at-func.c faccessat.c endif ## end gnulib module faccessat =20 +## begin gnulib module fchmodat +ifeq (,$(OMIT_GNULIB_MODULE_fchmodat)) + +libgnu_a_SOURCES +=3D chmodat.c + +EXTRA_DIST +=3D at-func.c fchmodat.c + +EXTRA_libgnu_a_SOURCES +=3D at-func.c fchmodat.c + +endif +## end gnulib module fchmodat + ## begin gnulib module fcntl ifeq (,$(OMIT_GNULIB_MODULE_fcntl)) =20 @@ -1937,6 +1951,19 @@ EXTRA_DIST +=3D inttypes.in.h endif ## end gnulib module inttypes-incomplete =20 +## begin gnulib module lchmod +ifeq (,$(OMIT_GNULIB_MODULE_lchmod)) + +ifneq (,$(gl_GNULIB_ENABLED_lchmod)) + +endif +EXTRA_DIST +=3D lchmod.c + +EXTRA_libgnu_a_SOURCES +=3D lchmod.c + +endif +## end gnulib module lchmod + ## begin gnulib module libc-config ifeq (,$(OMIT_GNULIB_MODULE_libc-config)) =20 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 = . */ + +/* written by Paul Eggert */ + +#include + +/* If the user's config.h happens to include , let it includ= e only + the system's here, so that orig_fchmodat doesn't recurse= to + rpl_fchmodat. */ +#define __need_system_sys_stat_h +#include + +/* Specification. */ +#include +#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 +#include +#include +#include + +#ifdef __osf__ +/* Write "sys/stat.h" here, not , otherwise OSF/1 5.1 DTK cc + eliminates this include because of the preliminary #include + above. */ +# include "sys/stat.h" +#else +# include +#endif + +#include + +/* Work like chmod, except when FILE is a symbolic link. + In that case, on systems where permissions on symbolic links are unsu= pported + (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. */ + int fd =3D 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) !=3D 0) + { + int stat_errno =3D errno; + close (fd); + errno =3D stat_errno; + return -1; + } + if (S_ISLNK (st.st_mode)) + { + close (fd); + errno =3D EOPNOTSUPP; + return -1; + } + + static char const fmt[] =3D "/proc/self/fd/%d"; + char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; + sprintf (buf, fmt, fd); + int chmod_result =3D chmod (buf, mode); + int chmod_errno =3D errno; + close (fd); + if (chmod_result =3D=3D 0) + return chmod_result; + if (chmod_errno !=3D ENOENT) + { + errno =3D 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 =3D lstat (file, &st); + if (lstat_result !=3D 0) + return lstat_result; + if (S_ISLNK (st.st_mode)) + { + errno =3D 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 Wi= ndows */ + 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))) =20 ;;;###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, t= hus ;; 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 (=3D 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 (=3D 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))))) =20 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))))) =20 (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))) =20 ;; 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)))))) =20 =0C 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 ".."))))))))) =20 -(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))) =20 (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))) =20 ;;; ;;; 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) =20 -(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))) =20 (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))) =20 (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 locknam= e)) (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)))) =20 -(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 local= name)))) 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)))) =20 -(defun tramp-gvfs-handle-set-file-modes (filename mode) +(defun tramp-gvfs-handle-set-file-modes (filename mode &optional nofollo= w) "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)))) =20 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))))))))) =20 -(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))))) =20 ;; 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)) =20 @@ -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)) =20 ;; 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"))))))) =20 -(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))))) =20 -(defun tramp-sudoedit-handle-set-file-modes (filename mode) +(defun tramp-sudoedit-handle-set-file-modes (filename mode &optional nof= ollow) "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)))))) =20 =0C ;; 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))) =20 -(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) filena= me)))) (tramp-mode-string-to-int (tramp-compat-file-attribute-modes attrs))= )) =20 @@ -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))) =20 ;; 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 =3D %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)" mod= es)))) (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)))) =20 (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 !=3D yes; then + HAVE_FCHMODAT=3D0 + 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 + #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 =3D S_IRWXU | S_IRWXG | S_IRWXO; + int desired =3D S_IRUSR | S_IWUSR; + static char const f[] =3D "conftest.fchmodat"; + struct stat st; + if (creat (f, permissive) < 0) + return 1; + if (fchmodat (AT_FDCWD, f, desired, AT_SYMLINK_NOFOLLOW) != =3D 0) + return 1; + if (stat (f, &st) !=3D 0) + return 1; + return ! ((st.st_mode & permissive) =3D=3D desired); + ]])], + [gl_cv_func_fchmodat_works=3Dyes], + [gl_cv_func_fchmodat_works=3Dno], + [case "$host_os" in + dnl Guess no on Linux with glibc and Cygwin, yes otherwise. + linux-gnu* | cygwin*) gl_cv_func_fchmodat_works=3D"guessing = no" ;; + *) gl_cv_func_fchmodat_works=3D"$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 rig= ht on non-symlinks.]) + REPLACE_FCHMODAT=3D1 + ;; + 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 =3D 0 || test $REPLACE_FCHMODAT =3D 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 =3D 0 || test $REPLACE_FCNTL =3D 1; then AC_LIBOBJ([fcntl]) @@ -463,6 +472,7 @@ AC_DEFUN gl_gnulib_enabled_getgroups=3Dfalse gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=3Dfalse gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1=3Dfalse + gl_gnulib_enabled_lchmod=3Dfalse gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467=3Dfalse gl_gnulib_enabled_2049e887c7e5308faad27b3f894bb8c9=3Dfalse gl_gnulib_enabled_malloca=3Dfalse @@ -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 =3D 0 || test $REPLACE_LCHMOD =3D 1; then + AC_LIBOBJ([lchmod]) + gl_PREREQ_LCHMOD + fi + gl_SYS_STAT_MODULE_INDICATOR([lchmod]) + gl_gnulib_enabled_lchmod=3Dtrue + fi + } func_gl_gnulib_m4code_21ee726a3540c09237a8e70c0baf7467 () { if ! $gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467; then @@ -655,6 +677,15 @@ AC_DEFUN if test $HAVE_FACCESSAT =3D 0 || test $REPLACE_FACCESSAT =3D 1; then func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b + fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_lchmod + fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 + fi if test $HAVE_FCNTL =3D 0 || test $REPLACE_FCNTL =3D 1; then func_gl_gnulib_m4code_getdtablesize fi @@ -703,6 +734,7 @@ AC_DEFUN AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getg= roups]) 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_malloc= a]) @@ -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 versio= n. + +AC_DEFUN([gl_FUNC_LCHMOD], +[ + AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS]) + + dnl Persuade glibc 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" =3D no; then + HAVE_LCHMOD=3D0 + 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 =3D S_IRWXU | S_IRWXG | S_IRWXO; + int desired =3D S_IRUSR | S_IWUSR; + static char const f[] =3D "conftest.lchmod"; + struct stat st; + if (creat (f, permissive) < 0) + return 1; + if (lchmod (f, desired) !=3D 0) + return 1; + if (stat (f, &st) !=3D 0) + return 1; + return ! ((st.st_mode & permissive) =3D=3D desired); + ]])], + [gl_cv_func_lchmod_works=3Dyes], + [gl_cv_func_lchmod_works=3Dno], + [case "$host_os" in + dnl Guess no on Linux with glibc, yes otherwise. + linux-gnu*) gl_cv_func_lchmod_works=3D"guessing no" ;; + *) gl_cv_func_lchmod_works=3D"$gl_cross_guess_norma= l" ;; + 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=3D1 + ;; + 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_a= cl, return Qnil; } =0C -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 =3D 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 =3D Ffind_file_name_handler (absname, Qfile_modes)= ; if (!NILP (handler)) - return call2 (handler, Qfile_modes, absname); + return call3 (handler, Qfile_modes, absname, nofollow); =20 - if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (absname)), &st, 0) != =3D 0) + char *fname =3D SSDATA (ENCODE_FILE (absname)); + int flags =3D !NILP (nofollow) ? AT_SYMLINK_NOFOLLOW : 0; + if (emacs_fstatat (AT_FDCWD, fname, &st, flags) !=3D 0) return file_attribute_errno (absname, errno); return make_fixnum (st.st_mode & 07777); } =20 -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. =20 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 =3D Fexpand_file_name (filename, BVAR (current_buffer, directo= ry)); CHECK_FIXNUM (mode); + Lisp_Object absname =3D Fexpand_file_name (filename, + BVAR (current_buffer, directory)); =20 /* If the file name has special constructs in it, call the corresponding file name handler. */ - handler =3D Ffind_file_name_handler (absname, Qset_file_modes); + Lisp_Object handler =3D Ffind_file_name_handler (absname, Qset_file_mo= des); if (!NILP (handler)) - return call3 (handler, Qset_file_modes, absname, mode); - - encoded_absname =3D ENCODE_FILE (absname); + return call4 (handler, Qset_file_modes, absname, mode, nofollow); =20 - if (chmod (SSDATA (encoded_absname), XFIXNUM (mode) & 07777) < 0) + char *fname =3D SSDATA (ENCODE_FILE (absname)); + mode_t imode =3D XFIXNUM (mode) & 07777; + int flags =3D !NILP (nofollow) ? AT_SYMLINK_NOFOLLOW : 0; + if (fchmodat (AT_FDCWD, fname, imode, flags) < 0) report_file_error ("Doing chmod", absname); =20 return Qnil; @@ -5740,7 +5743,7 @@ auto_save_1 (void) =3D=3D 0) /* But make sure we can overwrite it later! */ auto_save_mode_bits =3D (st.st_mode | 0600) & 0777; - else if (modes =3D Ffile_modes (BVAR (current_buffer, filename)), + else if (modes =3D Ffile_modes (BVAR (current_buffer, filename), Q= nil), FIXNUMP (modes)) /* Remote files don't cooperate with fstatat. */ auto_save_mode_bits =3D (XFIXNUM (modes) | 0600) & 0777; --=20 2.24.1 From unknown Sat Aug 16 18:47:40 2025 X-Loop: help-debbugs@gnu.org Subject: bug#39683: [PATCH] Add NOFOLLOW flag to set-file-modes etc. Resent-From: Eli Zaretskii Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 20 Feb 2020 14:53:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 39683 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch To: Paul Eggert Cc: 39683@debbugs.gnu.org Received: via spool by 39683-submit@debbugs.gnu.org id=B39683.158221034823515 (code B ref 39683); Thu, 20 Feb 2020 14:53:01 +0000 Received: (at 39683) by debbugs.gnu.org; 20 Feb 2020 14:52:28 +0000 Received: from localhost ([127.0.0.1]:44166 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1j4nBj-00067C-KY for submit@debbugs.gnu.org; Thu, 20 Feb 2020 09:52:27 -0500 Received: from eggs.gnu.org ([209.51.188.92]:57278) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1j4nBh-00066y-S3 for 39683@debbugs.gnu.org; Thu, 20 Feb 2020 09:52:26 -0500 Received: from fencepost.gnu.org ([2001:470:142:3::e]:60078) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1j4nBb-0002jv-SF; Thu, 20 Feb 2020 09:52:19 -0500 Received: from [176.228.60.248] (port=2290 helo=home-c4e4a596f7) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1j4nBQ-0002D3-CY; Thu, 20 Feb 2020 09:52:19 -0500 Date: Thu, 20 Feb 2020 16:51:53 +0200 Message-Id: <831rqpl3au.fsf@gnu.org> From: Eli Zaretskii In-reply-to: <20200220003400.265566-1-eggert@cs.ucla.edu> (message from Paul Eggert on Wed, 19 Feb 2020 16:34:00 -0800) References: <20200220003400.265566-1-eggert@cs.ucla.edu> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Spam-Score: -0.7 (/) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.7 (-) > From: Paul Eggert > Date: Wed, 19 Feb 2020 16:34:00 -0800 > Cc: Paul Eggert > > 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 From unknown Sat Aug 16 18:47:40 2025 MIME-Version: 1.0 X-Mailer: MIME-tools 5.505 (Entity 5.505) X-Loop: help-debbugs@gnu.org From: help-debbugs@gnu.org (GNU bug Tracking System) To: Paul Eggert Subject: bug#39683: closed (Re: bug#39683: [PATCH] Add NOFOLLOW flag to set-file-modes etc.) Message-ID: References: <03f57850-4c4b-34d7-bc9d-a5500fa7755a@cs.ucla.edu> <20200220003400.265566-1-eggert@cs.ucla.edu> X-Gnu-PR-Message: they-closed 39683 X-Gnu-PR-Package: emacs X-Gnu-PR-Keywords: patch Reply-To: 39683@debbugs.gnu.org Date: Mon, 24 Feb 2020 00:51:02 +0000 Content-Type: multipart/mixed; boundary="----------=_1582505462-25473-1" This is a multi-part message in MIME format... ------------=_1582505462-25473-1 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Your bug report #39683: [PATCH] Add NOFOLLOW flag to set-file-modes etc. which was filed against the emacs package, has been closed. The explanation is attached below, along with your original report. If you require more details, please reply to 39683@debbugs.gnu.org. --=20 39683: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=3D39683 GNU Bug Tracking System Contact help-debbugs@gnu.org with problems ------------=_1582505462-25473-1 Content-Type: message/rfc822 Content-Disposition: inline Content-Transfer-Encoding: 7bit Received: (at 39683-done) by debbugs.gnu.org; 24 Feb 2020 00:50:25 +0000 Received: from localhost ([127.0.0.1]:51737 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1j61x1-0006bk-UQ for submit@debbugs.gnu.org; Sun, 23 Feb 2020 19:50:25 -0500 Received: from zimbra.cs.ucla.edu ([131.179.128.68]:56164) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1j61wy-0006bV-4z for 39683-done@debbugs.gnu.org; Sun, 23 Feb 2020 19:50:22 -0500 Received: from localhost (localhost [127.0.0.1]) by zimbra.cs.ucla.edu (Postfix) with ESMTP id 71B9516009E; Sun, 23 Feb 2020 16:50:14 -0800 (PST) Received: from zimbra.cs.ucla.edu ([127.0.0.1]) by localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032) with ESMTP id gwbqPweZDK4M; Sun, 23 Feb 2020 16:50:10 -0800 (PST) Received: from localhost (localhost [127.0.0.1]) by zimbra.cs.ucla.edu (Postfix) with ESMTP id 7C4EE1600A2; Sun, 23 Feb 2020 16:50:10 -0800 (PST) X-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu Received: from zimbra.cs.ucla.edu ([127.0.0.1]) by localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id BA_r8yZWzxJC; Sun, 23 Feb 2020 16:50:10 -0800 (PST) Received: from [192.168.1.9] (cpe-23-242-74-103.socal.res.rr.com [23.242.74.103]) by zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 3DF5016009E; Sun, 23 Feb 2020 16:50:10 -0800 (PST) Subject: Re: bug#39683: [PATCH] Add NOFOLLOW flag to set-file-modes etc. To: Eli Zaretskii References: <20200220003400.265566-1-eggert@cs.ucla.edu> <831rqpl3au.fsf@gnu.org> From: Paul Eggert Organization: UCLA Computer Science Department Message-ID: <03f57850-4c4b-34d7-bc9d-a5500fa7755a@cs.ucla.edu> Date: Sun, 23 Feb 2020 16:50:09 -0800 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.4.1 MIME-Version: 1.0 In-Reply-To: <831rqpl3au.fsf@gnu.org> Content-Type: multipart/mixed; boundary="------------DF02D43B886735289119FE5B" Content-Language: en-US X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: 39683-done Cc: 39683-done@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) This is a multi-part message in MIME format. --------------DF02D43B886735289119FE5B Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit 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. --------------DF02D43B886735289119FE5B Content-Type: text/x-patch; charset=UTF-8; name="0001-Add-nofollow-flag-to-set-file-modes-etc.patch" Content-Disposition: attachment; filename="0001-Add-nofollow-flag-to-set-file-modes-etc.patch" Content-Transfer-Encoding: quoted-printable >From 9d626dffc6ba62c0d7a1a5c712f576ed8684fd66 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Sun, 23 Feb 2020 16:19:42 -0800 Subject: [PATCH] Add 'nofollow' flag to set-file-modes etc. MIME-Version: 1.0 Content-Type: text/plain; charset=3DUTF-8 Content-Transfer-Encoding: 8bit This avoids some race conditions (Bug#39683). E.g., if some other program changes a file to a symlink between the time Emacs creates the file and the time it changes the file=E2=80=99s permissions, using th= e new flag prevents Emacs from inadvertently changing the permissions of a victim in some completely unrelated directory. * admin/merge-gnulib (GNULIB_MODULES): Add fchmodat. * doc/lispref/files.texi (Testing Accessibility, Changing Files): * doc/lispref/os.texi (File Notifications): * etc/NEWS: Adjust documentation accordingly. * lib/chmodat.c, lib/fchmodat.c, lib/lchmod.c, m4/fchmodat.m4: * m4/lchmod.m4: New files, copied from Gnulib. * 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.el (tramp-handle-file-modes): * lisp/net/tramp-gvfs.el (tramp-gvfs-handle-set-file-modes): * src/fileio.c (symlink_nofollow_flag): New function. (Ffile_modes, Fset_file_modes): Support an optional FLAG arg. All C callers changed. * 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): Accept an optional FLAG 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 | 27 +++++-- doc/lispref/os.texi | 2 +- etc/NEWS | 3 + lib/fchmodat.c | 144 ++++++++++++++++++++++++++++++++++++ lib/gnulib.mk.in | 26 +++++++ lib/lchmod.c | 110 +++++++++++++++++++++++++++ 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 | 9 ++- lisp/net/tramp-gvfs.el | 4 +- lisp/net/tramp-sh.el | 10 ++- lisp/net/tramp-smb.el | 3 +- lisp/net/tramp-sudoedit.el | 6 +- lisp/net/tramp.el | 13 ++-- lisp/server.el | 2 +- lisp/url/url-util.el | 4 +- m4/fchmodat.m4 | 82 ++++++++++++++++++++ m4/gnulib-comp.m4 | 35 +++++++++ m4/lchmod.m4 | 31 ++++++++ src/fileio.c | 46 +++++++----- 30 files changed, 533 insertions(+), 64 deletions(-) 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=3D crypto/md5-buffer crypto/sha1-buffer crypto/sha256-buffer crypto/sha51= 2-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..a69a4e5dd3 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 =20 -@defun file-modes filename +@defun file-modes filename &optional flag @cindex mode bits @cindex file permissions @cindex permissions, file @@ -946,12 +946,19 @@ 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. =20 +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 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. =20 @example @group -(file-modes "~/junk/diffs") +(file-modes "~/junk/diffs" 'nofollow) @result{} 492 ; @r{Decimal integer.} @end group @group @@ -960,7 +967,7 @@ Testing Accessibility @end group =20 @group -(set-file-modes "~/junk/diffs" #o666) +(set-file-modes "~/junk/diffs" #o666 'nofollow) @result{} nil @end group =20 @@ -1801,9 +1808,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 &optional flag 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{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 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{flag} is @code{nofollow}. =20 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 +1826,7 @@ Changing Files octal numbers to enter @var{mode}. For example, =20 @example -(set-file-modes #o644) +(set-file-modes "myfile" #o644 'nofollow) @end example =20 @noindent diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index a034ccdcd5..cf4ef52abf 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -3127,7 +3127,7 @@ File Notifications @end group =20 @group -(set-file-modes "/tmp/foo" (default-file-modes)) +(set-file-modes "/tmp/foo" (default-file-modes) 'nofollow) @result{} Event (35025468 attribute-changed "/tmp/foo") @end group @end example diff --git a/etc/NEWS b/etc/NEWS index 0279879836..5ca054363d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -198,6 +198,9 @@ called when the function object is garbage-collected.= Use 'set_function_finalizer' to set the finalizer and 'get_function_finalizer' to retrieve it. =20 +** '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". =20 diff --git a/lib/fchmodat.c b/lib/fchmodat.c new file mode 100644 index 0000000000..8950168608 --- /dev/null +++ b/lib/fchmodat.c @@ -0,0 +1,144 @@ +/* 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 = . */ + +/* written by Jim Meyering and Paul Eggert */ + +/* If the user's config.h happens to include , let it includ= e only + the system's here, so that orig_fchmodat doesn't recurse= to + rpl_fchmodat. */ +#define __need_system_sys_stat_h +#include + +/* Specification. */ +#include +#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 +#include +#include +#include +#include + +#ifdef __osf__ +/* Write "sys/stat.h" here, not , otherwise OSF/1 5.1 DTK cc + eliminates this include because of the preliminary #include + above. */ +# include "sys/stat.h" +#else +# include +#endif + +#include + +/* 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 =3D=3D AT_SYMLINK_NOFOLLOW) + { + struct stat st; + +# if defined O_PATH && defined AT_EMPTY_PATH + /* 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. = */ + int fd =3D 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 li= nk + - which is undesired - and on many file systems (ext4, btrfs, j= fs, + 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) !=3D 0) + { + int stat_errno =3D errno; + close (fd); + errno =3D stat_errno; + return -1; + } + if (S_ISLNK (st.st_mode)) + { + close (fd); + errno =3D EOPNOTSUPP; + return -1; + } + +# if defined __linux__ || defined __ANDROID__ + static char const fmt[] =3D "/proc/self/fd/%d"; + char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; + sprintf (buf, fmt, fd); + int chmod_result =3D chmod (buf, mode); + int chmod_errno =3D errno; + close (fd); + if (chmod_result =3D=3D 0) + return chmod_result; + if (chmod_errno !=3D ENOENT) + { + errno =3D chmod_errno; + return chmod_result; + } +# endif + /* /proc is not mounted or would not work as in GNU/Linux. */ + +# else + int fstatat_result =3D fstatat (dir, file, &st, AT_SYMLINK_NOFOLLO= W); + if (fstatat_result !=3D 0) + return fstatat_result; + if (S_ISLNK (st.st_mode)) + { + errno =3D EOPNOTSUPP; + return -1; + } +# endif + + /* Fall back on orig_fchmodat with no flags, despite a possible ra= ce. */ + flags =3D 0; + } +# 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 3c01e61b26..d4dc6a3df3 100644 --- a/lib/gnulib.mk.in +++ b/lib/gnulib.mk.in @@ -95,6 +95,7 @@ # execinfo \ # explicit_bzero \ # faccessat \ +# fchmodat \ # fcntl \ # fcntl-h \ # fdopendir \ @@ -1082,6 +1083,7 @@ gl_GNULIB_ENABLED_dirfd =3D @gl_GNULIB_ENABLED_dirf= d@ gl_GNULIB_ENABLED_euidaccess =3D @gl_GNULIB_ENABLED_euidaccess@ gl_GNULIB_ENABLED_getdtablesize =3D @gl_GNULIB_ENABLED_getdtablesize@ gl_GNULIB_ENABLED_getgroups =3D @gl_GNULIB_ENABLED_getgroups@ +gl_GNULIB_ENABLED_lchmod =3D @gl_GNULIB_ENABLED_lchmod@ gl_GNULIB_ENABLED_malloca =3D @gl_GNULIB_ENABLED_malloca@ gl_GNULIB_ENABLED_open =3D @gl_GNULIB_ENABLED_open@ gl_GNULIB_ENABLED_strtoll =3D @gl_GNULIB_ENABLED_strtoll@ @@ -1586,6 +1588,17 @@ EXTRA_libgnu_a_SOURCES +=3D at-func.c faccessat.c endif ## end gnulib module faccessat =20 +## begin gnulib module fchmodat +ifeq (,$(OMIT_GNULIB_MODULE_fchmodat)) + + +EXTRA_DIST +=3D at-func.c fchmodat.c + +EXTRA_libgnu_a_SOURCES +=3D at-func.c fchmodat.c + +endif +## end gnulib module fchmodat + ## begin gnulib module fcntl ifeq (,$(OMIT_GNULIB_MODULE_fcntl)) =20 @@ -1936,6 +1949,19 @@ EXTRA_DIST +=3D inttypes.in.h endif ## end gnulib module inttypes-incomplete =20 +## begin gnulib module lchmod +ifeq (,$(OMIT_GNULIB_MODULE_lchmod)) + +ifneq (,$(gl_GNULIB_ENABLED_lchmod)) + +endif +EXTRA_DIST +=3D lchmod.c + +EXTRA_libgnu_a_SOURCES +=3D lchmod.c + +endif +## end gnulib module lchmod + ## begin gnulib module libc-config ifeq (,$(OMIT_GNULIB_MODULE_libc-config)) =20 diff --git a/lib/lchmod.c b/lib/lchmod.c new file mode 100644 index 0000000000..e113211623 --- /dev/null +++ b/lib/lchmod.c @@ -0,0 +1,110 @@ +/* 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 = . */ + +/* written by Paul Eggert */ + +#include + +/* Specification. */ +#include + +#include +#include +#include +#include + +#ifdef __osf__ +/* Write "sys/stat.h" here, not , otherwise OSF/1 5.1 DTK cc + eliminates this include because of the preliminary #include + above. */ +# include "sys/stat.h" +#else +# include +#endif + +#include + +/* Work like chmod, except when FILE is a symbolic link. + In that case, on systems where permissions on symbolic links are unsu= pported + (such as Linux), set errno to EOPNOTSUPP and return -1. */ + +int +lchmod (char const *file, mode_t mode) +{ +#if defined O_PATH && defined AT_EMPTY_PATH + /* 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. */ + int fd =3D open (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 undesired - 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) !=3D 0) + { + int stat_errno =3D errno; + close (fd); + errno =3D stat_errno; + return -1; + } + if (S_ISLNK (st.st_mode)) + { + close (fd); + errno =3D EOPNOTSUPP; + return -1; + } + +# if defined __linux__ || defined __ANDROID__ + static char const fmt[] =3D "/proc/self/fd/%d"; + char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; + sprintf (buf, fmt, fd); + int chmod_result =3D chmod (buf, mode); + int chmod_errno =3D errno; + close (fd); + if (chmod_result =3D=3D 0) + return chmod_result; + if (chmod_errno !=3D ENOENT) + { + errno =3D chmod_errno; + return chmod_result; + } +# endif + /* /proc is not mounted or would not work as in GNU/Linux. */ + +#elif HAVE_LSTAT + struct stat st; + int lstat_result =3D lstat (file, &st); + if (lstat_result !=3D 0) + return lstat_result; + if (S_ISLNK (st.st_mode)) + { + errno =3D EOPNOTSUPP; + return -1; + } +#endif + + /* Fall back on chmod, despite a possible race. */ + return chmod (file, mode); +} diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el index 0069c1744d..8f00317c2b 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 'nofollow))) + 'nofollow)) (dired-do-redisplay arg))) =20 ;;;###autoload diff --git a/lisp/doc-view.el b/lisp/doc-view.el index 3788d79725..8b3d5527f0 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, t= hus ;; closing a bunch of security holes. (condition-case error - (set-file-modes dir #o0700) + (set-file-modes dir #o0700 'nofollow) (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..e9f7658327 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 (=3D temp-modes desired-modes) - (set-file-modes tempfile desired-modes)) + (set-file-modes tempfile desired-modes 'nofollow)) (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..24a36393b2 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 (=3D temp-modes desired-modes) - (set-file-modes tempfile desired-modes)) + (set-file-modes tempfile desired-modes 'nofollow)) (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..7219af45f5 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 'nofollow))) (if modes (logand ,mode modes))))) =20 diff --git a/lisp/files.el b/lisp/files.el index 683f4a8ce7..2e7694d767 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-flag) (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-flag 'nofollow) 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-flag))))) =20 (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))) =20 ;; 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 (unless follow 'nofollow))) (if times (set-file-times newname times)))))) =20 =0C diff --git a/lisp/gnus/gnus-util.el b/lisp/gnus/gnus-util.el index eb0fd2522d..83a85161aa 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 ".."))))))))) =20 -(defun gnus-set-file-modes (filename mode) +(defun gnus-set-file-modes (filename mode &optional flag) "Wrapper for set-file-modes." (ignore-errors - (set-file-modes filename mode))) + (set-file-modes filename mode flag))) =20 (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..acf35a376a 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 'nofollow)) (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..96695aabfd 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 'nofollow) (message "Viewing with %s" method) (cond (needsterm diff --git a/lisp/gnus/nnmail.el b/lisp/gnus/nnmail.el index 6e01b5c4d0..93e4b0e7a8 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 'nofollow))) =20 ;;; ;;; Status functions diff --git a/lisp/net/ange-ftp.el b/lisp/net/ange-ftp.el index f28394260d..e2d4d7dd05 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) =20 -(defun ange-ftp-set-file-modes (filename mode) +(defun ange-ftp-set-file-modes (filename mode &optional flag) + flag ;; FIXME: Support 'nofollow'. (ange-ftp-call-chmod (list (format "%o" mode) filename))) =20 (defun ange-ftp-make-symbolic-link (&rest _arguments) diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el index aa7fe147c2..96ef95dbe3 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) + 'nofollow)) tmpfile))) =20 (defun tramp-adb-handle-file-writable-p (filename) @@ -636,7 +637,8 @@ 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) + 'nofollow)) (tramp-run-real-handler #'write-region (list start end tmpfile append 'no-message locknam= e)) (with-tramp-progress-reporter @@ -665,8 +667,9 @@ tramp-adb-handle-write-region (tramp-message v 0 "Wrote %s" filename)) (run-hooks 'tramp-handle-write-region-hook)))) =20 -(defun tramp-adb-handle-set-file-modes (filename mode) +(defun tramp-adb-handle-set-file-modes (filename mode &optional flag) "Like `set-file-modes' for Tramp files." + flag ;; FIXME: Support 'nofollow'. (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 local= name)))) diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el index 762c4fe4b3..79835804bc 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)))) =20 -(defun tramp-gvfs-handle-set-file-modes (filename mode) +(defun tramp-gvfs-handle-set-file-modes (filename mode &optional flag) "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 flag "-nt" "-t") "uint32" (tramp-gvfs-url-file-name (tramp-make-tramp-file-name v)) "unix::mode" (number-to-string mode)))) =20 diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index 5a3abc31ea..f31d361588 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))))))))) =20 -(defun tramp-sh-handle-set-file-modes (filename mode) +(defun tramp-sh-handle-set-file-modes (filename mode &optional flag) "Like `set-file-modes' for Tramp files." (with-parsed-tramp-file-name filename nil (tramp-flush-file-properties v localname) + flag ;; FIXME: Support 'nofollow'. ;; 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 'nofollow) (tramp-set-file-uid-gid tmpfile (tramp-get-remote-uid v 'integer) @@ -3221,7 +3222,8 @@ tramp-sh-handle-file-local-copy (delete-file tmpfile2))))) =20 ;; Set proper permissions. - (set-file-modes tmpfile (tramp-default-file-modes filename)) + (set-file-modes tmpfile (tramp-default-file-modes filename) + 'nofollow) ;; Set local user ownership. (tramp-set-file-uid-gid tmpfile)) =20 @@ -3320,7 +3322,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) 'nofollow)) =20 ;; 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..95505ea101 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"))))))) =20 -(defun tramp-smb-handle-set-file-modes (filename mode) +(defun tramp-smb-handle-set-file-modes (filename mode &optional flag) "Like `set-file-modes' for Tramp files." + flag ;; FIXME: Support 'nofollow'. (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..4654d633fa 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))))) =20 -(defun tramp-sudoedit-handle-set-file-modes (filename mode) +(defun tramp-sudoedit-handle-set-file-modes (filename mode &optional fla= g) "Like `set-file-modes' for Tramp files." + flag ;; FIXME: Support 'nofollow'. (with-parsed-tramp-file-name filename nil (tramp-flush-file-properties v localname) (unless (tramp-sudoedit-send-command @@ -735,7 +736,8 @@ 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 + (when (eq mustbenew 'excl) 'nofollow)))))) =20 =0C ;; Internal functions. diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index 409e1f7499..64acaa95d4 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -3179,10 +3179,13 @@ tramp-handle-file-local-copy (copy-file filename tmpfile 'ok-if-already-exists 'keep-time) tmpfile))) =20 -(defun tramp-handle-file-modes (filename) +(defun tramp-handle-file-modes (filename &optional flag) "Like `file-modes' for Tramp files." - (when-let ((attrs (file-attributes (or (file-truename filename) filena= me)))) - (tramp-mode-string-to-int (tramp-compat-file-attribute-modes attrs))= )) + (when-let ((attrs (file-attributes filename))) + (let ((mode-string (tramp-compat-file-attribute-modes attrs))) + (if (and (not flag) (eq ?l (aref mode-string 0))) + (tramp-handle-file-modes (file-chase-links filename) 'nofollow) + (tramp-mode-string-to-int mode-string))))) =20 ;; Localname manipulation functions that grok Tramp localnames... (defun tramp-handle-file-name-as-directory (file) @@ -3884,7 +3887,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) 'nofollow) ;; 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 +4667,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 'nofollow))) =20 ;; Return the local part. (tramp-file-local-name result))) diff --git a/lisp/server.el b/lisp/server.el index e6d8b1783c..1861218147 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 =3D %s (%d))= " (user-full-name uid) uid)) (w32 nil) ; on NTFS? - ((let ((modes (file-modes dir))) + ((let ((modes (file-modes dir 'nofollow))) (unless (zerop (logand (or modes 0) #o077)) (format "it is accessible by others (%03o)" mod= es)))) (t nil)))) diff --git a/lisp/url/url-util.el b/lisp/url/url-util.el index 645011a578..6dd7a9c2aa 100644 --- a/lisp/url/url-util.el +++ b/lisp/url/url-util.el @@ -615,9 +615,7 @@ url-make-private-file (with-temp-buffer (write-region (point-min) (point-max) file nil 'silent nil 'ex= cl))) (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 'nofollow)))) =20 (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 !=3D yes; then + HAVE_FCHMODAT=3D0 + 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 + #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 =3D S_IRWXU | S_IRWXG | S_IRWXO; + int desired =3D S_IRUSR | S_IWUSR; + static char const f[] =3D "conftest.fchmodat"; + struct stat st; + if (creat (f, permissive) < 0) + return 1; + if (fchmodat (AT_FDCWD, f, desired, AT_SYMLINK_NOFOLLOW) != =3D 0) + return 1; + if (stat (f, &st) !=3D 0) + return 1; + return ! ((st.st_mode & permissive) =3D=3D desired); + ]])], + [gl_cv_func_fchmodat_works=3Dyes], + [gl_cv_func_fchmodat_works=3Dno], + [case "$host_os" in + dnl Guess no on Linux with glibc and Cygwin, yes otherwise. + linux-gnu* | cygwin*) gl_cv_func_fchmodat_works=3D"guessing = no" ;; + *) gl_cv_func_fchmodat_works=3D"$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 rig= ht on non-symlinks.]) + REPLACE_FCHMODAT=3D1 + ;; + 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 fea32b544f..1465ce811b 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: @@ -255,6 +257,12 @@ AC_DEFUN fi gl_MODULE_INDICATOR([faccessat]) gl_UNISTD_MODULE_INDICATOR([faccessat]) + gl_FUNC_FCHMODAT + if test $HAVE_FCHMODAT =3D 0 || test $REPLACE_FCHMODAT =3D 1; then + AC_LIBOBJ([fchmodat]) + gl_PREREQ_FCHMODAT + fi + gl_SYS_STAT_MODULE_INDICATOR([fchmodat]) gl_FUNC_FCNTL if test $HAVE_FCNTL =3D 0 || test $REPLACE_FCNTL =3D 1; then AC_LIBOBJ([fcntl]) @@ -468,6 +476,7 @@ AC_DEFUN gl_gnulib_enabled_getgroups=3Dfalse gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=3Dfalse gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1=3Dfalse + gl_gnulib_enabled_lchmod=3Dfalse gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467=3Dfalse gl_gnulib_enabled_2049e887c7e5308faad27b3f894bb8c9=3Dfalse gl_gnulib_enabled_malloca=3Dfalse @@ -569,6 +578,18 @@ AC_DEFUN fi fi } + func_gl_gnulib_m4code_lchmod () + { + if ! $gl_gnulib_enabled_lchmod; then + gl_FUNC_LCHMOD + if test $HAVE_LCHMOD =3D 0; then + AC_LIBOBJ([lchmod]) + gl_PREREQ_LCHMOD + fi + gl_SYS_STAT_MODULE_INDICATOR([lchmod]) + gl_gnulib_enabled_lchmod=3Dtrue + fi + } func_gl_gnulib_m4code_21ee726a3540c09237a8e70c0baf7467 () { if ! $gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467; then @@ -660,6 +681,15 @@ AC_DEFUN if test $HAVE_FACCESSAT =3D 0 || test $REPLACE_FACCESSAT =3D 1; then func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b + fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_lchmod + fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 + fi if test $HAVE_FCNTL =3D 0 || test $REPLACE_FCNTL =3D 1; then func_gl_gnulib_m4code_getdtablesize fi @@ -708,6 +738,7 @@ AC_DEFUN AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getg= roups]) 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_malloc= a]) @@ -908,6 +939,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 @@ -946,6 +978,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 @@ -1058,6 +1091,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 @@ -1083,6 +1117,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..b9e8a97cb3 --- /dev/null +++ b/m4/lchmod.m4 @@ -0,0 +1,31 @@ +#serial 7 + +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 versio= n. + +AC_DEFUN([gl_FUNC_LCHMOD], +[ + AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS]) + + dnl Persuade glibc to declare lchmod(). + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + + AC_CHECK_FUNCS_ONCE([lchmod lstat]) + if test "$ac_cv_func_lchmod" =3D no; then + HAVE_LCHMOD=3D0 + 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 6b56c473ab..2532f5233c 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -3332,50 +3332,60 @@ DEFUN ("set-file-acl", Fset_file_acl, Sset_file_a= cl, return Qnil; } =0C -DEFUN ("file-modes", Ffile_modes, Sfile_modes, 1, 1, 0, +static int +symlink_nofollow_flag (Lisp_Object flag) +{ + /* For now, treat all non-nil FLAGs like 'nofollow'. */ + return !NILP (flag) ? AT_SYMLINK_NOFOLLOW : 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 FLAG is `nofollow', +do not follow FILENAME if it is a symbolic link. */) + (Lisp_Object filename, Lisp_Object flag) { struct stat st; + int nofollow =3D symlink_nofollow_flag (flag); Lisp_Object absname =3D expand_and_dir_to_file (filename); =20 /* If the file name has special constructs in it, call the corresponding file name handler. */ Lisp_Object handler =3D Ffind_file_name_handler (absname, Qfile_modes)= ; if (!NILP (handler)) - return call2 (handler, Qfile_modes, absname); + return call3 (handler, Qfile_modes, absname, flag); =20 - if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (absname)), &st, 0) != =3D 0) + char *fname =3D SSDATA (ENCODE_FILE (absname)); + if (emacs_fstatat (AT_FDCWD, fname, &st, nofollow) !=3D 0) return file_attribute_errno (absname, errno); return make_fixnum (st.st_mode & 07777); } =20 -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 FLAG is `nofollow', +do not follow FILENAME if it is a symbolic link. =20 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 flag) { - Lisp_Object absname, encoded_absname; - Lisp_Object handler; - - absname =3D Fexpand_file_name (filename, BVAR (current_buffer, directo= ry)); CHECK_FIXNUM (mode); + int nofollow =3D symlink_nofollow_flag (flag); + Lisp_Object absname =3D Fexpand_file_name (filename, + BVAR (current_buffer, directory)); =20 /* If the file name has special constructs in it, call the corresponding file name handler. */ - handler =3D Ffind_file_name_handler (absname, Qset_file_modes); + Lisp_Object handler =3D Ffind_file_name_handler (absname, Qset_file_mo= des); if (!NILP (handler)) - return call3 (handler, Qset_file_modes, absname, mode); - - encoded_absname =3D ENCODE_FILE (absname); + return call4 (handler, Qset_file_modes, absname, mode, flag); =20 - if (chmod (SSDATA (encoded_absname), XFIXNUM (mode) & 07777) < 0) + char *fname =3D SSDATA (ENCODE_FILE (absname)); + mode_t imode =3D XFIXNUM (mode) & 07777; + if (fchmodat (AT_FDCWD, fname, imode, nofollow) !=3D 0) report_file_error ("Doing chmod", absname); =20 return Qnil; @@ -5740,7 +5750,7 @@ auto_save_1 (void) =3D=3D 0) /* But make sure we can overwrite it later! */ auto_save_mode_bits =3D (st.st_mode | 0600) & 0777; - else if (modes =3D Ffile_modes (BVAR (current_buffer, filename)), + else if (modes =3D Ffile_modes (BVAR (current_buffer, filename), Q= nil), FIXNUMP (modes)) /* Remote files don't cooperate with fstatat. */ auto_save_mode_bits =3D (XFIXNUM (modes) | 0600) & 0777; --=20 2.17.1 --------------DF02D43B886735289119FE5B-- ------------=_1582505462-25473-1 Content-Type: message/rfc822 Content-Disposition: inline Content-Transfer-Encoding: 7bit Received: (at submit) by debbugs.gnu.org; 20 Feb 2020 00:34:27 +0000 Received: from localhost ([127.0.0.1]:43601 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1j4ZnN-0002dC-VP for submit@debbugs.gnu.org; Wed, 19 Feb 2020 19:34:27 -0500 Received: from lists.gnu.org ([209.51.188.17]:39524) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1j4ZnL-0002d2-Bo for submit@debbugs.gnu.org; Wed, 19 Feb 2020 19:34:25 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:52544) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4ZnG-0003dc-Cz for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:34:23 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-1.5 required=5.0 tests=BAYES_50,RCVD_IN_DNSWL_MED, URIBL_BLOCKED autolearn=disabled version=3.3.2 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j4ZnA-00074u-Hh for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:34:17 -0500 Received: from zimbra.cs.ucla.edu ([131.179.128.68]:42006) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1j4Zn9-00073f-QM for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:34:12 -0500 Received: from localhost (localhost [127.0.0.1]) by zimbra.cs.ucla.edu (Postfix) with ESMTP id 90E001600AB for ; Wed, 19 Feb 2020 16:34:08 -0800 (PST) Received: from zimbra.cs.ucla.edu ([127.0.0.1]) by localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10032) with ESMTP id HpxGVqt6ILb8; Wed, 19 Feb 2020 16:34:04 -0800 (PST) Received: from localhost (localhost [127.0.0.1]) by zimbra.cs.ucla.edu (Postfix) with ESMTP id 8773D1600A4; Wed, 19 Feb 2020 16:34:04 -0800 (PST) X-Virus-Scanned: amavisd-new at zimbra.cs.ucla.edu Received: from zimbra.cs.ucla.edu ([127.0.0.1]) by localhost (zimbra.cs.ucla.edu [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id 0APMZINEpU7k; Wed, 19 Feb 2020 16:34:04 -0800 (PST) Received: from Penguin.CS.UCLA.EDU (Penguin.CS.UCLA.EDU [131.179.64.200]) by zimbra.cs.ucla.edu (Postfix) with ESMTPSA id 4D7421600A2; Wed, 19 Feb 2020 16:34:04 -0800 (PST) From: Paul Eggert To: bug-gnu-emacs@gnu.org Subject: [PATCH] Add NOFOLLOW flag to set-file-modes etc. Date: Wed, 19 Feb 2020 16:34:00 -0800 Message-Id: <20200220003400.265566-1-eggert@cs.ucla.edu> X-Mailer: git-send-email 2.24.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 131.179.128.68 X-Spam-Score: 0.3 (/) X-Debbugs-Envelope-To: submit Cc: Paul Eggert X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -0.7 (/) 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=E2=80=99s permissions, using th= e 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=3D crypto/md5-buffer crypto/sha1-buffer crypto/sha256-buffer crypto/sha51= 2-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 =20 -@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. =20 +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. =20 @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 =20 @group -(set-file-modes "~/junk/diffs" #o666) +(set-file-modes "~/junk/diffs" #o666 t) @result{} nil @end group =20 @@ -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}. =20 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, =20 @example -(set-file-modes #o644) +(set-file-modes "myfile" #o644 t) @end example =20 @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 =20 @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. =20 +** '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". =20 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 +#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 = . */ + +/* written by Jim Meyering and Paul Eggert */ + +/* If the user's config.h happens to include , let it includ= e only + the system's here, so that orig_fchmodat doesn't recurse= to + rpl_fchmodat. */ +#define __need_system_sys_stat_h +#include + +/* Specification. */ +#include +#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 +#include +#include +#include +#include + +#ifdef __osf__ +/* Write "sys/stat.h" here, not , otherwise OSF/1 5.1 DTK cc + eliminates this include because of the preliminary #include + above. */ +# include "sys/stat.h" +#else +# include +#endif + +#include + +/* 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 =3D=3D 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. = */ + int fd =3D 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 li= nk + - 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) !=3D 0) + { + int stat_errno =3D errno; + close (fd); + errno =3D stat_errno; + return -1; + } + if (S_ISLNK (st.st_mode)) + { + close (fd); + errno =3D EOPNOTSUPP; + return -1; + } + + static char const fmt[] =3D "/proc/self/fd/%d"; + char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; + sprintf (buf, fmt, fd); + int chmod_result =3D chmod (buf, mode); + int chmod_errno =3D errno; + close (fd); + if (chmod_result =3D=3D 0) + return chmod_result; + if (chmod_errno !=3D ENOENT) + { + errno =3D 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 =3D fstatat (dir, file, &st, AT_SYMLINK_NOFOLLO= W); + if (fstatat_result !=3D 0) + return fstatat_result; + if (S_ISLNK (st.st_mode)) + { + errno =3D 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 =3D @gl_GNULIB_ENABLED_dirf= d@ gl_GNULIB_ENABLED_euidaccess =3D @gl_GNULIB_ENABLED_euidaccess@ gl_GNULIB_ENABLED_getdtablesize =3D @gl_GNULIB_ENABLED_getdtablesize@ gl_GNULIB_ENABLED_getgroups =3D @gl_GNULIB_ENABLED_getgroups@ +gl_GNULIB_ENABLED_lchmod =3D @gl_GNULIB_ENABLED_lchmod@ gl_GNULIB_ENABLED_malloca =3D @gl_GNULIB_ENABLED_malloca@ gl_GNULIB_ENABLED_open =3D @gl_GNULIB_ENABLED_open@ gl_GNULIB_ENABLED_strtoll =3D @gl_GNULIB_ENABLED_strtoll@ @@ -1587,6 +1589,18 @@ EXTRA_libgnu_a_SOURCES +=3D at-func.c faccessat.c endif ## end gnulib module faccessat =20 +## begin gnulib module fchmodat +ifeq (,$(OMIT_GNULIB_MODULE_fchmodat)) + +libgnu_a_SOURCES +=3D chmodat.c + +EXTRA_DIST +=3D at-func.c fchmodat.c + +EXTRA_libgnu_a_SOURCES +=3D at-func.c fchmodat.c + +endif +## end gnulib module fchmodat + ## begin gnulib module fcntl ifeq (,$(OMIT_GNULIB_MODULE_fcntl)) =20 @@ -1937,6 +1951,19 @@ EXTRA_DIST +=3D inttypes.in.h endif ## end gnulib module inttypes-incomplete =20 +## begin gnulib module lchmod +ifeq (,$(OMIT_GNULIB_MODULE_lchmod)) + +ifneq (,$(gl_GNULIB_ENABLED_lchmod)) + +endif +EXTRA_DIST +=3D lchmod.c + +EXTRA_libgnu_a_SOURCES +=3D lchmod.c + +endif +## end gnulib module lchmod + ## begin gnulib module libc-config ifeq (,$(OMIT_GNULIB_MODULE_libc-config)) =20 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 = . */ + +/* written by Paul Eggert */ + +#include + +/* If the user's config.h happens to include , let it includ= e only + the system's here, so that orig_fchmodat doesn't recurse= to + rpl_fchmodat. */ +#define __need_system_sys_stat_h +#include + +/* Specification. */ +#include +#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 +#include +#include +#include + +#ifdef __osf__ +/* Write "sys/stat.h" here, not , otherwise OSF/1 5.1 DTK cc + eliminates this include because of the preliminary #include + above. */ +# include "sys/stat.h" +#else +# include +#endif + +#include + +/* Work like chmod, except when FILE is a symbolic link. + In that case, on systems where permissions on symbolic links are unsu= pported + (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. */ + int fd =3D 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) !=3D 0) + { + int stat_errno =3D errno; + close (fd); + errno =3D stat_errno; + return -1; + } + if (S_ISLNK (st.st_mode)) + { + close (fd); + errno =3D EOPNOTSUPP; + return -1; + } + + static char const fmt[] =3D "/proc/self/fd/%d"; + char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; + sprintf (buf, fmt, fd); + int chmod_result =3D chmod (buf, mode); + int chmod_errno =3D errno; + close (fd); + if (chmod_result =3D=3D 0) + return chmod_result; + if (chmod_errno !=3D ENOENT) + { + errno =3D 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 =3D lstat (file, &st); + if (lstat_result !=3D 0) + return lstat_result; + if (S_ISLNK (st.st_mode)) + { + errno =3D 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 Wi= ndows */ + 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))) =20 ;;;###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, t= hus ;; 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 (=3D 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 (=3D 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))))) =20 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))))) =20 (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))) =20 ;; 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)))))) =20 =0C 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 ".."))))))))) =20 -(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))) =20 (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))) =20 ;;; ;;; 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) =20 -(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))) =20 (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))) =20 (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 locknam= e)) (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)))) =20 -(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 local= name)))) 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)))) =20 -(defun tramp-gvfs-handle-set-file-modes (filename mode) +(defun tramp-gvfs-handle-set-file-modes (filename mode &optional nofollo= w) "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)))) =20 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))))))))) =20 -(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))))) =20 ;; 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)) =20 @@ -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)) =20 ;; 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"))))))) =20 -(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))))) =20 -(defun tramp-sudoedit-handle-set-file-modes (filename mode) +(defun tramp-sudoedit-handle-set-file-modes (filename mode &optional nof= ollow) "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)))))) =20 =0C ;; 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))) =20 -(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) filena= me)))) (tramp-mode-string-to-int (tramp-compat-file-attribute-modes attrs))= )) =20 @@ -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))) =20 ;; 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 =3D %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)" mod= es)))) (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)))) =20 (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 !=3D yes; then + HAVE_FCHMODAT=3D0 + 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 + #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 =3D S_IRWXU | S_IRWXG | S_IRWXO; + int desired =3D S_IRUSR | S_IWUSR; + static char const f[] =3D "conftest.fchmodat"; + struct stat st; + if (creat (f, permissive) < 0) + return 1; + if (fchmodat (AT_FDCWD, f, desired, AT_SYMLINK_NOFOLLOW) != =3D 0) + return 1; + if (stat (f, &st) !=3D 0) + return 1; + return ! ((st.st_mode & permissive) =3D=3D desired); + ]])], + [gl_cv_func_fchmodat_works=3Dyes], + [gl_cv_func_fchmodat_works=3Dno], + [case "$host_os" in + dnl Guess no on Linux with glibc and Cygwin, yes otherwise. + linux-gnu* | cygwin*) gl_cv_func_fchmodat_works=3D"guessing = no" ;; + *) gl_cv_func_fchmodat_works=3D"$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 rig= ht on non-symlinks.]) + REPLACE_FCHMODAT=3D1 + ;; + 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 =3D 0 || test $REPLACE_FCHMODAT =3D 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 =3D 0 || test $REPLACE_FCNTL =3D 1; then AC_LIBOBJ([fcntl]) @@ -463,6 +472,7 @@ AC_DEFUN gl_gnulib_enabled_getgroups=3Dfalse gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=3Dfalse gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1=3Dfalse + gl_gnulib_enabled_lchmod=3Dfalse gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467=3Dfalse gl_gnulib_enabled_2049e887c7e5308faad27b3f894bb8c9=3Dfalse gl_gnulib_enabled_malloca=3Dfalse @@ -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 =3D 0 || test $REPLACE_LCHMOD =3D 1; then + AC_LIBOBJ([lchmod]) + gl_PREREQ_LCHMOD + fi + gl_SYS_STAT_MODULE_INDICATOR([lchmod]) + gl_gnulib_enabled_lchmod=3Dtrue + fi + } func_gl_gnulib_m4code_21ee726a3540c09237a8e70c0baf7467 () { if ! $gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467; then @@ -655,6 +677,15 @@ AC_DEFUN if test $HAVE_FACCESSAT =3D 0 || test $REPLACE_FACCESSAT =3D 1; then func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b + fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_lchmod + fi + if test $HAVE_FCHMODAT =3D 0; then + func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 + fi if test $HAVE_FCNTL =3D 0 || test $REPLACE_FCNTL =3D 1; then func_gl_gnulib_m4code_getdtablesize fi @@ -703,6 +734,7 @@ AC_DEFUN AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getg= roups]) 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_malloc= a]) @@ -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 versio= n. + +AC_DEFUN([gl_FUNC_LCHMOD], +[ + AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS]) + + dnl Persuade glibc 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" =3D no; then + HAVE_LCHMOD=3D0 + 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 =3D S_IRWXU | S_IRWXG | S_IRWXO; + int desired =3D S_IRUSR | S_IWUSR; + static char const f[] =3D "conftest.lchmod"; + struct stat st; + if (creat (f, permissive) < 0) + return 1; + if (lchmod (f, desired) !=3D 0) + return 1; + if (stat (f, &st) !=3D 0) + return 1; + return ! ((st.st_mode & permissive) =3D=3D desired); + ]])], + [gl_cv_func_lchmod_works=3Dyes], + [gl_cv_func_lchmod_works=3Dno], + [case "$host_os" in + dnl Guess no on Linux with glibc, yes otherwise. + linux-gnu*) gl_cv_func_lchmod_works=3D"guessing no" ;; + *) gl_cv_func_lchmod_works=3D"$gl_cross_guess_norma= l" ;; + 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=3D1 + ;; + 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_a= cl, return Qnil; } =0C -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 =3D 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 =3D Ffind_file_name_handler (absname, Qfile_modes)= ; if (!NILP (handler)) - return call2 (handler, Qfile_modes, absname); + return call3 (handler, Qfile_modes, absname, nofollow); =20 - if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (absname)), &st, 0) != =3D 0) + char *fname =3D SSDATA (ENCODE_FILE (absname)); + int flags =3D !NILP (nofollow) ? AT_SYMLINK_NOFOLLOW : 0; + if (emacs_fstatat (AT_FDCWD, fname, &st, flags) !=3D 0) return file_attribute_errno (absname, errno); return make_fixnum (st.st_mode & 07777); } =20 -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. =20 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 =3D Fexpand_file_name (filename, BVAR (current_buffer, directo= ry)); CHECK_FIXNUM (mode); + Lisp_Object absname =3D Fexpand_file_name (filename, + BVAR (current_buffer, directory)); =20 /* If the file name has special constructs in it, call the corresponding file name handler. */ - handler =3D Ffind_file_name_handler (absname, Qset_file_modes); + Lisp_Object handler =3D Ffind_file_name_handler (absname, Qset_file_mo= des); if (!NILP (handler)) - return call3 (handler, Qset_file_modes, absname, mode); - - encoded_absname =3D ENCODE_FILE (absname); + return call4 (handler, Qset_file_modes, absname, mode, nofollow); =20 - if (chmod (SSDATA (encoded_absname), XFIXNUM (mode) & 07777) < 0) + char *fname =3D SSDATA (ENCODE_FILE (absname)); + mode_t imode =3D XFIXNUM (mode) & 07777; + int flags =3D !NILP (nofollow) ? AT_SYMLINK_NOFOLLOW : 0; + if (fchmodat (AT_FDCWD, fname, imode, flags) < 0) report_file_error ("Doing chmod", absname); =20 return Qnil; @@ -5740,7 +5743,7 @@ auto_save_1 (void) =3D=3D 0) /* But make sure we can overwrite it later! */ auto_save_mode_bits =3D (st.st_mode | 0600) & 0777; - else if (modes =3D Ffile_modes (BVAR (current_buffer, filename)), + else if (modes =3D Ffile_modes (BVAR (current_buffer, filename), Q= nil), FIXNUMP (modes)) /* Remote files don't cooperate with fstatat. */ auto_save_mode_bits =3D (XFIXNUM (modes) | 0600) & 0777; --=20 2.24.1 ------------=_1582505462-25473-1--