1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-11-22 07:09:54 +00:00

Add 'nofollow' flag to set-file-modes etc.

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’s permissions, using the
new flag prevents Emacs from inadvertently changing the
permissions of a victim in some completely unrelated directory.
* admin/merge-gnulib (GNULIB_MODULES): Add fchmodat.
* 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.
This commit is contained in:
Paul Eggert 2020-02-23 16:19:42 -08:00
parent c4ca8219dd
commit 9d626dffc6
30 changed files with 533 additions and 64 deletions

View File

@ -33,7 +33,7 @@ GNULIB_MODULES='
crypto/md5-buffer crypto/sha1-buffer crypto/sha256-buffer crypto/sha512-buffer
d-type diffseq dosname double-slash-root dtoastr dtotimespec dup2
environ execinfo explicit_bzero faccessat
fcntl fcntl-h fdopendir
fchmodat fcntl fcntl-h fdopendir
filemode filevercmp flexmember fpieee fstatat fsusage fsync
getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog
ieee754-h ignore-value intprops largefile lstat

View File

@ -928,7 +928,7 @@ also checks that the file's group would be unchanged.
This function does not follow symbolic links.
@end defun
@defun file-modes filename
@defun file-modes filename &optional flag
@cindex mode bits
@cindex file permissions
@cindex permissions, file
@ -946,12 +946,19 @@ The highest possible value is 4095 (7777 octal), meaning that everyone
has read, write, and execute permission, the @acronym{SUID} bit is set
for both others and group, and the sticky bit is set.
By default this function follows symbolic links. However, if the
optional argument @var{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.
@example
@group
(file-modes "~/junk/diffs")
(file-modes "~/junk/diffs" 'nofollow)
@result{} 492 ; @r{Decimal integer.}
@end group
@group
@ -960,7 +967,7 @@ can be used to set these permissions.
@end group
@group
(set-file-modes "~/junk/diffs" #o666)
(set-file-modes "~/junk/diffs" #o666 'nofollow)
@result{} nil
@end group
@ -1801,9 +1808,17 @@ See also @code{delete-directory} in @ref{Create/Delete Dirs}.
@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}.
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 @@ lowest 9 bits are meaningful. You can use the Lisp construct for
octal numbers to enter @var{mode}. For example,
@example
(set-file-modes #o644)
(set-file-modes "myfile" #o644 'nofollow)
@end example
@noindent

View File

@ -3127,7 +3127,7 @@ being reported. For example:
@end group
@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

View File

@ -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.
** '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".

144
lib/fchmodat.c Normal file
View File

@ -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 <https://www.gnu.org/licenses/>. */
/* written by Jim Meyering and Paul Eggert */
/* If the user's config.h happens to include <sys/stat.h>, let it include only
the system's <sys/stat.h> here, so that orig_fchmodat doesn't recurse to
rpl_fchmodat. */
#define __need_system_sys_stat_h
#include <config.h>
/* Specification. */
#include <sys/stat.h>
#undef __need_system_sys_stat_h
#if HAVE_FCHMODAT
static int
orig_fchmodat (int dir, char const *file, mode_t mode, int flags)
{
return fchmodat (dir, file, mode, flags);
}
#endif
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef __osf__
/* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
eliminates this include because of the preliminary #include <sys/stat.h>
above. */
# include "sys/stat.h"
#else
# include <sys/stat.h>
#endif
#include <intprops.h>
/* Invoke chmod or lchmod on FILE, using mode MODE, in the directory
open on descriptor FD. If possible, do it without changing the
working directory. Otherwise, resort to using save_cwd/fchdir,
then (chmod|lchmod)/restore_cwd. If either the save_cwd or the
restore_cwd fails, then give a diagnostic and exit nonzero.
Note that an attempt to use a FLAG value of AT_SYMLINK_NOFOLLOW
on a system without lchmod support causes this function to fail. */
#if HAVE_FCHMODAT
int
fchmodat (int dir, char const *file, mode_t mode, int flags)
{
# if NEED_FCHMODAT_NONSYMLINK_FIX
if (flags == AT_SYMLINK_NOFOLLOW)
{
struct stat st;
# if defined O_PATH && defined AT_EMPTY_PATH
/* Open a file descriptor with O_NOFOLLOW, to make sure we don't
follow symbolic links, if /proc is mounted. O_PATH is used to
avoid a failure if the file is not readable.
Cf. <https://sourceware.org/bugzilla/show_bug.cgi?id=14578> */
int fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0)
return fd;
/* Up to Linux 5.3 at least, when FILE refers to a symbolic link, the
chmod call below will change the permissions of the symbolic link
- which is 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. */
if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0)
{
int stat_errno = errno;
close (fd);
errno = stat_errno;
return -1;
}
if (S_ISLNK (st.st_mode))
{
close (fd);
errno = EOPNOTSUPP;
return -1;
}
# if defined __linux__ || defined __ANDROID__
static char const fmt[] = "/proc/self/fd/%d";
char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
sprintf (buf, fmt, fd);
int chmod_result = chmod (buf, mode);
int chmod_errno = errno;
close (fd);
if (chmod_result == 0)
return chmod_result;
if (chmod_errno != ENOENT)
{
errno = chmod_errno;
return chmod_result;
}
# endif
/* /proc is not mounted or would not work as in GNU/Linux. */
# else
int fstatat_result = fstatat (dir, file, &st, AT_SYMLINK_NOFOLLOW);
if (fstatat_result != 0)
return fstatat_result;
if (S_ISLNK (st.st_mode))
{
errno = EOPNOTSUPP;
return -1;
}
# endif
/* Fall back on orig_fchmodat with no flags, despite a possible race. */
flags = 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

View File

@ -95,6 +95,7 @@
# execinfo \
# explicit_bzero \
# faccessat \
# fchmodat \
# fcntl \
# fcntl-h \
# fdopendir \
@ -1082,6 +1083,7 @@ gl_GNULIB_ENABLED_dirfd = @gl_GNULIB_ENABLED_dirfd@
gl_GNULIB_ENABLED_euidaccess = @gl_GNULIB_ENABLED_euidaccess@
gl_GNULIB_ENABLED_getdtablesize = @gl_GNULIB_ENABLED_getdtablesize@
gl_GNULIB_ENABLED_getgroups = @gl_GNULIB_ENABLED_getgroups@
gl_GNULIB_ENABLED_lchmod = @gl_GNULIB_ENABLED_lchmod@
gl_GNULIB_ENABLED_malloca = @gl_GNULIB_ENABLED_malloca@
gl_GNULIB_ENABLED_open = @gl_GNULIB_ENABLED_open@
gl_GNULIB_ENABLED_strtoll = @gl_GNULIB_ENABLED_strtoll@
@ -1586,6 +1588,17 @@ EXTRA_libgnu_a_SOURCES += at-func.c faccessat.c
endif
## end gnulib module faccessat
## begin gnulib module fchmodat
ifeq (,$(OMIT_GNULIB_MODULE_fchmodat))
EXTRA_DIST += at-func.c fchmodat.c
EXTRA_libgnu_a_SOURCES += at-func.c fchmodat.c
endif
## end gnulib module fchmodat
## begin gnulib module fcntl
ifeq (,$(OMIT_GNULIB_MODULE_fcntl))
@ -1936,6 +1949,19 @@ EXTRA_DIST += inttypes.in.h
endif
## end gnulib module inttypes-incomplete
## begin gnulib module lchmod
ifeq (,$(OMIT_GNULIB_MODULE_lchmod))
ifneq (,$(gl_GNULIB_ENABLED_lchmod))
endif
EXTRA_DIST += lchmod.c
EXTRA_libgnu_a_SOURCES += lchmod.c
endif
## end gnulib module lchmod
## begin gnulib module libc-config
ifeq (,$(OMIT_GNULIB_MODULE_libc-config))

110
lib/lchmod.c Normal file
View File

@ -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 <https://www.gnu.org/licenses/>. */
/* written by Paul Eggert */
#include <config.h>
/* Specification. */
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#ifdef __osf__
/* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
eliminates this include because of the preliminary #include <sys/stat.h>
above. */
# include "sys/stat.h"
#else
# include <sys/stat.h>
#endif
#include <intprops.h>
/* Work like chmod, except when FILE is a symbolic link.
In that case, on systems where permissions on symbolic links are unsupported
(such as Linux), set errno to EOPNOTSUPP and return -1. */
int
lchmod (char const *file, mode_t mode)
{
#if 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. <https://sourceware.org/bugzilla/show_bug.cgi?id=14578> */
int fd = 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) != 0)
{
int stat_errno = errno;
close (fd);
errno = stat_errno;
return -1;
}
if (S_ISLNK (st.st_mode))
{
close (fd);
errno = EOPNOTSUPP;
return -1;
}
# if defined __linux__ || defined __ANDROID__
static char const fmt[] = "/proc/self/fd/%d";
char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
sprintf (buf, fmt, fd);
int chmod_result = chmod (buf, mode);
int chmod_errno = errno;
close (fd);
if (chmod_result == 0)
return chmod_result;
if (chmod_errno != ENOENT)
{
errno = chmod_errno;
return chmod_result;
}
# endif
/* /proc is not mounted or would not work as in GNU/Linux. */
#elif HAVE_LSTAT
struct stat st;
int lstat_result = lstat (file, &st);
if (lstat_result != 0)
return lstat_result;
if (S_ISLNK (st.st_mode))
{
errno = EOPNOTSUPP;
return -1;
}
#endif
/* Fall back on chmod, despite a possible race. */
return chmod (file, mode);
}

View File

@ -409,7 +409,8 @@ has no effect on MS-Windows."
(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)))
;;;###autoload

View File

@ -683,8 +683,6 @@ at the top edge of the page moves to the previous page."
;; 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 @@ at the top edge of the page moves to the previous page."
;; sure we have write-access to the directory and that we own it, thus
;; closing a bunch of security holes.
(condition-case error
(set-file-modes dir #o0700)
(set-file-modes dir #o0700 'nofollow)
(file-error
(error
(format "Unable to use temporary directory %s: %s"

View File

@ -895,7 +895,7 @@ FILE's modification time."
(cons (lambda () (ignore-errors (delete-file tempfile)))
kill-emacs-hook)))
(unless (= temp-modes desired-modes)
(set-file-modes tempfile desired-modes))
(set-file-modes tempfile desired-modes 'nofollow))
(write-region (point-min) (point-max) tempfile nil 1)
(backup-buffer)
(rename-file tempfile buffer-file-name t))

View File

@ -2008,7 +2008,7 @@ The value is non-nil if there were no errors, nil if errors."
(delete-file tempfile)))
kill-emacs-hook)))
(unless (= 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

View File

@ -478,7 +478,7 @@ that `ls -l' will show in the first column of its display."
(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)))))

View File

@ -4672,6 +4672,7 @@ BACKUPNAME is the backup file name, which is the old file renamed."
;; 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 @@ BACKUPNAME is the backup file name, which is the old file renamed."
(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 @@ BACKUPNAME is the backup file name, which is the old file renamed."
(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)))))
(defvar file-name-version-regexp
"\\(?:~\\|\\.~[-[:alnum:]:#@^._]+\\(?:~[[:digit:]]+\\)?~\\)"
@ -5900,7 +5902,8 @@ into NEWNAME instead."
;; 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 @@ into NEWNAME instead."
(or parents (not (file-directory-p newname)))
(setq newname (concat newname
(file-name-nondirectory directory))))
(make-directory (directory-file-name newname) parents)))
(make-directory (directory-file-name newname) parents))
(t (setq follow t)))
;; Copy recursively.
(dolist (file
@ -5941,7 +5945,7 @@ into NEWNAME instead."
(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))))))

View File

@ -1601,10 +1601,10 @@ empty directories from OLD-PATH."
(file-truename
(concat old-dir "..")))))))))
(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)))
(defun gnus-rescale-image (image size)
"Rescale IMAGE to SIZE if possible.

View File

@ -695,7 +695,7 @@ Deleting old (> %s day(s)) incoming mail file `%s'." diff bfile)
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)

View File

@ -948,7 +948,7 @@ external if displayed 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

View File

@ -1958,7 +1958,7 @@ If TIME is nil, then return the cutoff time for oldness instead."
(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)))
;;;
;;; Status functions

