From: Michael Albinus Date: Mon, 12 Aug 2019 14:18:59 +0000 (+0200) Subject: Quote file names properly in Tramp X-Git-Tag: emacs-27.0.90~1640 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=88006cf542ed99ca8236c9b61c6b19b732353d6c;p=emacs.git Quote file names properly in Tramp * lisp/net/tramp.el (tramp-handle-file-truename) (tramp-handle-insert-directory): * lisp/net/tramp-adb.el (tramp-adb-handle-file-truename): * lisp/net/tramp-sh.el (tramp-sh-handle-file-truename) (tramp-sh-handle-insert-directory): * lisp/net/tramp-smb.el (tramp-smb-handle-insert-directory): * lisp/net/tramp-sudoedit.el (tramp-sudoedit-handle-file-truename): Use `tramp-compat-directory-name-p'. * lisp/net/tramp.el (tramp-drop-volume-letter) (tramp-handle-file-truename): * lisp/net/tramp-adb.el (tramp-adb-handle-file-truename): * lisp/net/tramp-sh.el (tramp-sh-handle-make-symbolic-link) (tramp-sh-handle-file-truename): * lisp/net/tramp-smb.el (tramp-smb-handle-make-symbolic-link): * lisp/net/tramp-sudoedit.el (tramp-sudoedit-handle-file-truename): (tramp-sudoedit-handle-make-symbolic-link): Quote properly. * lisp/net/tramp-compat.el (tramp-compat-file-name-quote) (tramp-compat-file-name-unquote): Add optional argument TOP. --- diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el index 2192f7f0252..df4778c9c96 100644 --- a/lisp/net/tramp-adb.el +++ b/lisp/net/tramp-adb.el @@ -232,96 +232,100 @@ pass to the OPERATION." ;; code could be shared? (defun tramp-adb-handle-file-truename (filename) "Like `file-truename' for Tramp files." - ;; Preserve trailing "/". + ;; Preserve trailing "/". (funcall - (if (string-equal (file-name-nondirectory filename) "") + (if (tramp-compat-directory-name-p filename) #'file-name-as-directory #'identity) - (with-parsed-tramp-file-name (expand-file-name filename) nil - (tramp-make-tramp-file-name - v - (with-tramp-file-property v localname "file-truename" - (let ((result nil) ; result steps in reverse order - (quoted (tramp-compat-file-name-quoted-p localname))) - (tramp-message v 4 "Finding true name for `%s'" filename) - (let* ((steps (split-string localname "/" 'omit)) - (localnamedir (tramp-run-real-handler - 'file-name-as-directory (list localname))) - (is-dir (string= localname localnamedir)) - (thisstep nil) - (numchase 0) - ;; Don't make the following value larger than - ;; necessary. People expect an error message in a - ;; timely fashion when something is wrong; otherwise - ;; they might think that Emacs is hung. Of course, - ;; correctness has to come first. - (numchase-limit 20) - symlink-target) - (while (and steps (< numchase numchase-limit)) - (setq thisstep (pop steps)) - (tramp-message - v 5 "Check %s" - (string-join - (append '("") (reverse result) (list thisstep)) "/")) - (setq symlink-target - (tramp-compat-file-attribute-type - (file-attributes - (tramp-make-tramp-file-name - v - (string-join - (append '("") (reverse result) (list thisstep)) "/"))))) - (cond ((string= "." thisstep) - (tramp-message v 5 "Ignoring step `.'")) - ((string= ".." thisstep) - (tramp-message v 5 "Processing step `..'") - (pop result)) - ((stringp symlink-target) - ;; It's a symlink, follow it. - (tramp-message v 5 "Follow symlink to %s" symlink-target) - (setq numchase (1+ numchase)) - (when (file-name-absolute-p symlink-target) - (setq result nil)) - ;; If the symlink was absolute, we'll get a string - ;; like "/user@host:/some/target"; extract the - ;; "/some/target" part from it. - (when (tramp-tramp-file-p symlink-target) - (unless (tramp-equal-remote filename symlink-target) - (tramp-error - v 'file-error - "Symlink target `%s' on wrong host" symlink-target)) - (setq symlink-target localname)) - (setq steps - (append (split-string symlink-target "/" 'omit) - steps))) - (t - ;; It's a file. - (setq result (cons thisstep result))))) - (when (>= numchase numchase-limit) - (tramp-error - v 'file-error - "Maximum number (%d) of symlinks exceeded" numchase-limit)) - (setq result (reverse result)) - ;; Combine list to form string. - (setq result - (if result - (string-join (cons "" result) "/") - "/")) - (when (and is-dir (or (string-empty-p result) - (not (string= (substring result -1) "/")))) - (setq result (concat result "/")))) - - ;; Detect cycle. - (when (and (file-symlink-p filename) - (string-equal result localname)) - (tramp-error - v 'file-error - "Apparent cycle of symbolic links for %s" filename)) - ;; If the resulting localname looks remote, we must quote it - ;; for security reasons. - (when (or quoted (file-remote-p result)) - (let (file-name-handler-alist) - (setq result (tramp-compat-file-name-quote result)))) - (tramp-message v 4 "True name of `%s' is `%s'" localname result) - result)))))) + ;; Quote properly. + (funcall + (if (tramp-compat-file-name-quoted-p filename) + #'tramp-compat-file-name-quote #'identity) + (with-parsed-tramp-file-name + (tramp-compat-file-name-unquote (expand-file-name filename)) nil + (tramp-make-tramp-file-name + v + (with-tramp-file-property v localname "file-truename" + (let (result) ; result steps in reverse order + (tramp-message v 4 "Finding true name for `%s'" filename) + (let* ((steps (split-string localname "/" 'omit)) + (localnamedir (tramp-run-real-handler + 'file-name-as-directory (list localname))) + (is-dir (string= localname localnamedir)) + (thisstep nil) + (numchase 0) + ;; Don't make the following value larger than + ;; necessary. People expect an error message in a + ;; timely fashion when something is wrong; otherwise + ;; they might think that Emacs is hung. Of course, + ;; correctness has to come first. + (numchase-limit 20) + symlink-target) + (while (and steps (< numchase numchase-limit)) + (setq thisstep (pop steps)) + (tramp-message + v 5 "Check %s" + (string-join + (append '("") (reverse result) (list thisstep)) "/")) + (setq symlink-target + (tramp-compat-file-attribute-type + (file-attributes + (tramp-make-tramp-file-name + v + (string-join + (append + '("") (reverse result) (list thisstep)) "/"))))) + (cond ((string= "." thisstep) + (tramp-message v 5 "Ignoring step `.'")) + ((string= ".." thisstep) + (tramp-message v 5 "Processing step `..'") + (pop result)) + ((stringp symlink-target) + ;; It's a symlink, follow it. + (tramp-message v 5 "Follow symlink to %s" symlink-target) + (setq numchase (1+ numchase)) + (when (file-name-absolute-p symlink-target) + (setq result nil)) + ;; If the symlink was absolute, we'll get a string + ;; like "/user@host:/some/target"; extract the + ;; "/some/target" part from it. + (when (tramp-tramp-file-p symlink-target) + (unless (tramp-equal-remote filename symlink-target) + (tramp-error + v 'file-error + "Symlink target `%s' on wrong host" symlink-target)) + (setq symlink-target localname)) + (setq steps + (append (split-string symlink-target "/" 'omit) + steps))) + (t + ;; It's a file. + (setq result (cons thisstep result))))) + (when (>= numchase numchase-limit) + (tramp-error + v 'file-error + "Maximum number (%d) of symlinks exceeded" numchase-limit)) + (setq result (reverse result)) + ;; Combine list to form string. + (setq result + (if result + (string-join (cons "" result) "/") + "/")) + (when (and is-dir (or (string-empty-p result) + (not (string= (substring result -1) "/")))) + (setq result (concat result "/")))) + + ;; Detect cycle. + (when (and (file-symlink-p filename) + (string-equal result localname)) + (tramp-error + v 'file-error + "Apparent cycle of symbolic links for %s" filename)) + ;; If the resulting localname looks remote, we must quote it + ;; for security reasons. + (when (file-remote-p result) + (setq result (tramp-compat-file-name-quote result 'top))) + (tramp-message v 4 "True name of `%s' is `%s'" localname result) + result))))))) (defun tramp-adb-handle-file-attributes (filename &optional id-format) "Like `file-attributes' for Tramp files." diff --git a/lisp/net/tramp-compat.el b/lisp/net/tramp-compat.el index fca2654bee7..e83c1a1500b 100644 --- a/lisp/net/tramp-compat.el +++ b/lisp/net/tramp-compat.el @@ -227,24 +227,31 @@ If NAME is a remote file name and TOP is nil, check the local part of NAME." (string-prefix-p "/:" (tramp-compat-file-local-name name)))))) (defalias 'tramp-compat-file-name-quote - (if (fboundp 'file-name-quote) + (if (and + (fboundp 'file-name-quote) + (equal (tramp-compat-funcall 'func-arity #'file-name-quote) '(1 . 2))) #'file-name-quote - (lambda (name) + (lambda (name &optional top) "Add the quotation prefix \"/:\" to file NAME. -If NAME is a remote file name, the local part of NAME is quoted." - (if (tramp-compat-file-name-quoted-p name) - name - (concat - (file-remote-p name) "/:" (tramp-compat-file-local-name name)))))) +If NAME is a remote file name and TOP is nil, the local part of NAME is quoted." + (let ((file-name-handler-alist (unless top file-name-handler-alist))) + (if (tramp-compat-file-name-quoted-p name top) + name + (concat + (file-remote-p name) "/:" (tramp-compat-file-local-name name))))))) (defalias 'tramp-compat-file-name-unquote - (if (fboundp 'file-name-unquote) + (if (and + (fboundp 'file-name-unquote) + (equal (tramp-compat-funcall 'func-arity #'file-name-unquote) '(1 . 2))) #'file-name-unquote - (lambda (name) + (lambda (name &optional top) "Remove quotation prefix \"/:\" from file NAME. -If NAME is a remote file name, the local part of NAME is unquoted." - (let ((localname (tramp-compat-file-local-name name))) - (when (tramp-compat-file-name-quoted-p localname) +If NAME is a remote file name and TOP is nil, the local part of +NAME is unquoted." + (let* ((file-name-handler-alist (unless top file-name-handler-alist)) + (localname (tramp-compat-file-local-name name))) + (when (tramp-compat-file-name-quoted-p localname top) (setq localname (if (= (length localname) 2) "/" (substring localname 2)))) (concat (file-remote-p name) localname))))) diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index 6e18e7330c7..f1f0abc6e5c 100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@ -1044,8 +1044,7 @@ component is used as the target of the symlink." ;; If TARGET is still remote, quote it. (if (tramp-tramp-file-p target) - (make-symbolic-link - (let (file-name-handler-alist) (tramp-compat-file-name-quote target)) + (make-symbolic-link (tramp-compat-file-name-quote target 'top) linkname ok-if-already-exists) (let ((ln (tramp-get-remote-ln v)) @@ -1090,108 +1089,113 @@ component is used as the target of the symlink." (defun tramp-sh-handle-file-truename (filename) "Like `file-truename' for Tramp files." - ;; Preserve trailing "/". + ;; Preserve trailing "/". (funcall - (if (string-equal (file-name-nondirectory filename) "") + (if (tramp-compat-directory-name-p filename) #'file-name-as-directory #'identity) - (with-parsed-tramp-file-name (expand-file-name filename) nil - (tramp-make-tramp-file-name - v - (with-tramp-file-property v localname "file-truename" - (let ((result nil) ; result steps in reverse order - (quoted (tramp-compat-file-name-quoted-p localname)) - (localname (tramp-compat-file-name-unquote localname))) - (tramp-message v 4 "Finding true name for `%s'" filename) - (cond - ;; Use GNU readlink --canonicalize-missing where available. - ((tramp-get-remote-readlink v) - (tramp-send-command-and-check - v - (format "%s --canonicalize-missing %s" - (tramp-get-remote-readlink v) - (tramp-shell-quote-argument localname))) - (with-current-buffer (tramp-get-connection-buffer v) - (goto-char (point-min)) - (setq result (buffer-substring (point-min) (point-at-eol))))) - - ;; Use Perl implementation. - ((and (tramp-get-remote-perl v) - (tramp-get-connection-property v "perl-file-spec" nil) - (tramp-get-connection-property v "perl-cwd-realpath" nil)) - (tramp-maybe-send-script - v tramp-perl-file-truename "tramp_perl_file_truename") - (setq result - (tramp-send-command-and-read - v - (format "tramp_perl_file_truename %s" - (tramp-shell-quote-argument localname))))) - - ;; Do it yourself. - (t (let ((steps (split-string localname "/" 'omit)) - (thisstep nil) - (numchase 0) - ;; Don't make the following value larger than - ;; necessary. People expect an error message in a - ;; timely fashion when something is wrong; - ;; otherwise they might think that Emacs is hung. - ;; Of course, correctness has to come first. - (numchase-limit 20) - symlink-target) - (while (and steps (< numchase numchase-limit)) - (setq thisstep (pop steps)) - (tramp-message - v 5 "Check %s" - (string-join - (append '("") (reverse result) (list thisstep)) "/")) - (setq symlink-target - (tramp-compat-file-attribute-type - (file-attributes - (tramp-make-tramp-file-name - v - (string-join - (append '("") (reverse result) (list thisstep)) "/") - 'nohop)))) - (cond ((string= "." thisstep) - (tramp-message v 5 "Ignoring step `.'")) - ((string= ".." thisstep) - (tramp-message v 5 "Processing step `..'") - (pop result)) - ((stringp symlink-target) - ;; It's a symlink, follow it. - (tramp-message - v 5 "Follow symlink to %s" symlink-target) - (setq numchase (1+ numchase)) - (when (file-name-absolute-p symlink-target) - (setq result nil)) - (setq steps - (append - (split-string symlink-target "/" 'omit) steps))) - (t - ;; It's a file. - (setq result (cons thisstep result))))) - (when (>= numchase numchase-limit) - (tramp-error - v 'file-error - "Maximum number (%d) of symlinks exceeded" numchase-limit)) - (setq result (reverse result)) - ;; Combine list to form string. - (setq result (if result (string-join (cons "" result) "/") "/")) - (when (string-empty-p result) (setq result "/"))))) - - ;; Detect cycle. - (when (and (file-symlink-p filename) - (string-equal result localname)) - (tramp-error - v 'file-error - "Apparent cycle of symbolic links for %s" filename)) - ;; If the resulting localname looks remote, we must quote it - ;; for security reasons. - (when (or quoted (file-remote-p result)) - (let (file-name-handler-alist) - (setq result (tramp-compat-file-name-quote result)))) - (tramp-message v 4 "True name of `%s' is `%s'" localname result) - result)) - 'nohop)))) + ;; Quote properly. + (funcall + (if (tramp-compat-file-name-quoted-p filename) + #'tramp-compat-file-name-quote #'identity) + (with-parsed-tramp-file-name + (tramp-compat-file-name-unquote (expand-file-name filename)) nil + (tramp-make-tramp-file-name + v + (with-tramp-file-property v localname "file-truename" + (let (result) ; result steps in reverse order + (tramp-message v 4 "Finding true name for `%s'" filename) + (cond + ;; Use GNU readlink --canonicalize-missing where available. + ((tramp-get-remote-readlink v) + (tramp-send-command-and-check + v + (format "%s --canonicalize-missing %s" + (tramp-get-remote-readlink v) + (tramp-shell-quote-argument localname))) + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (setq result (buffer-substring (point-min) (point-at-eol))))) + + ;; Use Perl implementation. + ((and (tramp-get-remote-perl v) + (tramp-get-connection-property v "perl-file-spec" nil) + (tramp-get-connection-property v "perl-cwd-realpath" nil)) + (tramp-maybe-send-script + v tramp-perl-file-truename "tramp_perl_file_truename") + (setq result + (tramp-send-command-and-read + v + (format "tramp_perl_file_truename %s" + (tramp-shell-quote-argument localname))))) + + ;; Do it yourself. + (t (let ((steps (split-string localname "/" 'omit)) + (thisstep nil) + (numchase 0) + ;; Don't make the following value larger than + ;; necessary. People expect an error message in a + ;; timely fashion when something is wrong; + ;; otherwise they might think that Emacs is hung. + ;; Of course, correctness has to come first. + (numchase-limit 20) + symlink-target) + (while (and steps (< numchase numchase-limit)) + (setq thisstep (pop steps)) + (tramp-message + v 5 "Check %s" + (string-join + (append '("") (reverse result) (list thisstep)) "/")) + (setq symlink-target + (tramp-compat-file-attribute-type + (file-attributes + (tramp-make-tramp-file-name + v + (string-join + (append + '("") (reverse result) (list thisstep)) "/") + 'nohop)))) + (cond ((string= "." thisstep) + (tramp-message v 5 "Ignoring step `.'")) + ((string= ".." thisstep) + (tramp-message v 5 "Processing step `..'") + (pop result)) + ((stringp symlink-target) + ;; It's a symlink, follow it. + (tramp-message + v 5 "Follow symlink to %s" symlink-target) + (setq numchase (1+ numchase)) + (when (file-name-absolute-p symlink-target) + (setq result nil)) + (setq steps + (append + (split-string symlink-target "/" 'omit) + steps))) + (t + ;; It's a file. + (setq result (cons thisstep result))))) + (when (>= numchase numchase-limit) + (tramp-error + v 'file-error + "Maximum number (%d) of symlinks exceeded" numchase-limit)) + (setq result (reverse result)) + ;; Combine list to form string. + (setq result + (if result (string-join (cons "" result) "/") "/")) + (when (string-empty-p result) (setq result "/"))))) + + ;; Detect cycle. + (when (and (file-symlink-p filename) + (string-equal result localname)) + (tramp-error + v 'file-error + "Apparent cycle of symbolic links for %s" filename)) + ;; If the resulting localname looks remote, we must quote it + ;; for security reasons. + (when (file-remote-p result) + (setq result (tramp-compat-file-name-quote result 'top))) + (tramp-message v 4 "True name of `%s' is `%s'" localname result) + result)) + 'nohop))))) ;; Basic functions. @@ -2676,7 +2680,7 @@ The method used must be an out-of-band method." (when (file-symlink-p filename) (goto-char (search-backward "->" beg 'noerror))) (search-backward - (if (zerop (length (file-name-nondirectory filename))) + (if (tramp-compat-directory-name-p filename) "." (file-name-nondirectory filename)) beg 'noerror) diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el index b619e77a52a..5df26a1e33e 100644 --- a/lisp/net/tramp-smb.el +++ b/lisp/net/tramp-smb.el @@ -986,7 +986,7 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." (setq filename (expand-file-name filename)) (unless switches (setq switches "")) ;; Mark trailing "/". - (when (and (zerop (length (file-name-nondirectory filename))) + (when (and (tramp-compat-directory-name-p filename) (not full-directory-p)) (setq switches (concat switches "F"))) (if full-directory-p @@ -1175,8 +1175,7 @@ component is used as the target of the symlink." ;; If TARGET is still remote, quote it. (if (tramp-tramp-file-p target) - (make-symbolic-link - (let (file-name-handler-alist) (tramp-compat-file-name-quote target)) + (make-symbolic-link (tramp-compat-file-name-quote target 'top) linkname ok-if-already-exists) ;; Do the 'confirm if exists' thing. diff --git a/lisp/net/tramp-sudoedit.el b/lisp/net/tramp-sudoedit.el index 5d5a3f1f75c..80ce8f78747 100644 --- a/lisp/net/tramp-sudoedit.el +++ b/lisp/net/tramp-sudoedit.el @@ -533,34 +533,36 @@ the result will be a local, non-Tramp, file name." (defun tramp-sudoedit-handle-file-truename (filename) "Like `file-truename' for Tramp files." - ;; Preserve trailing "/". + ;; Preserve trailing "/". (funcall - (if (string-equal (file-name-nondirectory filename) "") + (if (tramp-compat-directory-name-p filename) #'file-name-as-directory #'identity) - (with-parsed-tramp-file-name (expand-file-name filename) nil - (tramp-make-tramp-file-name - v - (with-tramp-file-property v localname "file-truename" - (let ((quoted (tramp-compat-file-name-quoted-p localname)) - (localname (tramp-compat-file-name-unquote localname)) - result) - (tramp-message v 4 "Finding true name for `%s'" filename) - (setq result (tramp-sudoedit-send-command-string - v "readlink" "--canonicalize-missing" localname)) - ;; Detect cycle. - (when (and (file-symlink-p filename) - (string-equal result localname)) - (tramp-error - v 'file-error - "Apparent cycle of symbolic links for %s" filename)) - ;; If the resulting localname looks remote, we must quote it - ;; for security reasons. - (when (or quoted (file-remote-p result)) - (let (file-name-handler-alist) - (setq result (tramp-compat-file-name-quote result)))) - (tramp-message v 4 "True name of `%s' is `%s'" localname result) - result)) - 'nohop)))) + ;; Quote properly. + (funcall + (if (tramp-compat-file-name-quoted-p filename) + #'tramp-compat-file-name-quote #'identity) + (with-parsed-tramp-file-name + (tramp-compat-file-name-unquote (expand-file-name filename)) nil + (tramp-make-tramp-file-name + v + (with-tramp-file-property v localname "file-truename" + (let (result) + (tramp-message v 4 "Finding true name for `%s'" filename) + (setq result (tramp-sudoedit-send-command-string + v "readlink" "--canonicalize-missing" localname)) + ;; Detect cycle. + (when (and (file-symlink-p filename) + (string-equal result localname)) + (tramp-error + v 'file-error + "Apparent cycle of symbolic links for %s" filename)) + ;; If the resulting localname looks remote, we must quote it + ;; for security reasons. + (when (file-remote-p result) + (setq result (tramp-compat-file-name-quote result 'top))) + (tramp-message v 4 "True name of `%s' is `%s'" localname result) + result)) + 'nohop))))) (defun tramp-sudoedit-handle-file-writable-p (filename) "Like `file-writable-p' for Tramp files." @@ -609,8 +611,7 @@ component is used as the target of the symlink." ;; If TARGET is still remote, quote it. (if (tramp-tramp-file-p target) - (make-symbolic-link - (let (file-name-handler-alist) (tramp-compat-file-name-quote target)) + (make-symbolic-link (tramp-compat-file-name-quote target 'top) linkname ok-if-already-exists) ;; Do the 'confirm if exists' thing. diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index 7bae4347232..d419f9d87d0 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -2004,13 +2004,11 @@ locally on a remote file name. When the local system is a W32 system but the remote system is Unix, this introduces a superfluous drive letter into the file name. This function removes it." (save-match-data - (funcall - (if (tramp-compat-file-name-quoted-p name) - #'tramp-compat-file-name-quote #'identity) - (let ((name (tramp-compat-file-name-unquote name))) - (if (string-match "\\`[a-zA-Z]:/" name) - (replace-match "/" nil t name) - name))))) + (let ((quoted (tramp-compat-file-name-quoted-p name 'top)) + (result (tramp-compat-file-name-unquote name 'top))) + (setq result (if (string-match "\\`[a-zA-Z]:/" result) + (replace-match "/" nil t result) result)) + (if quoted (tramp-compat-file-name-quote result 'top) result)))) ;;; Config Manipulation Functions: @@ -3287,45 +3285,44 @@ User is always nil." "Like `file-truename' for Tramp files." ;; Preserve trailing "/". (funcall - (if (string-equal (file-name-nondirectory filename) "") + (if (tramp-compat-directory-name-p filename) #'file-name-as-directory #'identity) - (let ((result (expand-file-name filename)) - (numchase 0) - ;; Don't make the following value larger than necessary. - ;; People expect an error message in a timely fashion when - ;; something is wrong; otherwise they might think that Emacs - ;; is hung. Of course, correctness has to come first. - (numchase-limit 20) - symlink-target) - (with-parsed-tramp-file-name result v1 - ;; We cache only the localname. - (tramp-make-tramp-file-name - v1 - (with-tramp-file-property v1 v1-localname "file-truename" - (while (and (setq symlink-target (file-symlink-p result)) - (< numchase numchase-limit)) - (setq numchase (1+ numchase) - result - (with-parsed-tramp-file-name (expand-file-name result) v2 - (tramp-make-tramp-file-name - v2 - (funcall - (if (tramp-compat-file-name-quoted-p v2-localname) - #'tramp-compat-file-name-quote #'identity) - + ;; Quote properly. + (funcall + (if (tramp-compat-file-name-quoted-p filename) + #'tramp-compat-file-name-quote #'identity) + (let ((result (tramp-compat-file-name-unquote (expand-file-name filename))) + (numchase 0) + ;; Don't make the following value larger than necessary. + ;; People expect an error message in a timely fashion when + ;; something is wrong; otherwise they might think that Emacs + ;; is hung. Of course, correctness has to come first. + (numchase-limit 20) + symlink-target) + (with-parsed-tramp-file-name result v1 + ;; We cache only the localname. + (tramp-make-tramp-file-name + v1 + (with-tramp-file-property v1 v1-localname "file-truename" + (while (and (setq symlink-target (file-symlink-p result)) + (< numchase numchase-limit)) + (setq numchase (1+ numchase) + result + (with-parsed-tramp-file-name (expand-file-name result) v2 + (tramp-make-tramp-file-name + v2 (if (stringp symlink-target) (if (file-remote-p symlink-target) - (let (file-name-handler-alist) - (tramp-compat-file-name-quote symlink-target)) + (tramp-compat-file-name-quote symlink-target 'top) (expand-file-name symlink-target (file-name-directory v2-localname))) - v2-localname)) - 'nohop))) - (when (>= numchase numchase-limit) - (tramp-error - v1 'file-error - "Maximum number (%d) of symlinks exceeded" numchase-limit))) - (tramp-compat-file-local-name (directory-file-name result)))))))) + v2-localname) + 'nohop))) + (when (>= numchase numchase-limit) + (tramp-error + v1 'file-error + "Maximum number (%d) of symlinks exceeded" numchase-limit))) + (tramp-compat-file-local-name (directory-file-name result))))))))) (defun tramp-handle-file-writable-p (filename) "Like `file-writable-p' for Tramp files." @@ -3360,7 +3357,7 @@ User is always nil." "Like `insert-directory' for Tramp files." (unless switches (setq switches "")) ;; Mark trailing "/". - (when (and (zerop (length (file-name-nondirectory filename))) + (when (and (tramp-compat-directory-name-p filename) (not full-directory-p)) (setq switches (concat switches "F"))) ;; Check, whether directory is accessible.