From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.ciao.gmane.io!not-for-mail From: Paul Eggert Newsgroups: gmane.emacs.bugs Subject: bug#39683: [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> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Injection-Info: ciao.gmane.io; posting-host="ciao.gmane.io:159.69.161.202"; logging-data="110179"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Paul Eggert To: 39683@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Thu Feb 20 01:35:24 2020 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1j4ZoJ-000STr-UI for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 20 Feb 2020 01:35:24 +0100 Original-Received: from localhost ([::1]:34308 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4ZoH-0003iW-N7 for geb-bug-gnu-emacs@m.gmane-mx.org; Wed, 19 Feb 2020 19:35:22 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:52612) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1j4Zo3-0003iA-67 for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:35:12 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1j4Zny-0007ju-CD for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:35:07 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:37628) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1j4Zny-0007jq-70 for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:35:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1j4Zny-0002e8-3H for bug-gnu-emacs@gnu.org; Wed, 19 Feb 2020 19:35:02 -0500 X-Loop: help-debbugs@gnu.org 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 X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.158215886710123 (code B ref -1); Thu, 20 Feb 2020 00:35:01 +0000 Original-Received: (at submit) by debbugs.gnu.org; 20 Feb 2020 00:34:27 +0000 Original-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 Original-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 Original-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 Original-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 Original-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 Original-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) Original-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) Original-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 Original-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) Original-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) X-Mailer: git-send-email 2.24.1 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.51.188.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:176266 Archived-At: 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