View File

@ -4740,7 +4740,8 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
(setq ange-ftp-ls-cache-file nil) ;Stop confusing Dired.
0)
(defun ange-ftp-set-file-modes (filename mode)
(defun ange-ftp-set-file-modes (filename mode &optional flag)
flag ;; FIXME: Support 'nofollow'.
(ange-ftp-call-chmod (list (format "%o" mode) filename)))
(defun ange-ftp-make-symbolic-link (&rest _arguments)

View File

@ -591,7 +591,8 @@ Emacs dired can't find files."
(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)))
(defun tramp-adb-handle-file-writable-p (filename)
@ -636,7 +637,8 @@ But handle the case, if the \"test\" command is not available."
(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 lockname))
(with-tramp-progress-reporter
@ -665,8 +667,9 @@ But handle the case, if the \"test\" command is not available."
(tramp-message v 0 "Wrote %s" filename))
(run-hooks 'tramp-handle-write-region-hook))))
(defun tramp-adb-handle-set-file-modes (filename mode)
(defun tramp-adb-handle-set-file-modes (filename mode &optional 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 localname))))

View File

@ -1562,12 +1562,12 @@ If FILE-SYSTEM is non-nil, return file system attributes."
(tramp-run-real-handler
#'rename-file (list filename newname ok-if-already-exists))))
(defun tramp-gvfs-handle-set-file-modes (filename mode)
(defun tramp-gvfs-handle-set-file-modes (filename mode &optional 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))))

