(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)
- 'nofollow))
+ (set-file-modes
+ tmpfile
+ (logior (or (tramp-compat-file-modes filename 'nofollow) 0) #o0400)))
tmpfile)))
(defun tramp-adb-handle-file-writable-p (filename)
(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)
- 'nofollow))
+ (set-file-modes tmpfile (logior (or (file-modes tmpfile) 0) #o0600)))
(tramp-run-real-handler
#'write-region (list start end tmpfile append 'no-message lockname))
(with-tramp-progress-reporter
(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
+ (when (and (eq flag 'nofollow) (file-symlink-p filename))
+ (tramp-error v 'file-error "Cannot chmod %s with %s flag" filename flag))
(tramp-flush-file-properties v localname)
(tramp-adb-send-command-and-check v (format "chmod %o %s" mode localname))))
"The error symbol for the `file-missing' error.")
;; `file-local-name', `file-name-quoted-p', `file-name-quote' and
-;; `file-name-unquote' are introduced in Emacs 26.
+;; `file-name-unquote' are introduced in Emacs 26.1.
(defalias 'tramp-compat-file-local-name
(if (fboundp 'file-local-name)
#'file-local-name
`process-file', `start-file-process', or `shell-command'."
(or (file-remote-p name 'localname) name))))
-;; `file-name-quoted-p' got a second argument in Emacs 27.1.
+;; `file-name-quoted-p', `file-name-quote' and `file-name-unquote' got
+;; a second argument in Emacs 27.1.
(defalias 'tramp-compat-file-name-quoted-p
(if (and
(fboundp 'file-name-quoted-p)
localname (if (= (length localname) 2) "/" (substring localname 2))))
(concat (file-remote-p name) localname)))))
-;; `tramp-syntax' has changed its meaning in Emacs 26. We still
+;; `tramp-syntax' has changed its meaning in Emacs 26.1. We still
;; support old settings.
(defsubst tramp-compat-tramp-syntax ()
"Return proper value of `tramp-syntax'."
(lambda (reporter &optional value _suffix)
(progress-reporter-update reporter value))))
+;; `file-modes' and `set-file-modes' got argument FLAG in Emacs 28.1.
+(defalias 'tramp-compat-file-modes
+ (if (equal (tramp-compat-funcall 'func-arity #'file-modes) '(1 . 2))
+ #'file-modes
+ (lambda (filename &optional _flag)
+ (file-modes filename))))
+
+(defalias 'tramp-compat-set-file-modes
+ (if (equal (tramp-compat-funcall 'func-arity #'set-file-modes) '(2 . 3))
+ #'set-file-modes
+ (lambda (filename mode &optional _flag)
+ (set-file-modes filename mode))))
+
(add-hook 'tramp-unload-hook
(lambda ()
(unload-feature 'tramp-loaddefs 'force)
;;; TODO:
;;
+;; * `func-arity' exists since Emacs 26.1.
+;;
;; * Starting with Emacs 27.1, there's no need to escape open
;; parentheses with a backslash in docstrings anymore.
(with-parsed-tramp-file-name filename nil
(tramp-flush-file-properties v localname)
(tramp-gvfs-send-command
- v "gvfs-set-attribute" (if flag "-nt" "-t") "uint32"
+ v "gvfs-set-attribute" (if (eq flag 'nofollow) "-nt" "-t") "uint32"
(tramp-gvfs-url-file-name (tramp-make-tramp-file-name v))
"unix::mode" (number-to-string 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
- (format "chmod %o %s" mode (tramp-shell-quote-argument localname))
- "Error while changing file's mode %s" filename)))
+ (let ((chmod "chmod"))
+ (when (and (eq flag 'nofollow) (file-symlink-p filename))
+ (or (setq chmod (tramp-get-remote-chmod-h v))
+ (tramp-error
+ v 'file-error "Cannot chmod %s with %s flag" filename flag)))
+ (tramp-flush-file-properties v localname)
+ ;; FIXME: extract the proper text from chmod's stderr.
+ (tramp-barf-unless-okay
+ v
+ (format "%s %o %s" chmod mode (tramp-shell-quote-argument localname))
+ "Error while changing file's mode %s" filename))))
(defun tramp-sh-handle-set-file-times (filename &optional time)
"Like `set-file-times' for Tramp files."
;; We must change the ownership as local user.
;; Since this does not work reliable, we also
;; give read permissions.
- (set-file-modes tmpfile #o0777 'nofollow)
+ (set-file-modes tmpfile #o0777)
(tramp-set-file-uid-gid
tmpfile
(tramp-get-remote-uid v 'integer)
(delete-file tmpfile2)))))
;; Set proper permissions.
- (set-file-modes tmpfile (tramp-default-file-modes filename)
- 'nofollow)
+ (set-file-modes tmpfile (tramp-default-file-modes filename))
;; Set local user ownership.
(tramp-set-file-uid-gid tmpfile))
#'write-region
(list start end localname append 'no-message lockname))
- (let* ((modes (save-excursion (tramp-default-file-modes filename)))
+ (let* ((modes (tramp-default-file-modes
+ filename (and (eq mustbenew 'excl) 'nofollow)))
;; We use this to save the value of
;; `last-coding-system-used' after writing the tmp
;; file. At the end of the function, we set
;; handles permissions.
;; Ensure that it is still readable.
(when modes
- (set-file-modes tmpfile (logior (or modes 0) #o0400) 'nofollow))
+ (set-file-modes tmpfile (logior (or modes 0) #o0400)))
;; This is a bit lengthy due to the different methods
;; possible for file transfer. First, we check whether the
vec (concat command " -A n </dev/null"))
command)))))
+(defun tramp-get-remote-chmod-h (vec)
+ "Determine remote `chmod' command which supports nofollow argument."
+ (with-tramp-connection-property vec "chmod-h"
+ (tramp-message vec 5 "Finding a suitable `chmod' command with nofollow")
+ (let ((tmpfile
+ (make-temp-name
+ (expand-file-name
+ tramp-temp-name-prefix (tramp-get-remote-tmpdir vec)))))
+ (when (tramp-send-command-and-check
+ vec
+ (format
+ "ln -s foo %s && chmod -h %s 0777"
+ (tramp-file-local-name tmpfile) (tramp-file-local-name tmpfile)))
+ (delete-file tmpfile)
+ "chmod -h"))))
+
(defun tramp-get-env-with-u-option (vec)
"Check, whether the remote `env' command supports the -u option."
(with-tramp-connection-property vec "env-u-option"
(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 (and (eq flag 'nofollow) (file-symlink-p filename))
+ (tramp-error v 'file-error "Cannot chmod %s with %s flag" filename flag))
(when (tramp-smb-get-cifs-capabilities v)
(tramp-flush-file-properties v localname)
(unless (tramp-smb-send-command
(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
+ (when (and (eq flag 'nofollow) (file-symlink-p filename))
+ (tramp-error v 'file-error "Cannot chmod %s with %s flag" filename flag))
(tramp-flush-file-properties v localname)
(unless (tramp-sudoedit-send-command
v "chmod" (format "%o" mode)
(start end filename &optional append visit lockname mustbenew)
"Like `write-region' for Tramp files."
(with-parsed-tramp-file-name filename nil
- (let ((uid (or (tramp-compat-file-attribute-user-id
- (file-attributes filename 'integer))
- (tramp-sudoedit-get-remote-uid v 'integer)))
- (gid (or (tramp-compat-file-attribute-group-id
- (file-attributes filename 'integer))
- (tramp-sudoedit-get-remote-gid v 'integer)))
- (modes (tramp-default-file-modes filename)))
+ (let* ((uid (or (tramp-compat-file-attribute-user-id
+ (file-attributes filename 'integer))
+ (tramp-sudoedit-get-remote-uid v 'integer)))
+ (gid (or (tramp-compat-file-attribute-group-id
+ (file-attributes filename 'integer))
+ (tramp-sudoedit-get-remote-gid v 'integer)))
+ (flag (and (eq mustbenew 'excl) 'nofollow))
+ (modes (tramp-default-file-modes filename flag)))
(prog1
(tramp-handle-write-region
start end filename append visit lockname mustbenew)
(file-attributes filename 'integer))
gid))
(tramp-set-file-uid-gid filename uid gid))
- (set-file-modes filename modes
- (when (eq mustbenew 'excl) 'nofollow))))))
+ (tramp-compat-set-file-modes filename modes flag)))))
\f
;; Internal functions.
(defvar tramp-devices 0
"Keeps virtual device numbers.")
-(defun tramp-default-file-modes (filename)
+(defun tramp-default-file-modes (filename &optional flag)
"Return file modes of FILENAME as integer.
-If the file modes of FILENAME cannot be determined, return the
-value of `default-file-modes', without execute permissions."
- (or (file-modes filename)
+If optional FLAG is ‘nofollow’, do not follow FILENAME if it is a
+symbolic link. If the file modes of FILENAME cannot be
+determined, return the value of `default-file-modes', without
+execute permissions."
+ (or (tramp-compat-file-modes filename flag)
(logand (default-file-modes) #o0666)))
(defun tramp-replace-environment-variables (filename)
(defun tramp-handle-file-modes (filename &optional flag)
"Like `file-modes' for Tramp files."
- (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)))))
+ (when-let ((attrs (file-attributes filename))
+ (mode-string (tramp-compat-file-attribute-modes attrs)))
+ (if (and (not (eq flag 'nofollow)) (eq ?l (aref mode-string 0)))
+ (file-modes (file-truename filename))
+ (tramp-mode-string-to-int mode-string))))
;; Localname manipulation functions that grok Tramp localnames...
(defun tramp-handle-file-name-as-directory (file)
(tramp-error v 'file-already-exists filename))
(let ((tmpfile (tramp-compat-make-temp-file filename))
- (modes (save-excursion (tramp-default-file-modes filename))))
+ (modes (tramp-default-file-modes
+ filename (and (eq mustbenew 'excl) 'nofollow))))
(when (and append (file-exists-p filename))
(copy-file filename tmpfile 'ok))
;; The permissions of the temporary file should be set. If
;; 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) 'nofollow)
+ (set-file-modes tmpfile (logior (or modes 0) #o0400))
;; 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.
(setq result nil)
;; This creates the file by side effect.
(set-file-times result)
- (set-file-modes result #o0700 'nofollow)))
+ (set-file-modes result #o0700)))
;; Return the local part.
(tramp-file-local-name result)))
;; Method "smb" supports `make-symbolic-link' only if the remote host
;; has CIFS capabilities. tramp-adb.el, tramp-gvfs.el and
;; tramp-rclone.el do not support symbolic links at all.
+;; We check also `set-file-modes' with nofollow flag.
(defmacro tramp--test-ignore-make-symbolic-link-error (&rest body)
"Run BODY, ignoring \"make-symbolic-link not supported\" file error."
(declare (indent defun) (debug (body)))
`(condition-case err
(progn ,@body)
(file-error
- (unless (string-equal (error-message-string err)
- "make-symbolic-link not supported")
+ (unless (string-match-p
+ (concat
+ "^\\(make-symbolic-link not supported"
+ "\\|Cannot chmod .* with nofollow flag\\)$")
+ (error-message-string err))
(signal (car err) (cdr err))))))
(ert-deftest tramp-test18-file-attributes ()
"ftp" (file-remote-p tramp-test-temporary-file-directory 'method)))))
(dolist (quoted (if (tramp--test-expensive-test) '(nil t) '(nil)))
- (let ((tmp-name (tramp--test-make-temp-name nil quoted)))
+ (let ((tmp-name1 (tramp--test-make-temp-name nil quoted))
+ (tmp-name2 (tramp--test-make-temp-name nil quoted)))
+
(unwind-protect
(progn
- (write-region "foo" nil tmp-name)
- (should (file-exists-p tmp-name))
- (set-file-modes tmp-name #o777)
- (should (= (file-modes tmp-name) #o777))
- (should (file-executable-p tmp-name))
- (should (file-writable-p tmp-name))
- (set-file-modes tmp-name #o444)
- (should (= (file-modes tmp-name) #o444))
- (should-not (file-executable-p tmp-name))
+ (write-region "foo" nil tmp-name1)
+ (should (file-exists-p tmp-name1))
+ (set-file-modes tmp-name1 #o777)
+ (should (= (file-modes tmp-name1) #o777))
+ (should (file-executable-p tmp-name1))
+ (should (file-writable-p tmp-name1))
+ (set-file-modes tmp-name1 #o444)
+ (should (= (file-modes tmp-name1) #o444))
+ (should-not (file-executable-p tmp-name1))
;; A file is always writable for user "root".
(unless (zerop (tramp-compat-file-attribute-user-id
- (file-attributes tmp-name)))
- (should-not (file-writable-p tmp-name))))
+ (file-attributes tmp-name1)))
+ (should-not (file-writable-p tmp-name1))))
;; Cleanup.
- (ignore-errors (delete-file tmp-name))))))
+ (ignore-errors (delete-file tmp-name1)))
+
+ ;; Check the NOFOLLOW arg. It exists since Emacs 28.
+ (when (tramp--test-emacs28-p)
+ (unwind-protect
+ (tramp--test-ignore-make-symbolic-link-error
+ (write-region "foo" nil tmp-name1)
+ (should (file-exists-p tmp-name1))
+ (make-symbolic-link tmp-name1 tmp-name2)
+ (should
+ (string-equal
+ (funcall
+ (if quoted #'tramp-compat-file-name-unquote #'identity)
+ (file-remote-p tmp-name1 'localname))
+ (file-symlink-p tmp-name2)))
+ ;; Both report the modes of `tmp-name1'.
+ (should
+ (= (file-modes tmp-name1) (file-modes tmp-name2)))
+ ;; `tmp-name1' is a regular file. NOFOLLOW doesn't matter.
+ (should
+ (= (file-modes tmp-name1) (file-modes tmp-name1 'nofollow)))
+ ;; `tmp-name2' is a symbolic link. It has different permissions.
+ (should-not
+ (= (file-modes tmp-name2) (file-modes tmp-name2 'nofollow)))
+ (should-not
+ (= (file-modes tmp-name1 'nofollow)
+ (file-modes tmp-name2 'nofollow)))
+ ;; Change permissions.
+ (set-file-modes tmp-name1 #o200)
+ (set-file-modes tmp-name2 #o200)
+ (should
+ (= (file-modes tmp-name1) (file-modes tmp-name2) #o200))
+ ;; Change permissions with NOFOLLOW.
+ (set-file-modes tmp-name1 #o300 'nofollow)
+ (set-file-modes tmp-name2 #o300 'nofollow)
+ (should
+ (= (file-modes tmp-name1 'nofollow)
+ (file-modes tmp-name2 'nofollow)))
+ (should-not (= (file-modes tmp-name1) (file-modes tmp-name2))))
+
+ ;; Cleanup.
+ (ignore-errors (delete-file tmp-name1))
+ (ignore-errors (delete-file tmp-name2)))))))
;; Method "smb" could run into "NT_STATUS_REVISION_MISMATCH" error.
(defmacro tramp--test-ignore-add-name-to-file-error (&rest body)
(while (accept-process-output proc 0 nil t)))
;; We cannot use `string-equal', because tramp-adb.el
;; echoes also the sent string. And a remote macOS sends
- ;; a slightly modified string. On MS-Windows,
+ ;; a slightly modified string. On MS Windows,
;; `delete-process' sends an unknown signal.
(should
(string-match
variables, so we check the Emacs version directly."
(>= emacs-major-version 27))
+(defun tramp--test-emacs28-p ()
+ "Check for Emacs version >= 28.1.
+Some semantics has been changed for there, w/o new functions or
+variables, so we check the Emacs version directly."
+ (>= emacs-major-version 28))
+
(defun tramp--test-adb-p ()
"Check, whether the remote host runs Android.
This requires restrictions of file name syntax."