View File

@ -1478,10 +1478,11 @@ of."
;; only if that agrees with the buffer's record.
(t (tramp-compat-time-equal-p mt tramp-time-doesnt-exist)))))))))
(defun tramp-sh-handle-set-file-modes (filename mode)
(defun tramp-sh-handle-set-file-modes (filename mode &optional 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 @@ the uid and gid from FILENAME."
;; 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 @@ STDERR can also be a file name."
(delete-file tmpfile2)))))
;; 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))
@ -3320,7 +3322,7 @@ STDERR can also be a file name."
;; 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))
;; This is a bit lengthy due to the different methods
;; possible for file transfer. First, we check whether the

View File

@ -1464,8 +1464,9 @@ component is used as the target of the symlink."
(tramp-flush-connection-property v "process-name")
(tramp-flush-connection-property v "process-buffer")))))))
(defun tramp-smb-handle-set-file-modes (filename mode)
(defun tramp-smb-handle-set-file-modes (filename mode &optional 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)

View File

@ -463,8 +463,9 @@ the result will be a local, non-Tramp, file name."
(tramp-sudoedit-send-command
v "test" "-r" (tramp-compat-file-name-unquote localname)))))
(defun tramp-sudoedit-handle-set-file-modes (filename mode)
(defun tramp-sudoedit-handle-set-file-modes (filename mode &optional 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)
(unless (tramp-sudoedit-send-command
@ -735,7 +736,8 @@ ID-FORMAT valid values are `string' and `integer'."
(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))))))
;; Internal functions.

View File

@ -3179,10 +3179,13 @@ User is always nil."
(copy-file filename tmpfile 'ok-if-already-exists 'keep-time)
tmpfile)))
(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) filename))))
(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)))))
;; Localname manipulation functions that grok Tramp localnames...
(defun tramp-handle-file-name-as-directory (file)
@ -3884,7 +3887,7 @@ of."
;; 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 @@ Return the local name of the temporary 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)))
;; Return the local part.
(tramp-file-local-name result)))

View File

@ -563,7 +563,7 @@ See variable `server-auth-dir' for details."
(format "it is not owned by you (owner = %s (%d))"
(user-full-name uid) uid))
(w32 nil) ; on NTFS?
((let ((modes (file-modes dir)))
((let ((modes (file-modes dir 'nofollow)))
(unless (zerop (logand (or modes 0) #o077))
(format "it is accessible by others (%03o)" modes))))
(t nil))))

View File

@ -615,9 +615,7 @@ Creates FILE and its parent directories if they do not exist."
(with-temp-buffer
(write-region (point-min) (point-max) file nil 'silent nil 'excl)))
(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))))
(autoload 'puny-encode-domain "puny")
(autoload 'url-domsuf-cookie-allowed-p "url-domsuf")

82
m4/fchmodat.m4 Normal file
View File

@ -0,0 +1,82 @@
# fchmodat.m4 serial 4
dnl Copyright (C) 2004-2020 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
# Written by Jim Meyering.
AC_DEFUN([gl_FUNC_FCHMODAT],
[
AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
AC_CHECK_FUNCS_ONCE([fchmodat lchmod])
if test $ac_cv_func_fchmodat != yes; then
HAVE_FCHMODAT=0
else
AC_CACHE_CHECK(
[whether fchmodat+AT_SYMLINK_NOFOLLOW works on non-symlinks],
[gl_cv_func_fchmodat_works],
[dnl This test fails on GNU/Linux with glibc 2.31 (but not on
dnl GNU/kFreeBSD nor GNU/Hurd) and Cygwin 2.9.
AC_RUN_IFELSE(
[AC_LANG_PROGRAM(
[
AC_INCLUDES_DEFAULT[
#include <fcntl.h>
#ifndef S_IRUSR
#define S_IRUSR 0400
#endif
#ifndef S_IWUSR
#define S_IWUSR 0200
#endif
#ifndef S_IRWXU
#define S_IRWXU 0700
#endif
#ifndef S_IRWXG
#define S_IRWXG 0070
#endif
#ifndef S_IRWXO
#define S_IRWXO 0007
#endif
]],
[[
int permissive = S_IRWXU | S_IRWXG | S_IRWXO;
int desired = S_IRUSR | S_IWUSR;
static char const f[] = "conftest.fchmodat";
struct stat st;
if (creat (f, permissive) < 0)
return 1;
if (fchmodat (AT_FDCWD, f, desired, AT_SYMLINK_NOFOLLOW) != 0)
return 1;
if (stat (f, &st) != 0)
return 1;
return ! ((st.st_mode & permissive) == desired);
]])],
[gl_cv_func_fchmodat_works=yes],
[gl_cv_func_fchmodat_works=no],
[case "$host_os" in
dnl Guess no on Linux with glibc and Cygwin, yes otherwise.
linux-gnu* | cygwin*) gl_cv_func_fchmodat_works="guessing no" ;;
*) gl_cv_func_fchmodat_works="$gl_cross_guess_normal" ;;
esac
])
rm -f conftest.fchmodat])
case $gl_cv_func_fchmodat_works in
*yes) ;;
*)
AC_DEFINE([NEED_FCHMODAT_NONSYMLINK_FIX], [1],
[Define to 1 if fchmodat+AT_SYMLINK_NOFOLLOW does not work right on non-symlinks.])
REPLACE_FCHMODAT=1
;;
esac
fi
])
# Prerequisites of lib/fchmodat.c.
AC_DEFUN([gl_PREREQ_FCHMODAT],
[
AC_CHECK_FUNCS_ONCE([lchmod])
:
])

View File

@ -82,6 +82,7 @@ AC_DEFUN([gl_EARLY],
# 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([gl_EARLY],
# 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([gl_INIT],
fi
gl_MODULE_INDICATOR([faccessat])
gl_UNISTD_MODULE_INDICATOR([faccessat])
gl_FUNC_FCHMODAT
if test $HAVE_FCHMODAT = 0 || test $REPLACE_FCHMODAT = 1; then
AC_LIBOBJ([fchmodat])
gl_PREREQ_FCHMODAT
fi
gl_SYS_STAT_MODULE_INDICATOR([fchmodat])
gl_FUNC_FCNTL
if test $HAVE_FCNTL = 0 || test $REPLACE_FCNTL = 1; then
AC_LIBOBJ([fcntl])
@ -468,6 +476,7 @@ AC_DEFUN([gl_INIT],
gl_gnulib_enabled_getgroups=false
gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=false
gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1=false
gl_gnulib_enabled_lchmod=false
gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467=false
gl_gnulib_enabled_2049e887c7e5308faad27b3f894bb8c9=false
gl_gnulib_enabled_malloca=false
@ -569,6 +578,18 @@ AC_DEFUN([gl_INIT],
fi
fi
}
func_gl_gnulib_m4code_lchmod ()
{
if ! $gl_gnulib_enabled_lchmod; then
gl_FUNC_LCHMOD
if test $HAVE_LCHMOD = 0; then
AC_LIBOBJ([lchmod])
gl_PREREQ_LCHMOD
fi
gl_SYS_STAT_MODULE_INDICATOR([lchmod])
gl_gnulib_enabled_lchmod=true
fi
}
func_gl_gnulib_m4code_21ee726a3540c09237a8e70c0baf7467 ()
{
if ! $gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467; then
@ -660,6 +681,15 @@ AC_DEFUN([gl_INIT],
if test $HAVE_FACCESSAT = 0 || test $REPLACE_FACCESSAT = 1; then
func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7
fi
if test $HAVE_FCHMODAT = 0; then
func_gl_gnulib_m4code_260941c0e5dc67ec9e87d1fb321c300b
fi
if test $HAVE_FCHMODAT = 0; then
func_gl_gnulib_m4code_lchmod
fi
if test $HAVE_FCHMODAT = 0; then
func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7
fi
if test $HAVE_FCNTL = 0 || test $REPLACE_FCNTL = 1; then
func_gl_gnulib_m4code_getdtablesize
fi
@ -708,6 +738,7 @@ AC_DEFUN([gl_INIT],
AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getgroups])
AM_CONDITIONAL([gl_GNULIB_ENABLED_be453cec5eecf5731a274f2de7f2db36], [$gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36])
AM_CONDITIONAL([gl_GNULIB_ENABLED_a9786850e999ae65a836a6041e8e5ed1], [$gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1])
AM_CONDITIONAL([gl_GNULIB_ENABLED_lchmod], [$gl_gnulib_enabled_lchmod])
AM_CONDITIONAL([gl_GNULIB_ENABLED_21ee726a3540c09237a8e70c0baf7467], [$gl_gnulib_enabled_21ee726a3540c09237a8e70c0baf7467])
AM_CONDITIONAL([gl_GNULIB_ENABLED_2049e887c7e5308faad27b3f894bb8c9], [$gl_gnulib_enabled_2049e887c7e5308faad27b3f894bb8c9])
AM_CONDITIONAL([gl_GNULIB_ENABLED_malloca], [$gl_gnulib_enabled_malloca])
@ -908,6 +939,7 @@ AC_DEFUN([gl_FILE_LIST], [
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([gl_FILE_LIST], [
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([gl_FILE_LIST], [
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([gl_FILE_LIST], [
m4/include_next.m4
m4/inttypes.m4
m4/largefile.m4
m4/lchmod.m4
m4/limits-h.m4
m4/localtime-buffer.m4
m4/lstat.m4

31
m4/lchmod.m4 Normal file
View File

@ -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 version.
AC_DEFUN([gl_FUNC_LCHMOD],
[
AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
dnl Persuade glibc <sys/stat.h> to declare lchmod().
AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
AC_CHECK_FUNCS_ONCE([lchmod lstat])
if test "$ac_cv_func_lchmod" = no; then
HAVE_LCHMOD=0
fi
])
# Prerequisites of lib/lchmod.c.
AC_DEFUN([gl_PREREQ_LCHMOD],
[
AC_REQUIRE([AC_C_INLINE])
:
])

View File

@ -3332,50 +3332,60 @@ support. */)
return Qnil;
}
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 = symlink_nofollow_flag (flag);
Lisp_Object absname = expand_and_dir_to_file (filename);
/* If the file name has special constructs in it,
call the corresponding file name handler. */
Lisp_Object handler = Ffind_file_name_handler (absname, Qfile_modes);
if (!NILP (handler))
return call2 (handler, Qfile_modes, absname);
return call3 (handler, Qfile_modes, absname, flag);
if (emacs_fstatat (AT_FDCWD, SSDATA (ENCODE_FILE (absname)), &st, 0) != 0)
char *fname = SSDATA (ENCODE_FILE (absname));
if (emacs_fstatat (AT_FDCWD, fname, &st, nofollow) != 0)
return file_attribute_errno (absname, errno);
return make_fixnum (st.st_mode & 07777);
}
DEFUN ("set-file-modes", Fset_file_modes, Sset_file_modes, 2, 2,
DEFUN ("set-file-modes", Fset_file_modes, Sset_file_modes, 2, 3,
"(let ((file (read-file-name \"File: \"))) \
(list file (read-file-modes nil file)))",
doc: /* Set mode bits of file named FILENAME to MODE (an integer).
Only the 12 low bits of MODE are used.
Only the 12 low bits of MODE are used. If optional FLAG is `nofollow',
do not follow FILENAME if it is a symbolic link.
Interactively, mode bits are read by `read-file-modes', which accepts
symbolic notation, like the `chmod' command from GNU Coreutils. */)
(Lisp_Object filename, Lisp_Object mode)
(Lisp_Object filename, Lisp_Object mode, Lisp_Object flag)
{
Lisp_Object absname, encoded_absname;
Lisp_Object handler;
absname = Fexpand_file_name (filename, BVAR (current_buffer, directory));
CHECK_FIXNUM (mode);
int nofollow = symlink_nofollow_flag (flag);
Lisp_Object absname = Fexpand_file_name (filename,
BVAR (current_buffer, directory));
/* If the file name has special constructs in it,
call the corresponding file name handler. */
handler = Ffind_file_name_handler (absname, Qset_file_modes);
Lisp_Object handler = Ffind_file_name_handler (absname, Qset_file_modes);
if (!NILP (handler))
return call3 (handler, Qset_file_modes, absname, mode);
return call4 (handler, Qset_file_modes, absname, mode, flag);
encoded_absname = ENCODE_FILE (absname);
if (chmod (SSDATA (encoded_absname), XFIXNUM (mode) & 07777) < 0)
char *fname = SSDATA (ENCODE_FILE (absname));
mode_t imode = XFIXNUM (mode) & 07777;
if (fchmodat (AT_FDCWD, fname, imode, nofollow) != 0)
report_file_error ("Doing chmod", absname);
return Qnil;
@ -5740,7 +5750,7 @@ auto_save_1 (void)
== 0)
/* But make sure we can overwrite it later! */
auto_save_mode_bits = (st.st_mode | 0600) & 0777;
else if (modes = Ffile_modes (BVAR (current_buffer, filename)),
else if (modes = Ffile_modes (BVAR (current_buffer, filename), Qnil),
FIXNUMP (modes))
/* Remote files don't cooperate with fstatat. */
auto_save_mode_bits = (XFIXNUM (modes) | 0600) & 0777;