diff options
Diffstat (limited to 'lisp/net/tramp-sh.el')
-rw-r--r-- | lisp/net/tramp-sh.el | 1558 |
1 files changed, 788 insertions, 770 deletions
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el index 3e6fb384a8f..66e648624b2 100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@ -31,14 +31,13 @@ ;;; Code: -(eval-when-compile (require 'cl-lib)) +(require 'cl-lib) (require 'tramp) ;; `dired-*' declarations can be removed, starting with Emacs 29.1. (declare-function dired-compress-file "dired-aux") (declare-function dired-remove-file "dired-aux") (defvar dired-compress-file-suffixes) -(defvar ls-lisp-use-insert-directory-program) ;; Added in Emacs 28.1. (defvar process-file-return-signal-string) (defvar vc-handled-backends) @@ -100,19 +99,23 @@ detected as prompt when being sent on echoing hosts, therefore.") (defconst tramp-end-of-heredoc (md5 tramp-end-of-output) "String used to recognize end of heredoc strings.") -(defcustom tramp-use-ssh-controlmaster-options (not (eq system-type 'windows-nt)) - "Whether to use `tramp-ssh-controlmaster-options'. -Set it to t, if you want Tramp to apply these options. +(define-obsolete-variable-alias + 'tramp-use-ssh-controlmaster-options 'tramp-use-connection-share "30.1") + +(defcustom tramp-use-connection-share (not (eq system-type 'windows-nt)) + "Whether to use connection share in ssh or PuTTY. +Set it to t, if you want Tramp to apply respective options. These +are `tramp-ssh-controlmaster-options' for ssh, and \"-share\" for PuTTY. Set it to nil, if you use Control* or Proxy* options in your ssh configuration. Set it to `suppress' if you want to disable settings in your -\"~/.ssh/config¸\"." +\"~/.ssh/config\" file or in your PuTTY session." :group 'tramp - :version "29.2" + :version "30.1" :type '(choice (const :tag "Set ControlMaster" t) (const :tag "Don't set ControlMaster" nil) (const :tag "Suppress ControlMaster" suppress)) - ;; Check with (safe-local-variable-p 'tramp-use-ssh-controlmaster-options 'suppress) + ;; Check with (safe-local-variable-p 'tramp-use-connection-share 'suppress) :safe (lambda (val) (and (memq val '(t nil suppress)) t))) (defvar tramp-ssh-controlmaster-options nil @@ -124,8 +127,8 @@ If it is a string, it should have the form spec must be doubled, because the string is used as format string. Otherwise, it will be auto-detected by Tramp, if -`tramp-use-ssh-controlmaster-options' is t. The value depends on -the installed local ssh version. +`tramp-use-connection-share' is t. The value depends on the +installed local ssh version. The string is used in `tramp-methods'.") @@ -279,6 +282,7 @@ The string is used in `tramp-methods'.") (tramp-copy-program "nc") ;; We use "-v" for better error tracking. (tramp-copy-args (("-w" "1") ("-v") ("%h") ("%r"))) + (tramp-copy-file-name (("%f"))) (tramp-remote-copy-program "nc") ;; We use "-p" as required for newer busyboxes. For older ;; busybox/nc versions, the value must be (("-l") ("%r")). This @@ -342,7 +346,7 @@ The string is used in `tramp-methods'.") (add-to-list 'tramp-methods `("plink" (tramp-login-program "plink") - (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") + (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") ("%c") ("-t") ("%h") ("\"") (,(format "env 'TERM=%s' 'PROMPT_COMMAND=' 'PS1=%s'" @@ -355,7 +359,7 @@ The string is used in `tramp-methods'.") (add-to-list 'tramp-methods `("plinkx" (tramp-login-program "plink") - (tramp-login-args (("-load") ("%h") ("-t") ("\"") + (tramp-login-args (("-load") ("%h") ("%c") ("-t") ("\"") (,(format "env 'TERM=%s' 'PROMPT_COMMAND=' 'PS1=%s'" tramp-terminal-type @@ -367,7 +371,7 @@ The string is used in `tramp-methods'.") (add-to-list 'tramp-methods `("pscp" (tramp-login-program "plink") - (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") + (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") ("%c") ("-t") ("%h") ("\"") (,(format "env 'TERM=%s' 'PROMPT_COMMAND=' 'PS1=%s'" @@ -385,7 +389,7 @@ The string is used in `tramp-methods'.") (add-to-list 'tramp-methods `("psftp" (tramp-login-program "plink") - (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") + (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") ("%c") ("-t") ("%h") ("\"") (,(format "env 'TERM=%s' 'PROMPT_COMMAND=' 'PS1=%s'" @@ -397,7 +401,7 @@ The string is used in `tramp-methods'.") (tramp-remote-shell-args ("-c")) (tramp-copy-program "pscp") (tramp-copy-args (("-l" "%u") ("-P" "%p") ("-sftp") - ("-p" "%k") ("-q"))) + ("-p" "%k"))) (tramp-copy-keep-date t))) (add-to-list 'tramp-methods `("fcp" @@ -412,7 +416,7 @@ The string is used in `tramp-methods'.") (add-to-list 'tramp-default-method-alist `(,tramp-local-host-regexp - ,(tramp-compat-rx bos (literal tramp-root-id-string) eos) "su")) + ,(rx bos (literal tramp-root-id-string) eos) "su")) (add-to-list 'tramp-default-user-alist `(,(rx bos (| "su" "sudo" "doas" "ksu") eos) @@ -425,6 +429,9 @@ The string is used in `tramp-methods'.") eos) nil ,(user-login-name)))) +(defconst tramp-default-copy-file-name '(("%u" "@") ("%h" ":") ("%f")) + "Default `tramp-copy-file-name' entry for out-of-band methods.") + ;;;###tramp-autoload (defconst tramp-completion-function-alist-rsh '((tramp-parse-rhosts "/etc/hosts.equiv") @@ -517,8 +524,8 @@ The string is used in `tramp-methods'.") (tramp-set-completion-function "fcp" tramp-completion-function-alist-ssh)) (defcustom tramp-sh-extra-args - `((,(rx "/bash" eos) . "-noediting -norc -noprofile") - (,(rx "/zsh" eos) . "-f +Z -V")) + `((,(rx (| bos "/") "bash" eos) . "-noediting -norc -noprofile") + (,(rx (| bos "/") "zsh" eos) . "-f +Z -V")) "Alist specifying extra arguments to pass to the remote shell. Entries are (REGEXP . ARGS) where REGEXP is a regular expression matching the shell file name and ARGS is a string specifying the @@ -529,7 +536,7 @@ This variable is only used when Tramp needs to start up another shell for tilde expansion. The extra arguments should typically prevent the shell from reading its init file." :group 'tramp - :version "27.1" + :version "30.1" :type '(alist :key-type regexp :value-type string)) (defconst tramp-actions-before-shell @@ -616,6 +623,13 @@ if (!$result) { $result = File::Spec->catpath($vol, File::Spec->catdir(@dirs), \"\"); } +if (-l $ARGV[0]) { + print \"t\\n\"; + } +else { + print \"nil\\n\"; + } + $result =~ s/\"/\\\\\"/g; print \"\\\"$result\\\"\\n\"; ' \"$1\" %n" @@ -626,16 +640,23 @@ characters need to be doubled.") (defconst tramp-perl-file-name-all-completions "%p -e ' -opendir(d, $ARGV[0]) || die(\"$ARGV[0]: $!\\nfail\\n\"); +$dir = $ARGV[0]; +if ($dir ne \"/\") { + $dir =~ s#/+$##; +} +opendir(d, $dir) || die(\"$dir: $!\\nfail\\n\"); @files = readdir(d); closedir(d); +print \"(\\n\"; foreach $f (@files) { - if (-d \"$ARGV[0]/$f\") { - print \"$f/\\n\"; - } - else { - print \"$f\\n\"; - } + ($p = $f) =~ s/\\\"/\\\\\\\"/g; + ($q = \"$dir/$f\") =~ s/\\\"/\\\\\\\"/g; + print \"(\", + ((-d \"$q\") ? \"\\\"$p/\\\" \\\"$q\\\" t\" : \"\\\"$p\\\" \\\"$q\\\" nil\"), + ((-e \"$q\") ? \" t\" : \" nil\"), + ((-r \"$q\") ? \" t\" : \" nil\"), + \")\\n\"; } +print \")\\n\"; ' \"$1\" %n" "Perl script to produce output suitable for use with `file-name-all-completions' on the remote file system. @@ -699,11 +720,37 @@ characters need to be doubled.") " '((%s%%%%N%s) %%%%h (%s%%%%U%s . %%%%u) (%s%%%%G%s . %%%%g)" " %%%%X %%%%Y %%%%Z %%%%s %s%%%%A%s t %%%%i -1)' \"$1\" %%n || echo nil) |" " sed -e 's/\"/\\\\\"/g' -e 's/%s/\"/g'") - tramp-stat-marker tramp-stat-marker ; %%N - tramp-stat-marker tramp-stat-marker ; %%U - tramp-stat-marker tramp-stat-marker ; %%G - tramp-stat-marker tramp-stat-marker ; %%A - tramp-stat-quoted-marker) + tramp-stat-marker tramp-stat-marker ; %%N + tramp-stat-marker tramp-stat-marker ; %%U + tramp-stat-marker tramp-stat-marker ; %%G + tramp-stat-marker tramp-stat-marker ; %%A + tramp-stat-quoted-marker) + "Shell function to produce output suitable for use with `file-attributes' +on the remote file system. +Format specifiers are replaced by `tramp-expand-script', percent +characters need to be doubled.") + +(defconst tramp-stat-file-attributes-with-selinux + (format + (concat + "(%%s -c" + " '((%s%%%%N%s) %%%%h (%s%%%%U%s . %%%%u) (%s%%%%G%s . %%%%g)" + " %%%%X %%%%Y %%%%Z %%%%s %s%%%%A%s t %%%%i -1 %s%%%%C%s)'" + " \"$1\" %%n || echo nil) |" + " sed -e 's/\"/\\\\\"/g' -e 's/%s/\"/g'") + tramp-stat-marker tramp-stat-marker ; %%N + tramp-stat-marker tramp-stat-marker ; %%U + tramp-stat-marker tramp-stat-marker ; %%G + tramp-stat-marker tramp-stat-marker ; %%A + tramp-stat-marker tramp-stat-marker ; %%C + tramp-stat-quoted-marker) + "Shell function to produce output suitable for use with `file-attributes' +on the remote file system, including SELinux context. +Format specifiers are replaced by `tramp-expand-script', percent +characters need to be doubled.") + +(defconst tramp-ls-file-attributes + "%s -ild %s \"$1\" || return\n%s -lnd%s %s \"$1\"" "Shell function to produce output suitable for use with `file-attributes' on the remote file system. Format specifiers are replaced by `tramp-expand-script', percent @@ -787,6 +834,33 @@ characters need to be doubled.") Format specifiers are replaced by `tramp-expand-script', percent characters need to be doubled.") +(defconst tramp-stat-directory-files-and-attributes-with-selinux + (format + (concat + ;; We must care about file names with spaces, or starting with + ;; "-"; this would confuse xargs. "ls -aQ" might be a solution, + ;; but it does not work on all remote systems. Therefore, we use + ;; \000 as file separator. `tramp-sh--quoting-style-options' do + ;; not work for file names with spaces piped to "xargs". + ;; Apostrophes in the stat output are masked as + ;; `tramp-stat-marker', in order to make a proper shell escape of + ;; them in file names. + "cd \"$1\" && echo \"(\"; (%%l -a | tr '\\n\\r' '\\000\\000' |" + " xargs -0 %%s -c" + " '(%s%%%%n%s (%s%%%%N%s) %%%%h (%s%%%%U%s . %%%%u) (%s%%%%G%s . %%%%g) %%%%X %%%%Y %%%%Z %%%%s %s%%%%A%s t %%%%i -1 %s%%%%C%s)'" + " -- %%n | sed -e 's/\"/\\\\\"/g' -e 's/%s/\"/g'); echo \")\"") + tramp-stat-marker tramp-stat-marker ; %n + tramp-stat-marker tramp-stat-marker ; %N + tramp-stat-marker tramp-stat-marker ; %U + tramp-stat-marker tramp-stat-marker ; %G + tramp-stat-marker tramp-stat-marker ; %A + tramp-stat-marker tramp-stat-marker ; %C + tramp-stat-quoted-marker) + "Shell function implementing `directory-files-and-attributes' as Lisp +`read'able output, including SELinux context. +Format specifiers are replaced by `tramp-expand-script', percent +characters need to be doubled.") + (defconst tramp-perl-id "%p -e ' use strict; @@ -1015,28 +1089,23 @@ BEGIN { Format specifiers are replaced by `tramp-expand-script', percent characters need to be doubled.") -(defconst tramp-vc-registered-read-file-names +(defconst tramp-bundle-read-file-names "echo \"(\" while read file; do quoted=`echo \"$file\" | sed -e \"s/\\\"/\\\\\\\\\\\\\\\\\\\"/\"` - if %s \"$file\"; then - echo \"(\\\"$quoted\\\" \\\"file-exists-p\\\" t)\" - else - echo \"(\\\"$quoted\\\" \\\"file-exists-p\\\" nil)\" - fi - if %s \"$file\"; then - echo \"(\\\"$quoted\\\" \\\"file-readable-p\\\" t)\" - else - echo \"(\\\"$quoted\\\" \\\"file-readable-p\\\" nil)\" - fi + printf \"(%%b\" \"\\\"$quoted\\\"\" + if %s \"$file\"; then printf \" %%b\" t; else printf \" %%b\" nil; fi + if %s \"$file\"; then printf \" %%b\" t; else printf \" %%b\" nil; fi + if %s \"$file\"; then printf \" %%b)\n\" t; else printf \" %%b)\n\" nil; fi done echo \")\"" - "Script to check existence of VC related files. -It must be send formatted with two strings; the tests for file -existence, and file readability. Input shall be read via -here-document, otherwise the command could exceed maximum length -of command line. -Format specifiers \"%s\" are replaced before the script is used.") + "Script to check file attributes of a bundle of files. +It must be sent formatted with three strings; the tests for file +existence, file readability, and file directory. Input shall be +read via here-document, otherwise the command could exceed +maximum length of command line. +Format specifiers \"%s\" are replaced before the script is used, +percent characters need to be doubled.") ;; New handlers should be added here. ;;;###tramp-autoload @@ -1067,6 +1136,7 @@ Format specifiers \"%s\" are replaced before the script is used.") (file-equal-p . tramp-handle-file-equal-p) (file-executable-p . tramp-sh-handle-file-executable-p) (file-exists-p . tramp-sh-handle-file-exists-p) + (file-group-gid . tramp-handle-file-group-gid) (file-in-directory-p . tramp-handle-file-in-directory-p) (file-local-copy . tramp-sh-handle-file-local-copy) (file-locked-p . tramp-handle-file-locked-p) @@ -1090,6 +1160,7 @@ Format specifiers \"%s\" are replaced before the script is used.") (file-symlink-p . tramp-handle-file-symlink-p) (file-system-info . tramp-sh-handle-file-system-info) (file-truename . tramp-sh-handle-file-truename) + (file-user-uid . tramp-handle-file-user-uid) (file-writable-p . tramp-sh-handle-file-writable-p) (find-backup-file-name . tramp-handle-find-backup-file-name) ;; `get-file-buffer' performed by default handler. @@ -1135,121 +1206,65 @@ Operations not mentioned here will be handled by the normal Emacs functions.") (defun tramp-sh-handle-make-symbolic-link (target linkname &optional ok-if-already-exists) - "Like `make-symbolic-link' for Tramp files. -If TARGET is a non-Tramp file, it is used verbatim as the target -of the symlink. If TARGET is a Tramp file, only the localname -component is used as the target of the symlink." - (with-parsed-tramp-file-name (expand-file-name linkname) nil - ;; If TARGET is a Tramp name, use just the localname component. - ;; Don't check for a proper method. - (let ((non-essential t)) - (when (and (tramp-tramp-file-p target) - (tramp-file-name-equal-p v (tramp-dissect-file-name target))) - (setq target (tramp-file-local-name (expand-file-name target)))) - ;; There could be a cyclic link. - (tramp-flush-file-properties - v (expand-file-name target (tramp-file-local-name default-directory)))) - - ;; If TARGET is still remote, quote it. - (if (tramp-tramp-file-p target) - (make-symbolic-link - (tramp-compat-file-name-quote target 'top) - linkname ok-if-already-exists) - - (let ((ln (tramp-get-remote-ln v)) - (cwd (tramp-run-real-handler - #'file-name-directory (list localname)))) - (unless ln - (tramp-error - v 'file-error - (concat "Making a symbolic link: " - "ln(1) does not exist on the remote host"))) + "Like `make-symbolic-link' for Tramp files." + (let ((v (tramp-dissect-file-name (expand-file-name linkname)))) + (unless (tramp-get-remote-ln v) + (tramp-error + v 'file-error + (concat "Making a symbolic link: " + "ln(1) does not exist on the remote host")))) - ;; Do the 'confirm if exists' thing. - (when (file-exists-p linkname) - ;; What to do? - (if (or (null ok-if-already-exists) ; not allowed to exist - (and (numberp ok-if-already-exists) - (not - (yes-or-no-p - (format - "File %s already exists; make it a link anyway?" - localname))))) - (tramp-error v 'file-already-exists localname) - (delete-file linkname))) - - (tramp-flush-file-properties v localname) - - ;; Right, they are on the same host, regardless of user, - ;; method, etc. We now make the link on the remote machine. - ;; This will occur as the user that TARGET belongs to. - (and (tramp-send-command-and-check - v (format "cd %s" (tramp-shell-quote-argument cwd))) - (tramp-send-command-and-check - v (format - "%s -sf %s %s" ln - (tramp-shell-quote-argument target) - ;; The command could exceed PATH_MAX, so we use - ;; relative file names. However, relative file names - ;; could start with "-". - ;; `tramp-shell-quote-argument' does not handle this, - ;; we must do it ourselves. - (tramp-shell-quote-argument - (concat "./" (file-name-nondirectory localname)))))))))) + (tramp-skeleton-make-symbolic-link target linkname ok-if-already-exists + (tramp-send-command-and-check + v (format + "cd %s && %s -sf %s %s" + (tramp-shell-quote-argument (file-name-directory localname)) + (tramp-get-remote-ln v) + (tramp-shell-quote-argument target) + ;; The command could exceed PATH_MAX, so we use relative + ;; file names. + (tramp-shell-quote-argument + (concat "./" (file-name-nondirectory localname))))))) (defun tramp-sh-handle-file-truename (filename) "Like `file-truename' for Tramp files." - ;; Preserve trailing "/". - (funcall - (if (directory-name-p filename) #'file-name-as-directory #'identity) - ;; 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" - (tramp-message v 4 "Finding true name for `%s'" filename) - (let ((result - (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)) - (buffer-substring (point-min) (line-end-position)))) - - ;; Use Perl implementation. - ((and (tramp-get-remote-perl v) - (tramp-get-connection-property v "perl-file-spec") - (tramp-get-connection-property v "perl-cwd-realpath")) - (tramp-maybe-send-script - v tramp-perl-file-truename "tramp_perl_file_truename") - (tramp-send-command-and-read - v (format "tramp_perl_file_truename %s" - (tramp-shell-quote-argument localname)))) - - ;; Do it yourself. - (t (tramp-file-local-name - (tramp-handle-file-truename filename)))))) - - ;; 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))))))) + (tramp-skeleton-file-truename filename + (cond + ;; Use GNU readlink --canonicalize-missing where available. + ((tramp-get-remote-readlink v) + (tramp-send-command-and-check + v (format + (concat + "(if %s -h \"%s\"; then echo t; else echo nil; fi) && " + "%s --canonicalize-missing %s") + (tramp-get-test-command v) + (tramp-shell-quote-argument localname) + (tramp-get-remote-readlink v) + (tramp-shell-quote-argument localname))) + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (tramp-set-file-property v localname "file-symlink-marker" (read (current-buffer))) + ;; We cannot call `read', the file name isn't quoted. + (forward-line) + (buffer-substring (point) (line-end-position)))) + + ;; Use Perl implementation. + ((and (tramp-get-remote-perl v) + (tramp-get-connection-property v "perl-file-spec") + (tramp-get-connection-property v "perl-cwd-realpath")) + (tramp-maybe-send-script + v tramp-perl-file-truename "tramp_perl_file_truename") + (tramp-send-command-and-check + v (format "tramp_perl_file_truename %s" + (tramp-shell-quote-argument localname))) + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (tramp-set-file-property v localname "file-symlink-marker" (read (current-buffer))) + (read (current-buffer)))) + + ;; Do it yourself. + (t (tramp-file-local-name + (tramp-handle-file-truename filename)))))) ;; Basic functions. @@ -1280,6 +1295,9 @@ component is used as the target of the symlink." (defconst tramp-sunos-unames (rx (| "SunOS 5.10" "SunOS 5.11")) "Regexp to determine remote SunOS.") +(defconst tramp-bsd-unames (rx (| "BSD" "DragonFly" "Darwin")) + "Regexp to determine remote *BSD and macOS.") + (defun tramp-sh--quoting-style-options (vec) "Quoting style options to be used for VEC." (or @@ -1293,42 +1311,25 @@ component is used as the target of the symlink." (defun tramp-do-file-attributes-with-ls (vec localname) "Implement `file-attributes' for Tramp files using the ls(1) command." - (let (symlinkp dirp + (tramp-message vec 5 "file attributes with ls: %s" localname) + (let ((tramp-ls-file-attributes + (format tramp-ls-file-attributes + (tramp-get-ls-command vec) + ;; On systems which have no quoting style, file + ;; names with special characters could fail. + (tramp-sh--quoting-style-options vec) + (tramp-get-ls-command vec) + (if (tramp-remote-selinux-p vec) "Z" "") + (tramp-sh--quoting-style-options vec))) + symlinkp dirp res-inode res-filemodes res-numlinks res-uid-string res-gid-string res-uid-integer res-gid-integer - res-size res-symlink-target) - (tramp-message vec 5 "file attributes with ls: %s" localname) - ;; We cannot send all three commands combined, it could exceed - ;; NAME_MAX or PATH_MAX. Happened on macOS, for example. + res-size res-symlink-target res-context) + (tramp-maybe-send-script + vec tramp-ls-file-attributes "tramp_ls_file_attributes") (when (tramp-send-command-and-check - vec - (format "cd %s && (%s %s || %s -h %s)" - (tramp-shell-quote-argument - (tramp-run-real-handler - #'file-name-directory (list localname))) - (tramp-get-file-exists-command vec) - (if (string-empty-p (file-name-nondirectory localname)) - "." - (tramp-shell-quote-argument - (file-name-nondirectory localname))) - (tramp-get-test-command vec) - (if (string-empty-p (file-name-nondirectory localname)) - "." - (tramp-shell-quote-argument - (file-name-nondirectory localname))))) - (tramp-send-command - vec - (format "%s -ild %s %s; %s -lnd %s %s" - (tramp-get-ls-command vec) - ;; On systems which have no quoting style, file names - ;; with special characters could fail. - (tramp-sh--quoting-style-options vec) - (tramp-shell-quote-argument localname) - (tramp-get-ls-command vec) - ;; On systems which have no quoting style, file names - ;; with special characters could fail. - (tramp-sh--quoting-style-options vec) - (tramp-shell-quote-argument localname))) + vec (format "tramp_ls_file_attributes %s" + (tramp-shell-quote-argument localname))) ;; Parse `ls -l' output ... (with-current-buffer (tramp-get-buffer vec) (when (> (buffer-size) 0) @@ -1374,6 +1375,10 @@ component is used as the target of the symlink." (setq res-uid-integer tramp-unknown-id-integer)) (unless (numberp res-gid-integer) (setq res-gid-integer tramp-unknown-id-integer)) + ;; ... SELinux context + (when (tramp-remote-selinux-p vec) + (setq res-context (read (current-buffer)) + res-context (symbol-name res-context))) ;; Return data gathered. (list @@ -1400,7 +1405,10 @@ component is used as the target of the symlink." ;; 10. Inode number. res-inode ;; 11. Device number. Will be replaced by a virtual device number. - -1)))))) + -1 + ;; 12. SELinux context. Will be extracted in + ;; `tramp-convert-file-attributes'. + res-context)))))) (defun tramp-do-file-attributes-with-perl (vec localname) "Implement `file-attributes' for Tramp files using a Perl script." @@ -1414,11 +1422,20 @@ component is used as the target of the symlink." (defun tramp-do-file-attributes-with-stat (vec localname) "Implement `file-attributes' for Tramp files using stat(1) command." (tramp-message vec 5 "file attributes with stat: %s" localname) - (tramp-maybe-send-script - vec tramp-stat-file-attributes "tramp_stat_file_attributes") - (tramp-send-command-and-read - vec (format "tramp_stat_file_attributes %s" - (tramp-shell-quote-argument localname)))) + (cond + ((tramp-remote-selinux-p vec) + (tramp-maybe-send-script + vec tramp-stat-file-attributes-with-selinux + "tramp_stat_file_attributes_with_selinux") + (tramp-send-command-and-read + vec (format "tramp_stat_file_attributes_with_selinux %s" + (tramp-shell-quote-argument localname)))) + (t + (tramp-maybe-send-script + vec tramp-stat-file-attributes "tramp_stat_file_attributes") + (tramp-send-command-and-read + vec (format "tramp_stat_file_attributes %s" + (tramp-shell-quote-argument localname)))))) (defun tramp-sh-handle-set-visited-file-modtime (&optional time-list) "Like `set-visited-file-modtime' for Tramp files." @@ -1435,7 +1452,7 @@ component is used as the target of the symlink." (modtime (or (file-attribute-modification-time attr) tramp-time-doesnt-exist))) (setq coding-system-used last-coding-system-used) - (if (not (tramp-compat-time-equal-p modtime tramp-time-dont-know)) + (if (not (time-equal-p modtime tramp-time-dont-know)) (tramp-run-real-handler #'set-visited-file-modtime (list modtime)) (progn (tramp-send-command @@ -1475,9 +1492,7 @@ of." (cond ;; File exists, and has a known modtime. - ((and attr - (not - (tramp-compat-time-equal-p modtime tramp-time-dont-know))) + ((and attr (not (time-equal-p modtime tramp-time-dont-know))) (< (abs (tramp-time-diff modtime mt)) 2)) ;; Modtime has the don't know value. (attr @@ -1494,7 +1509,7 @@ of." v localname "visited-file-modtime-ild" ""))) ;; If file does not exist, say it is not modified if and ;; only if that agrees with the buffer's record. - (t (tramp-compat-time-equal-p mt tramp-time-doesnt-exist))))))))) + (t (time-equal-p mt tramp-time-doesnt-exist))))))))) (defun tramp-sh-handle-set-file-modes (filename mode &optional flag) "Like `set-file-modes' for Tramp files." @@ -1516,21 +1531,17 @@ of." "Like `set-file-times' for Tramp files." (tramp-skeleton-set-file-modes-times-uid-gid filename (when (tramp-get-remote-touch v) - (let ((time - (if (or (null time) - (tramp-compat-time-equal-p time tramp-time-doesnt-exist) - (tramp-compat-time-equal-p time tramp-time-dont-know)) - nil - time))) - (tramp-send-command-and-check - v (format - "env TZ=UTC0 %s %s %s %s" - (tramp-get-remote-touch v) - (if (tramp-get-connection-property v "touch-t") - (format "-t %s" (format-time-string "%Y%m%d%H%M.%S" time t)) - "") - (if (eq flag 'nofollow) "-h" "") - (tramp-shell-quote-argument localname))))))) + (tramp-send-command-and-check + v (format + "env TZ=UTC0 %s %s %s %s" + (tramp-get-remote-touch v) + (if (tramp-get-connection-property v "touch-t") + (format + "-t %s" + (format-time-string "%Y%m%d%H%M.%S" (tramp-defined-time time) t)) + "") + (if (eq flag 'nofollow) "-h" "") + (tramp-shell-quote-argument localname)))))) (defun tramp-sh-handle-get-home-directory (vec &optional user) "The remote home directory for connection VEC as local file name. @@ -1619,7 +1630,7 @@ ID-FORMAT valid values are `string' and `integer'." (tramp-shell-quote-argument localname)))))))) (defun tramp-remote-selinux-p (vec) - "Check, whether SELINUX is enabled on the remote host." + "Check, whether SELinux is enabled on the remote host." (with-tramp-connection-property (tramp-get-process vec) "selinux-p" (tramp-send-command-and-check vec "selinuxenabled"))) @@ -1628,7 +1639,7 @@ ID-FORMAT valid values are `string' and `integer'." (with-parsed-tramp-file-name (expand-file-name filename) nil (with-tramp-file-property v localname "file-selinux-context" (let ((context '(nil nil nil nil)) - (regexp (tramp-compat-rx + (regexp (rx (group (+ (any "_" alnum))) ":" (group (+ (any "_" alnum))) ":" (group (+ (any "_" alnum))) ":" @@ -1641,7 +1652,7 @@ ID-FORMAT valid values are `string' and `integer'." (tramp-shell-quote-argument localname)))) (with-current-buffer (tramp-get-connection-buffer v) (goto-char (point-min)) - (when (re-search-forward regexp (line-end-position) t) + (when (search-forward-regexp regexp (line-end-position) t) (setq context (list (match-string 1) (match-string 2) (match-string 3) (match-string 4)))))) ;; Return the context. @@ -1745,8 +1756,8 @@ ID-FORMAT valid values are `string' and `integer'." (with-tramp-file-property v localname "file-directory-p" (if-let ((truename (tramp-get-file-property v localname "file-truename")) - (attr-p (tramp-file-property-p - v (tramp-file-local-name truename) "file-attributes"))) + ((tramp-file-property-p + v (tramp-file-local-name truename) "file-attributes"))) (eq (file-attribute-type (tramp-get-file-property v (tramp-file-local-name truename) "file-attributes")) @@ -1758,9 +1769,9 @@ ID-FORMAT valid values are `string' and `integer'." (with-parsed-tramp-file-name (expand-file-name filename) nil (with-tramp-file-property v localname "file-writable-p" (if (file-exists-p filename) + ;; Examine `file-attributes' cache to see if request can be + ;; satisfied without remote operation. (if (tramp-file-property-p v localname "file-attributes") - ;; Examine `file-attributes' cache to see if request can - ;; be satisfied without remote operation. (tramp-check-cached-permissions v ?w) (tramp-run-test v "-w" localname)) ;; If file doesn't exist, check if directory is writable. @@ -1785,7 +1796,7 @@ ID-FORMAT valid values are `string' and `integer'." ;; On BSD-derived systems files always inherit the ;; parent directory's group, so skip the group-gid ;; test. - (tramp-check-remote-uname v (rx (| "BSD" "DragonFly" "Darwin"))) + (tramp-check-remote-uname v tramp-bsd-unames) (= (file-attribute-group-id attributes) (tramp-get-remote-gid v 'integer))))))))) @@ -1822,12 +1833,21 @@ ID-FORMAT valid values are `string' and `integer'." (defun tramp-do-directory-files-and-attributes-with-stat (vec localname) "Implement `directory-files-and-attributes' for Tramp files with stat(1) command." (tramp-message vec 5 "directory-files-and-attributes with stat: %s" localname) - (tramp-maybe-send-script - vec tramp-stat-directory-files-and-attributes - "tramp_stat_directory_files_and_attributes") - (tramp-send-command-and-read - vec (format "tramp_stat_directory_files_and_attributes %s" - (tramp-shell-quote-argument localname)))) + (cond + ((tramp-remote-selinux-p vec) + (tramp-maybe-send-script + vec tramp-stat-directory-files-and-attributes-with-selinux + "tramp_stat_directory_files_and_attributes_with_selinux") + (tramp-send-command-and-read + vec (format "tramp_stat_directory_files_and_attributes_with_selinux %s" + (tramp-shell-quote-argument localname)))) + (t + (tramp-maybe-send-script + vec tramp-stat-directory-files-and-attributes + "tramp_stat_directory_files_and_attributes") + (tramp-send-command-and-read + vec (format "tramp_stat_directory_files_and_attributes %s" + (tramp-shell-quote-argument localname)))))) ;; This function should return "foo/" for directories and "bar" for ;; files. @@ -1845,35 +1865,48 @@ ID-FORMAT valid values are `string' and `integer'." ;; Get a list of directories and files, including ;; reliably tagging the directories with a trailing "/". ;; Because I rock. --daniel@danann.net - (when (tramp-send-command-and-check - v - (if (tramp-get-remote-perl v) - (progn - (tramp-maybe-send-script - v tramp-perl-file-name-all-completions - "tramp_perl_file_name_all_completions") - (format "tramp_perl_file_name_all_completions %s" - (tramp-shell-quote-argument localname))) - - (format (concat - "cd %s 2>&1 && %s -a 2>%s" - " | while IFS= read f; do" - " if %s -d \"$f\" 2>%s;" - " then \\echo \"$f/\"; else \\echo \"$f\"; fi;" - " done") - (tramp-shell-quote-argument localname) - (tramp-get-ls-command v) - (tramp-get-remote-null-device v) - (tramp-get-test-command v) - (tramp-get-remote-null-device v)))) - - ;; Now grab the output. - (with-current-buffer (tramp-get-buffer v) - (goto-char (point-max)) - (while (zerop (forward-line -1)) - (push - (buffer-substring (point) (line-end-position)) result))) - result))))))))) + (if (tramp-get-remote-perl v) + (progn + (tramp-maybe-send-script + v tramp-perl-file-name-all-completions + "tramp_perl_file_name_all_completions") + (setq result + (tramp-send-command-and-read + v (format "tramp_perl_file_name_all_completions %s" + (tramp-shell-quote-argument localname)) + 'noerror)) + ;; Cached values. + (dolist (elt result) + (tramp-set-file-property + v (cadr elt) "file-directory-p" (nth 2 elt)) + (tramp-set-file-property + v (cadr elt) "file-exists-p" (nth 3 elt)) + (tramp-set-file-property + v (cadr elt) "file-readable-p" (nth 4 elt))) + ;; Result. + (mapcar #'car result)) + + ;; Do it with ls. + (when (tramp-send-command-and-check + v (format (concat + "cd %s 2>&1 && %s -a 2>%s" + " | while IFS= read f; do" + " if %s -d \"$f\" 2>%s;" + " then echo \"$f/\"; else echo \"$f\"; fi;" + " done") + (tramp-shell-quote-argument localname) + (tramp-get-ls-command v) + (tramp-get-remote-null-device v) + (tramp-get-test-command v) + (tramp-get-remote-null-device v))) + + ;; Now grab the output. + (with-current-buffer (tramp-get-buffer v) + (goto-char (point-max)) + (while (zerop (forward-line -1)) + (push + (buffer-substring (point) (line-end-position)) result))) + result)))))))))) ;; cp, mv and ln @@ -1982,7 +2015,7 @@ ID-FORMAT valid values are `string' and `integer'." #'copy-directory (list dirname newname keep-date parents copy-contents)))) - ;; When newname did exist, we have wrong cached values. + ;; NEWNAME has wrong cached values. (when t2 (with-parsed-tramp-file-name (expand-file-name newname) nil (tramp-flush-file-properties v localname))))))) @@ -2014,7 +2047,7 @@ OK-IF-ALREADY-EXISTS means don't barf if NEWNAME exists already. KEEP-DATE means to make sure that NEWNAME has the same timestamp as FILENAME. PRESERVE-UID-GID, when non-nil, instructs to keep the uid and gid if both files are on the same host. -PRESERVE-EXTENDED-ATTRIBUTES activates selinux and acl commands. +PRESERVE-EXTENDED-ATTRIBUTES activates SELinux and ACL commands. This function is invoked by `tramp-sh-handle-copy-file' and `tramp-sh-handle-rename-file'. It is an error if OP is neither @@ -2037,7 +2070,11 @@ file names." (t2 (tramp-tramp-file-p newname)) (length (file-attribute-size (file-attributes (file-truename filename)))) - (msg-operation (if (eq op 'copy) "Copying" "Renaming"))) + (file-times (file-attribute-modification-time + (file-attributes filename))) + (file-modes (tramp-default-file-modes filename)) + (msg-operation (if (eq op 'copy) "Copying" "Renaming")) + copy-keep-date) (with-parsed-tramp-file-name (if t1 filename newname) nil (unless length @@ -2062,6 +2099,8 @@ file names." ;; both files, we invoke `cp' or `mv' on the remote ;; host directly. ((tramp-equal-remote filename newname) + (setq copy-keep-date + (or (eq op 'rename) keep-date preserve-uid-gid)) (tramp-do-copy-or-rename-file-directly op filename newname ok-if-already-exists keep-date preserve-uid-gid)) @@ -2070,6 +2109,8 @@ file names." ((and (tramp-method-out-of-band-p v1 length) (tramp-method-out-of-band-p v2 length)) + (setq copy-keep-date + (tramp-get-method-parameter v 'tramp-copy-keep-date)) (tramp-do-copy-or-rename-file-out-of-band op filename newname ok-if-already-exists keep-date)) @@ -2091,6 +2132,8 @@ file names." (cond ;; Fast track on local machine. ((tramp-local-host-p v) + (setq copy-keep-date + (or (eq op 'rename) keep-date preserve-uid-gid)) (tramp-do-copy-or-rename-file-directly op filename newname ok-if-already-exists keep-date preserve-uid-gid)) @@ -2098,6 +2141,8 @@ file names." ;; If the Tramp file has an out-of-band method, the ;; corresponding copy-program can be invoked. ((tramp-method-out-of-band-p v length) + (setq copy-keep-date + (tramp-get-method-parameter v 'tramp-copy-keep-date)) (tramp-do-copy-or-rename-file-out-of-band op filename newname ok-if-already-exists keep-date)) @@ -2109,6 +2154,16 @@ file names." ;; One of them must be a Tramp file. (error "Tramp implementation says this cannot happen"))) + ;; In case of `rename', we must flush the cache of the source file. + (when (and t1 (eq op 'rename)) + (with-parsed-tramp-file-name filename v1 + (tramp-flush-file-properties v1 v1-localname))) + + ;; NEWNAME has wrong cached values. + (when t2 + (with-parsed-tramp-file-name newname v2 + (tramp-flush-file-properties v2 v2-localname))) + ;; Handle `preserve-extended-attributes'. We ignore ;; possible errors, because ACL strings could be ;; incompatible. @@ -2117,18 +2172,17 @@ file names." (ignore-errors (set-file-extended-attributes newname attributes))) - ;; In case of `rename', we must flush the cache of the source file. - (when (and t1 (eq op 'rename)) - (with-parsed-tramp-file-name filename v1 - (tramp-flush-file-properties v1 v1-localname))) + ;; KEEP-DATE handling. + (when (and keep-date (not copy-keep-date)) + (tramp-compat-set-file-times + newname file-times (unless ok-if-already-exists 'nofollow))) - ;; When newname did exist, we have wrong cached values. - (when t2 - (with-parsed-tramp-file-name newname v2 - (tramp-flush-file-properties v2 v2-localname))))))))) + ;; Set the mode. + (unless (and keep-date copy-keep-date) + (set-file-modes newname file-modes)))))))) (defun tramp-do-copy-or-rename-file-via-buffer - (op filename newname ok-if-already-exists keep-date) + (op filename newname _ok-if-already-exists _keep-date) "Use an Emacs buffer to copy or rename a file. First arg OP is either `copy' or `rename' and indicates the operation. FILENAME is the source file, NEWNAME the target file. @@ -2155,14 +2209,7 @@ KEEP-DATE is non-nil if NEWNAME should have the same timestamp as FILENAME." (with-temp-file newname (set-buffer-multibyte nil) (insert-file-contents-literally filename))) - ;; KEEP-DATE handling. - (when keep-date - (tramp-compat-set-file-times - newname - (file-attribute-modification-time (file-attributes filename)) - (unless ok-if-already-exists 'nofollow))) - ;; Set the mode. - (set-file-modes newname (tramp-default-file-modes filename)) + ;; If the operation was `rename', delete the original file. (unless (eq op 'copy) (delete-file filename))) @@ -2178,12 +2225,10 @@ as FILENAME. PRESERVE-UID-GID, when non-nil, instructs to keep the uid and gid from FILENAME." ;; FILENAME and NEWNAME are already expanded. (let ((t1 (tramp-tramp-file-p filename)) - (t2 (tramp-tramp-file-p newname)) - (file-times (file-attribute-modification-time - (file-attributes filename))) - (file-modes (tramp-default-file-modes filename))) + (t2 (tramp-tramp-file-p newname))) (with-parsed-tramp-file-name (if t1 filename newname) nil - (let* ((cmd (cond ((and (eq op 'copy) preserve-uid-gid) "cp -f -p") + (let* ((cmd (cond ((and (eq op 'copy) (or keep-date preserve-uid-gid)) + "cp -f -p") ((eq op 'copy) "cp -f") ((eq op 'rename) "mv -f") (t (tramp-error @@ -2211,7 +2256,7 @@ the uid and gid from FILENAME." (or (and keep-date ;; Mask cp -f error. - (re-search-forward + (search-forward-regexp tramp-operation-not-permitted-regexp nil t)) cmd-result) (tramp-error-with-buffer @@ -2312,19 +2357,7 @@ the uid and gid from FILENAME." (list tmpfile localname2 ok-if-already-exists))))) ;; Save exit. - (ignore-errors (delete-file tmpfile))))))))) - - ;; When newname did exist, we have wrong cached values. - (when t2 - (with-parsed-tramp-file-name newname v2 - (tramp-flush-file-properties v2 v2-localname))) - - ;; Set the time and mode. Mask possible errors. - (ignore-errors - (when keep-date - (tramp-compat-set-file-times - newname file-times (unless ok-if-already-exists 'nofollow)) - (set-file-modes newname file-modes)))))) + (ignore-errors (delete-file tmpfile)))))))))))) (defun tramp-do-copy-or-rename-file-out-of-band (op filename newname ok-if-already-exists keep-date) @@ -2336,7 +2369,7 @@ The method used must be an out-of-band method." (v2 (and (tramp-tramp-file-p newname) (tramp-dissect-file-name newname))) (v (or v1 v2)) - copy-program copy-args copy-env copy-keep-date listener spec + copy-program copy-args copy-env listener spec options source target remote-copy-program remote-copy-args p) (if (and v1 v2 (string-empty-p (tramp-scp-direct-remote-copying v1 v2))) @@ -2370,11 +2403,11 @@ The method used must be an out-of-band method." #'file-name-as-directory #'identity) (if v1 - (tramp-make-copy-program-file-name v1) - (tramp-compat-file-name-unquote filename))) + (tramp-make-copy-file-name v1) + (file-name-unquote filename))) target (if v2 - (tramp-make-copy-program-file-name v2) - (tramp-compat-file-name-unquote newname))) + (tramp-make-copy-file-name v2) + (file-name-unquote newname))) ;; Check for listener port. (when (tramp-get-method-parameter v 'tramp-remote-copy-args) @@ -2408,26 +2441,24 @@ The method used must be an out-of-band method." ?y (tramp-scp-force-scp-protocol v) ?z (tramp-scp-direct-remote-copying v1 v2)) copy-program (tramp-get-method-parameter v 'tramp-copy-program) - copy-keep-date (tramp-get-method-parameter - v 'tramp-copy-keep-date) copy-args ;; " " has either been a replacement of "%k" (when - ;; keep-date argument is non-nil), or a replacement for + ;; KEEP-DATE argument is non-nil), or a replacement for ;; the whole keep-date sublist. - (delete " " (apply #'tramp-expand-args v 'tramp-copy-args spec)) + (delete " " (apply #'tramp-expand-args v 'tramp-copy-args nil spec)) ;; `tramp-ssh-controlmaster-options' is a string instead ;; of a list. Unflatten it. copy-args - (tramp-compat-flatten-tree + (flatten-tree (mapcar (lambda (x) (if (tramp-compat-string-search " " x) (split-string x) x)) copy-args)) - copy-env (apply #'tramp-expand-args v 'tramp-copy-env spec) + copy-env (apply #'tramp-expand-args v 'tramp-copy-env nil spec) remote-copy-program (tramp-get-method-parameter v 'tramp-remote-copy-program) remote-copy-args - (apply #'tramp-expand-args v 'tramp-remote-copy-args spec)) + (apply #'tramp-expand-args v 'tramp-remote-copy-args nil spec)) ;; Check for local copy program. (unless (executable-find copy-program) @@ -2445,8 +2476,7 @@ The method used must be an out-of-band method." v 'file-error "Cannot find remote listener: %s" remote-copy-program)) (setq remote-copy-program - (mapconcat - #'identity + (string-join (append (list remote-copy-program) remote-copy-args (list (if v1 (concat "<" source) (concat ">" target)) "&")) @@ -2498,14 +2528,11 @@ The method used must be an out-of-band method." (tramp-get-connection-name v) (tramp-get-connection-buffer v) copy-program copy-args))) - (tramp-message v 6 "%s" (string-join (process-command p) " ")) - (process-put p 'tramp-vector v) ;; This is needed for ssh or PuTTY based processes, and ;; only if the respective options are set. Perhaps, ;; the setting could be more fine-grained. ;; (process-put p 'tramp-shared-socket t) - (process-put p 'adjust-window-size-function #'ignore) - (set-process-query-on-exit-flag p nil) + (tramp-post-process-creation p v) ;; We must adapt `tramp-local-end-of-line' for sending ;; the password. Also, we indicate that perhaps @@ -2521,25 +2548,7 @@ The method used must be an out-of-band method." ;; Houston, we have a problem! Likely, the listener is ;; still running, so let's clear everything (but the ;; cached password). - (tramp-cleanup-connection v 'keep-debug 'keep-password)))) - - ;; The cached file properties might be wrong if NEWNAME didn't - ;; exist. Flush them. - (when v2 - (with-parsed-tramp-file-name newname v2 - (tramp-flush-file-properties v2 v2-localname))) - - ;; Handle KEEP-DATE argument. - (when (and keep-date (not copy-keep-date)) - (tramp-compat-set-file-times - newname - (file-attribute-modification-time (file-attributes filename)) - (unless ok-if-already-exists 'nofollow))) - - ;; Set the mode. - (unless (and keep-date copy-keep-date) - (ignore-errors - (set-file-modes newname (tramp-default-file-modes filename))))) + (tramp-cleanup-connection v 'keep-debug 'keep-password))))) ;; If the operation was `rename', delete the original file. (unless (eq op 'copy) @@ -2549,19 +2558,10 @@ The method used must be an out-of-band method." (defun tramp-sh-handle-make-directory (dir &optional parents) "Like `make-directory' for Tramp files." - (setq dir (expand-file-name dir)) - (with-parsed-tramp-file-name dir nil - (when (and (null parents) (file-exists-p dir)) - (tramp-error v 'file-already-exists dir)) - ;; When PARENTS is non-nil, DIR could be a chain of non-existent - ;; directories a/b/c/... Instead of checking, we simply flush the - ;; whole cache. - (tramp-flush-directory-properties - v (if parents "/" (file-name-directory localname))) + (tramp-skeleton-make-directory dir parents (tramp-barf-unless-okay v (format "%s -m %#o %s" - (if parents "mkdir -p" "mkdir") - (default-file-modes) + "mkdir" (default-file-modes) (tramp-shell-quote-argument localname)) "Couldn't make directory %s" dir))) @@ -2576,14 +2576,10 @@ The method used must be an out-of-band method." (defun tramp-sh-handle-delete-file (filename &optional trash) "Like `delete-file' for Tramp files." - (setq filename (expand-file-name (expand-file-name filename))) - (with-parsed-tramp-file-name filename nil - (if (and delete-by-moving-to-trash trash) - (move-file-to-trash filename) - (tramp-barf-unless-okay - v (format "rm -f %s" (tramp-shell-quote-argument localname)) - "Couldn't delete %s" filename)) - (tramp-flush-file-properties v localname))) + (tramp-skeleton-delete-file filename trash + (tramp-barf-unless-okay + v (format "rm -f %s" (tramp-shell-quote-argument localname)) + "Couldn't delete %s" filename))) ;; Dired. @@ -2645,21 +2641,23 @@ The method used must be an out-of-band method." (defun tramp-sh-handle-insert-directory (filename switches &optional wildcard full-directory-p) "Like `insert-directory' for Tramp files." - (unless switches (setq switches "")) - ;; Check, whether directory is accessible. - (unless wildcard - (access-file filename "Reading directory")) - (with-parsed-tramp-file-name (expand-file-name filename) nil - (if (and (featurep 'ls-lisp) - (not ls-lisp-use-insert-directory-program)) - (tramp-handle-insert-directory - filename switches wildcard full-directory-p) - (when (stringp switches) - (setq switches (split-string switches))) - (setq switches - (append switches (split-string (tramp-sh--quoting-style-options v)))) - (unless (tramp-get-ls-command-with v "--dired") - (setq switches (delete "-N" (delete "--dired" switches)))) + (if (and (boundp 'ls-lisp-use-insert-directory-program) + (not ls-lisp-use-insert-directory-program)) + (tramp-handle-insert-directory + filename switches wildcard full-directory-p) + (unless switches (setq switches "")) + ;; Check, whether directory is accessible. + (unless wildcard + (access-file filename "Reading directory")) + (with-parsed-tramp-file-name (expand-file-name filename) nil + (let ((dired (tramp-get-ls-command-with v "--dired"))) + (when (stringp switches) + (setq switches (split-string switches))) + (setq switches + (append switches (split-string (tramp-sh--quoting-style-options v)) + (when dired `(,dired)))) + (unless dired + (setq switches (delete "-N" (delete "--dired" switches))))) (when wildcard (setq wildcard (tramp-run-real-handler #'file-name-nondirectory (list localname))) @@ -2667,7 +2665,8 @@ The method used must be an out-of-band method." #'file-name-directory (list localname)))) (unless (or full-directory-p (member "-d" switches)) (setq switches (append switches '("-d")))) - (setq switches (mapconcat #'tramp-shell-quote-argument switches " ")) + (setq switches (delete-dups switches) + switches (mapconcat #'tramp-shell-quote-argument switches " ")) (when wildcard (setq switches (concat switches " " wildcard))) (tramp-message @@ -2720,7 +2719,7 @@ The method used must be an out-of-band method." (save-restriction (narrow-to-region beg-marker end-marker) ;; Check for "--dired" output. - (when (re-search-backward + (when (search-backward-regexp (rx bol "//DIRED//" (+ blank) (group (+ nonl)) eol) nil 'noerror) (let ((beg (match-beginning 1)) @@ -2735,7 +2734,7 @@ The method used must be an out-of-band method." (put-text-property start end 'dired-filename t)))))) ;; Remove trailing lines. (goto-char (point-max)) - (while (re-search-backward (rx bol "//") nil 'noerror) + (while (search-backward-regexp (rx bol "//") nil 'noerror) (forward-line 1) (delete-region (match-beginning 0) (point)))) ;; Reset multibyte if needed. @@ -2747,7 +2746,7 @@ The method used must be an out-of-band method." (unless (tramp-compat-string-search "color" (tramp-get-connection-property v "ls" "")) (goto-char (point-min)) - (while (re-search-forward ansi-color-control-seq-regexp nil t) + (while (search-forward-regexp ansi-color-control-seq-regexp nil t) (replace-match ""))) ;; Now decode what read if necessary. Stolen from `insert-directory'. @@ -2794,7 +2793,8 @@ The method used must be an out-of-band method." ;; Try to insert the amount of free space. (goto-char (point-min)) ;; First find the line to put it on. - (when (and (re-search-forward (rx bol (group (* blank) "total")) nil t) + (when (and (search-forward-regexp + (rx bol (group (* blank) "total")) nil t) ;; Emacs 29.1 or later. (not (fboundp 'dired--insert-disk-space))) (when-let ((available (get-free-disk-space "."))) @@ -2823,8 +2823,7 @@ the result will be a local, non-Tramp, file name." ;; there could be the false positive "/:". (if (or (and (eq system-type 'windows-nt) (string-match-p - (tramp-compat-rx - bol (| (: alpha ":") (: (literal (or null-device "")) eol))) + (rx bol (| (: alpha ":") (: (literal (or null-device "")) eol))) name)) (and (not (tramp-tramp-file-p name)) (not (tramp-tramp-file-p dir)))) @@ -2848,9 +2847,7 @@ the result will be a local, non-Tramp, file name." ;; supposed to find such a shell on the remote host. Please ;; tell me about it when this doesn't work on your system. (when (string-match - (tramp-compat-rx - bos "~" (group (* (not "/"))) (group (* nonl)) eos) - localname) + (rx bos "~" (group (* (not "/"))) (group (* nonl)) eos) localname) (let ((uname (match-string 1 localname)) (fname (match-string 2 localname)) hname) @@ -2861,7 +2858,8 @@ the result will be a local, non-Tramp, file name." ;; appropriate either, because ssh and companions might ;; use a user name from the config file. (when (and (tramp-string-empty-or-nil-p uname) - (string-match-p (rx bos "su" (? "do") eos) method)) + (string-match-p + (rx bos (| "su" "sudo" "doas" "ksu") eos) method)) (setq uname user)) (when (setq hname (tramp-get-home-directory v uname)) (setq localname (concat hname fname))))) @@ -3042,6 +3040,7 @@ implementation will be used." v 'file-error "Stderr buffer `%s' not supported" stderr)) (with-current-buffer stderr (setq buffer-read-only nil)) + (tramp-taint-remote-process-buffer stderr) ;; Create named pipe. (tramp-send-command v (format (tramp-get-remote-mknod-or-mkfifo v) tmpstderr)) @@ -3219,8 +3218,7 @@ implementation will be used." (format "%s %s %s" (tramp-get-method-parameter vec 'tramp-remote-shell) - (mapconcat - #'identity + (string-join (tramp-get-method-parameter vec 'tramp-remote-shell-args) " ") (tramp-shell-quote-argument (format "kill -%d $$" i)))) @@ -3267,7 +3265,7 @@ implementation will be used." ;; Determine input. (if (null infile) (setq input (tramp-get-remote-null-device v)) - (setq infile (tramp-compat-file-name-unquote (expand-file-name infile))) + (setq infile (file-name-unquote (expand-file-name infile))) (if (tramp-equal-remote default-directory infile) ;; INFILE is on the same remote host. (setq input (tramp-unquote-file-local-name infile)) @@ -3644,6 +3642,40 @@ implementation will be used." "decoding command or an scp program") method))))))))) +(defun tramp-bundle-read-file-names (vec files) + "Read file attributes of FILES and with one command fill the cache. +FILES must be the local names only. The cache attributes to be +filled are described in `tramp-bundle-read-file-names'." + (when files + (tramp-maybe-send-script + vec + (format tramp-bundle-read-file-names + (tramp-get-file-exists-command vec) + (format "%s -r" (tramp-get-test-command vec)) + (format "%s -d" (tramp-get-test-command vec))) + "tramp_bundle_read_file_names") + + (dolist + (elt + (with-current-buffer (tramp-get-connection-buffer vec) + ;; We cannot use `tramp-send-command-and-read', because + ;; this does not cooperate well with heredoc documents. + (unless (tramp-send-command-and-check + vec + (format + "tramp_bundle_read_file_names <<'%s'\n%s\n%s\n" + tramp-end-of-heredoc + (mapconcat #'tramp-shell-quote-argument files "\n") + tramp-end-of-heredoc)) + (tramp-error vec 'file-error "%s" (tramp-get-buffer-string))) + ;; Read the expression. + (goto-char (point-min)) + (read (current-buffer)))) + + (tramp-set-file-property vec (car elt) "file-exists-p" (nth 1 elt)) + (tramp-set-file-property vec (car elt) "file-readable-p" (nth 2 elt)) + (tramp-set-file-property vec (car elt) "file-directory-p" (nth 3 elt))))) + (defvar tramp-vc-registered-file-names nil "List used to collect file names, which are checked during `vc-registered'.") @@ -3689,36 +3721,7 @@ implementation will be used." (tramp-message v 10 "\n%s" tramp-vc-registered-file-names) ;; Send just one command, in order to fill the cache. - (when tramp-vc-registered-file-names - (tramp-maybe-send-script - v - (format tramp-vc-registered-read-file-names - (tramp-get-file-exists-command v) - (format "%s -r" (tramp-get-test-command v))) - "tramp_vc_registered_read_file_names") - - (dolist - (elt - (ignore-errors - ;; We cannot use `tramp-send-command-and-read', - ;; because this does not cooperate well with - ;; heredoc documents. - (tramp-send-command - v - (format - "tramp_vc_registered_read_file_names <<'%s'\n%s\n%s\n" - tramp-end-of-heredoc - (mapconcat #'tramp-shell-quote-argument - tramp-vc-registered-file-names - "\n") - tramp-end-of-heredoc)) - (with-current-buffer (tramp-get-connection-buffer v) - ;; Read the expression. - (goto-char (point-min)) - (read (current-buffer))))) - - (tramp-set-file-property - v (car elt) (cadr elt) (cadr (cdr elt)))))) + (tramp-bundle-read-file-names v tramp-vc-registered-file-names)) ;; Second run. Now all `file-exists-p' or `file-readable-p' ;; calls shall be answered from the file cache. We unset @@ -3759,8 +3762,10 @@ implementation will be used." "Invoke remote-shell Tramp file name handler. Fall back to normal file name handler if no Tramp handler exists." (if-let ((fn (assoc operation tramp-sh-file-name-handler-alist))) - (save-match-data (apply (cdr fn) args)) - (tramp-run-real-handler operation args))) + (prog1 (save-match-data (apply (cdr fn) args)) + (setq tramp-debug-message-fnh-function (cdr fn))) + (prog1 (tramp-run-real-handler operation args) + (setq tramp-debug-message-fnh-function operation)))) ;;;###tramp-autoload (defun tramp-sh-file-name-handler-p (vec) @@ -3822,11 +3827,12 @@ Fall back to normal file name handler if no Tramp handler exists." (cond ((and (memq 'change flags) (memq 'attribute-change flags)) (concat "create,modify,move,moved_from,moved_to,move_self," - "delete,delete_self,attrib,ignored")) + "delete,delete_self,attrib")) ((memq 'change flags) (concat "create,modify,move,moved_from,moved_to,move_self," - "delete,delete_self,ignored")) - ((memq 'attribute-change flags) "attrib,ignored")) + "delete,delete_self")) + ((memq 'attribute-change flags) "attrib")) + events (concat events ",ignored,unmount") ;; "-P" has been added to version 3.21, so we cannot assume it yet. sequence `(,command "-mq" "-e" ,events ,localname) ;; Make events a list of symbols. @@ -3841,10 +3847,10 @@ Fall back to normal file name handler if no Tramp handler exists." (cond ((and (memq 'change flags) (memq 'attribute-change flags)) '(created changed changes-done-hint moved deleted - attribute-changed)) + attribute-changed unmounted)) ((memq 'change flags) - '(created changed changes-done-hint moved deleted)) - ((memq 'attribute-change flags) '(attribute-changed))) + '(created changed changes-done-hint moved deleted unmounted)) + ((memq 'attribute-change flags) '(attribute-changed unmounted))) sequence `(,command "monitor" ,localname))) ;; None. (t (tramp-error @@ -3864,8 +3870,6 @@ Fall back to normal file name handler if no Tramp handler exists." v 'file-notify-error "`%s' failed to start on remote host" (string-join sequence " ")) - (tramp-message v 6 "Run `%s', %S" (string-join sequence " ") p) - (process-put p 'tramp-vector v) ;; This is needed for ssh or PuTTY based processes, and only if ;; the respective options are set. Perhaps, the setting could ;; be more fine-grained. @@ -3873,9 +3877,9 @@ Fall back to normal file name handler if no Tramp handler exists." ;; Needed for process filter. (process-put p 'tramp-events events) (process-put p 'tramp-watch-name localname) - (set-process-query-on-exit-flag p nil) (set-process-filter p filter) (set-process-sentinel p #'tramp-file-notify-process-sentinel) + (tramp-post-process-creation p v) ;; There might be an error if the monitor is not supported. ;; Give the filter a chance to read the output. (while (tramp-accept-process-output p)) @@ -3939,7 +3943,7 @@ Fall back to normal file name handler if no Tramp handler exists." (setq string (tramp-compat-string-replace "\n\n" "\n" string)) (while (string-match - (tramp-compat-rx + (rx bol (+ (not ":")) ":" blank (group (+ (not ":"))) ":" blank (group (regexp (regexp-opt tramp-gio-events))) @@ -4043,66 +4047,55 @@ commands. \"%n\" is replaced by \"2>/dev/null\", and \"%t\" is replaced by a temporary file name. If VEC is nil, the respective local commands are used. If there is a format specifier which cannot be expanded, this function returns nil." - (if (not (string-match-p - (tramp-compat-rx (| bol (not "%")) "%" (any "ahlnoprsty")) script)) + (if (not (string-match-p (rx (| bol (not "%")) "%" (any "ahlnoprsty")) script)) script (catch 'wont-work - (let ((awk (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%a") script) + (let ((awk (when (string-match-p (rx (| bol (not "%")) "%a") script) (or (if vec (tramp-get-remote-awk vec) (executable-find "awk")) (throw 'wont-work nil)))) - (hdmp (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%h") script) + (hdmp (when (string-match-p (rx (| bol (not "%")) "%h") script) (or (if vec (tramp-get-remote-hexdump vec) (executable-find "hexdump")) (throw 'wont-work nil)))) - (dev (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%n") script) + (dev (when (string-match-p (rx (| bol (not "%")) "%n") script) (or (if vec (concat "2>" (tramp-get-remote-null-device vec)) (if (eq system-type 'windows-nt) "" (concat "2>" null-device))) (throw 'wont-work nil)))) - (ls (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%l") script) + (ls (when (string-match-p (rx (| bol (not "%")) "%l") script) (format "%s %s" (or (tramp-get-ls-command vec) (throw 'wont-work nil)) (tramp-sh--quoting-style-options vec)))) - (od (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%o") script) + (od (when (string-match-p (rx (| bol (not "%")) "%o") script) (or (if vec (tramp-get-remote-od vec) (executable-find "od")) (throw 'wont-work nil)))) - (perl (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%p") script) + (perl (when (string-match-p (rx (| bol (not "%")) "%p") script) (or (if vec (tramp-get-remote-perl vec) (executable-find "perl")) (throw 'wont-work nil)))) - (python (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%y") script) - (or - (if vec - (tramp-get-remote-python vec) - (executable-find "python")) - (throw 'wont-work nil)))) - (readlink (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%r") script) + (python (when (string-match-p (rx (| bol (not "%")) "%y") script) + (or + (if vec + (tramp-get-remote-python vec) + (executable-find "python")) + (throw 'wont-work nil)))) + (readlink (when (string-match-p (rx (| bol (not "%")) "%r") script) (or (if vec - (tramp-get-remote-readlink vec) - (executable-find "readlink")) - (throw 'wont-work nil)))) - (stat (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%s") script) + (tramp-get-remote-readlink vec) + (executable-find "readlink")) + (throw 'wont-work nil)))) + (stat (when (string-match-p (rx (| bol (not "%")) "%s") script) (or (if vec (tramp-get-remote-stat vec) (executable-find "stat")) (throw 'wont-work nil)))) - (tmp (when (string-match-p - (tramp-compat-rx (| bol (not "%")) "%t") script) + (tmp (when (string-match-p (rx (| bol (not "%")) "%t") script) (or (if vec (tramp-file-local-name (tramp-make-tramp-temp-name vec)) @@ -4124,7 +4117,7 @@ Only send the definition if it has not already been done." (unless (member name scripts) (with-tramp-progress-reporter vec 5 (format-message "Sending script `%s'" name) - ;; In bash, leading TABs like in `tramp-vc-registered-read-file-names' + ;; In bash, leading TABs like in `tramp-bundle-read-file-names' ;; could result in unwanted command expansion. Avoid this. (setq script (tramp-compat-string-replace (make-string 1 ?\t) (make-string 8 ? ) script)) @@ -4214,13 +4207,7 @@ variable PATH." (format "PATH=%s && export PATH" (string-join (tramp-get-remote-path vec) ":"))) - (pipe-buf - (with-tramp-connection-property vec "pipe-buf" - (tramp-send-command-and-read - vec - (format "getconf PIPE_BUF / 2>%s || echo 4096" - (tramp-get-remote-null-device vec)) - 'noerror))) + (pipe-buf (tramp-get-remote-pipe-buf vec)) tmpfile chunk chunksize) (tramp-message vec 5 "Setting $PATH environment variable") (if (tramp-compat-length< command pipe-buf) @@ -4290,14 +4277,17 @@ file exists and nonzero exit status otherwise." vec (format "%s %s" result existing)) (not (tramp-send-command-and-check vec (format "%s %s" result nonexistent))))) + ;; We cannot use `tramp-get-ls-command', this results in an infloop. + ;; (Bug#65321) (ignore-errors - (and (setq result (format "%s -d" (tramp-get-ls-command vec))) + (and (setq result (format "ls -d >%s" (tramp-get-remote-null-device vec))) (tramp-send-command-and-check vec (format "%s %s" result existing)) (not (tramp-send-command-and-check vec (format "%s %s" result nonexistent)))))) (tramp-error vec 'file-error "Couldn't find command to check if file exists")) + (tramp-set-file-property vec existing "file-exists-p" t) result)) (defun tramp-get-sh-extra-args (shell) @@ -4360,8 +4350,7 @@ file exists and nonzero exit status otherwise." "Couldn't find remote shell prompt for %s" shell) (unless (tramp-check-for-regexp - (tramp-get-connection-process vec) - (tramp-compat-rx (literal tramp-end-of-output))) + (tramp-get-connection-process vec) (rx (literal tramp-end-of-output))) (tramp-wait-for-output (tramp-get-connection-process vec)) (tramp-message vec 5 "Setting shell prompt") (tramp-send-command @@ -4385,6 +4374,8 @@ file exists and nonzero exit status otherwise." "`tramp-histfile-override' uses invalid file `%s'" tramp-histfile-override)) + (tramp-flush-connection-property + (tramp-get-connection-process vec) "scripts") (tramp-set-connection-property (tramp-get-connection-process vec) "remote-shell" shell))) @@ -4402,8 +4393,7 @@ file exists and nonzero exit status otherwise." (tramp-send-command vec (format "echo ~%s" tramp-root-id-string) t) (if (or (string-match-p - (tramp-compat-rx - bol "~" (literal tramp-root-id-string) eol) + (rx bol "~" (literal tramp-root-id-string) eol) (buffer-string)) ;; The default shell (ksh93) of OpenSolaris ;; and Solaris is buggy. We've got reports @@ -4442,7 +4432,7 @@ seconds. If not, it produces an error message with the given ERROR-ARGS." (condition-case nil (tramp-wait-for-regexp proc timeout - (tramp-compat-rx + (rx (| (regexp shell-prompt-pattern) (regexp tramp-shell-prompt-pattern)) (? (regexp ansi-color-control-seq-regexp)) eos)) @@ -4451,6 +4441,14 @@ seconds. If not, it produces an error message with the given ERROR-ARGS." (apply #'tramp-error-with-buffer (tramp-get-connection-buffer vec) vec 'file-error error-args))))) +(defvar tramp-config-check nil + "A function to be called with one argument, VEC. +It should return a string which is used to check, whether the +configuration of the remote host has been changed (which would +require flushing the cache data). This string is kept as +connection property \"config-check-data\". +This variable is intended as connection-local variable.") + (defun tramp-open-connection-setup-interactive-shell (proc vec) "Set up an interactive shell. Mainly sets the prompt and the echo correctly. PROC is the shell @@ -4459,12 +4457,10 @@ process to set up. VEC specifies the connection." (tramp-open-shell vec (tramp-get-method-parameter vec 'tramp-remote-shell)) (tramp-message vec 5 "Setting up remote shell environment") - ;; Disable line editing. - (tramp-send-command vec "set +o vi +o emacs" t) - - ;; Dump option settings in the traces. - (when (>= tramp-verbose 9) - (tramp-send-command vec "set -o" t)) + ;; Disable line editing. Dump option settings in the traces. + (tramp-send-command + vec + (if (>= tramp-verbose 9) "set +o vi +o emacs -o" "set +o vi +o emacs") t) ;; Disable echo expansion. (tramp-send-command @@ -4497,7 +4493,7 @@ process to set up. VEC specifies the connection." vec "uname" (tramp-send-command-and-read vec "echo \\\"`uname -sr`\\\"")))) (config-check-function - (tramp-get-method-parameter vec 'tramp-config-check)) + (buffer-local-value 'tramp-config-check (process-buffer proc))) (old-config-check (and config-check-function (tramp-get-connection-property vec "config-check-data"))) @@ -4604,7 +4600,7 @@ process to set up. VEC specifies the connection." (tramp-send-command vec "set +H" t)) ;; Disable tab expansion. - (if (string-match-p (rx (| "BSD" "DragonFly" "Darwin")) uname) + (if (string-match-p tramp-bsd-unames uname) (tramp-send-command vec "stty tabs" t) (tramp-send-command vec "stty tab0" t)) @@ -4830,7 +4826,7 @@ Goes through the list `tramp-local-coding-commands' and (with-current-buffer (tramp-get-connection-buffer vec) (goto-char (point-min)) - (unless (looking-at-p (tramp-compat-rx (literal magic))) + (unless (looking-at-p (rx (literal magic))) (throw 'wont-work-remote nil))) ;; `rem-enc' and `rem-dec' could be a string meanwhile. @@ -4916,7 +4912,7 @@ Goes through the list `tramp-inline-compress-commands'." nil t)) (throw 'next nil)) (goto-char (point-min)) - (unless (looking-at-p (tramp-compat-rx (literal magic))) + (unless (looking-at-p (rx (literal magic))) (throw 'next nil))) (tramp-message vec 5 @@ -4927,7 +4923,7 @@ Goes through the list `tramp-inline-compress-commands'." (throw 'next nil)) (with-current-buffer (tramp-get-buffer vec) (goto-char (point-min)) - (unless (looking-at-p (tramp-compat-rx (literal magic))) + (unless (looking-at-p (rx (literal magic))) (throw 'next nil))) (setq found t))) @@ -4963,12 +4959,19 @@ Goes through the list `tramp-inline-compress-commands'." "Return the Control* arguments of the local ssh." (cond ;; No options to be computed. - ((or (null tramp-use-ssh-controlmaster-options) + ((or (null tramp-use-connection-share) (null (assoc "%c" (tramp-get-method-parameter vec 'tramp-login-args)))) "") + ;; Use plink option. + ((string-match-p + (rx "plink" (? ".exe") eol) + (tramp-get-method-parameter vec 'tramp-login-program)) + (if (eq tramp-use-connection-share 'suppress) + "-noshare" "-share")) + ;; There is already a value to be used. - ((and (eq tramp-use-ssh-controlmaster-options t) + ((and (eq tramp-use-connection-share t) (stringp tramp-ssh-controlmaster-options)) tramp-ssh-controlmaster-options) @@ -4982,18 +4985,26 @@ Goes through the list `tramp-inline-compress-commands'." ;; ControlMaster and ControlPath options are introduced in OpenSSH 3.9. (concat "-o ControlMaster=" - (if (eq tramp-use-ssh-controlmaster-options 'suppress) + (if (eq tramp-use-connection-share 'suppress) "no" "auto") " -o ControlPath=" - (if (eq tramp-use-ssh-controlmaster-options 'suppress) + (if (eq tramp-use-connection-share 'suppress) "none" - ;; Hashed tokens are introduced in OpenSSH 6.7. - (if (tramp-ssh-option-exists-p vec "ControlPath=tramp.%C") - "tramp.%%C" "tramp.%%r@%%h:%%p")) + ;; Hashed tokens are introduced in OpenSSH 6.7. On macOS + ;; we cannot use an absolute file name, it is too long. + ;; See Bug#19702. + (if (eq system-type 'darwin) + (if (tramp-ssh-option-exists-p vec "ControlPath=tramp.%C") + "tramp.%%C" "tramp.%%r@%%h:%%p") + (expand-file-name + (if (tramp-ssh-option-exists-p vec "ControlPath=tramp.%C") + "tramp.%%C" "tramp.%%r@%%h:%%p") + (or small-temporary-file-directory + tramp-compat-temporary-file-directory)))) ;; ControlPersist option is introduced in OpenSSH 5.6. - (when (and (not (eq tramp-use-ssh-controlmaster-options 'suppress)) + (when (and (not (eq tramp-use-connection-share 'suppress)) (tramp-ssh-option-exists-p vec "ControlPersist=no")) " -o ControlPersist=no")))))) @@ -5080,7 +5091,7 @@ Goes through the list `tramp-inline-compress-commands'." (tramp-call-process vec1 tramp-encoding-shell nil t nil tramp-encoding-command-switch - (mapconcat #'identity command " ")) + (string-join command " ")) (goto-char (point-min)) (not (search-forward "remotecommand" nil 'noerror))))) @@ -5099,11 +5110,11 @@ Goes through the list `tramp-inline-compress-commands'." found string) (with-temp-buffer ;; Check hostkey of VEC2, seen from VEC1. - (tramp-send-command vec1 (mapconcat #'identity command " ")) + (tramp-send-command vec1 (string-join command " ")) ;; Check hostkey of VEC2, seen locally. (tramp-call-process vec1 tramp-encoding-shell nil t nil tramp-encoding-command-switch - (mapconcat #'identity command " ")) + (string-join command " ")) (goto-char (point-min)) (while (and (not found) (not (eobp))) (setq string @@ -5150,240 +5161,240 @@ connection if a previous connection has died for some reason." (unless (tramp-connectable-p vec) (throw 'non-essential 'non-essential)) - (let ((p (tramp-get-connection-process vec)) - (process-name (tramp-get-connection-property vec "process-name")) - (process-environment (copy-sequence process-environment)) - (pos (with-current-buffer (tramp-get-connection-buffer vec) (point)))) - - ;; If Tramp opens the same connection within a short time frame, - ;; there is a problem. We shall signal this. - (unless (or (process-live-p p) - (and (processp p) (not non-essential)) - (not (tramp-file-name-equal-p - vec (car tramp-current-connection))) - (time-less-p - (time-since (cdr tramp-current-connection)) - (or tramp-connection-min-time-diff 0))) - (throw 'suppress 'suppress)) - - ;; If too much time has passed since last command was sent, look - ;; whether process is still alive. If it isn't, kill it. When - ;; using ssh, it can sometimes happen that the remote end has hung - ;; up but the local ssh client doesn't recognize this until it - ;; tries to send some data to the remote end. So that's why we - ;; try to send a command from time to time, then look again - ;; whether the process is really alive. - (condition-case nil - (when (and (time-less-p - 60 (time-since - (tramp-get-connection-property p "last-cmd-time" 0))) - (process-live-p p)) - (tramp-send-command vec "echo are you awake" t t) - (unless (and (process-live-p p) - (tramp-wait-for-output p 10)) - ;; The error will be caught locally. - (tramp-error vec 'file-error "Awake did fail"))) - (file-error - (tramp-cleanup-connection vec t) - (setq p nil))) - - ;; New connection must be opened. - (condition-case err - (unless (process-live-p p) - (with-tramp-progress-reporter - vec 3 - (if (tramp-string-empty-or-nil-p (tramp-file-name-user vec)) - (format "Opening connection %s for %s using %s" + (with-tramp-debug-message vec "Opening connection" + (let ((p (tramp-get-connection-process vec)) + (process-name (tramp-get-connection-property vec "process-name")) + (process-environment (copy-sequence process-environment)) + (pos (with-current-buffer (tramp-get-connection-buffer vec) (point)))) + + ;; If Tramp opens the same connection within a short time frame, + ;; there is a problem. We shall signal this. + (unless (or (process-live-p p) + (and (processp p) (not non-essential)) + (not (tramp-file-name-equal-p + vec (car tramp-current-connection))) + (time-less-p + (time-since (cdr tramp-current-connection)) + (or tramp-connection-min-time-diff 0))) + (throw 'suppress 'suppress)) + + ;; If too much time has passed since last command was sent, look + ;; whether process is still alive. If it isn't, kill it. When + ;; using ssh, it can sometimes happen that the remote end has + ;; hung up but the local ssh client doesn't recognize this until + ;; it tries to send some data to the remote end. So that's why + ;; we try to send a command from time to time, then look again + ;; whether the process is really alive. + (condition-case nil + (when (and (time-less-p + 60 (time-since + (tramp-get-connection-property p "last-cmd-time" 0))) + (process-live-p p)) + (tramp-send-command vec "echo are you awake" t t) + (unless (and (process-live-p p) + (tramp-wait-for-output p 10)) + ;; The error will be caught locally. + (tramp-error vec 'file-error "Awake did fail"))) + (file-error + (tramp-cleanup-connection vec t) + (setq p nil))) + + ;; New connection must be opened. + (condition-case err + (unless (process-live-p p) + (with-tramp-progress-reporter + vec 3 + (if (tramp-string-empty-or-nil-p (tramp-file-name-user vec)) + (format "Opening connection %s for %s using %s" + process-name + (tramp-file-name-host vec) + (tramp-file-name-method vec)) + (format "Opening connection %s for %s@%s using %s" process-name + (tramp-file-name-user vec) (tramp-file-name-host vec) - (tramp-file-name-method vec)) - (format "Opening connection %s for %s@%s using %s" - process-name - (tramp-file-name-user vec) - (tramp-file-name-host vec) - (tramp-file-name-method vec))) - - (catch 'uname-changed - ;; Start new process. - (when (and p (processp p)) - (delete-process p)) - (setenv "TERM" tramp-terminal-type) - (setenv "LC_ALL" (tramp-get-local-locale vec)) - (if (stringp tramp-histfile-override) - (setenv "HISTFILE" tramp-histfile-override) - (if tramp-histfile-override - (progn - (setenv "HISTFILE") - (setenv "HISTFILESIZE" "0") - (setenv "HISTSIZE" "0")))) - (setenv "PROMPT_COMMAND") - (setenv "PS1" tramp-initial-end-of-output) - (unless (stringp tramp-encoding-shell) - (tramp-error vec 'file-error "`tramp-encoding-shell' not set")) - (let* ((current-host tramp-system-name) - (target-alist (tramp-compute-multi-hops vec)) - (previous-hop tramp-null-hop) - ;; We will apply `tramp-ssh-controlmaster-options' - ;; only for the first hop. - (options (tramp-ssh-controlmaster-options vec)) - (process-connection-type tramp-process-connection-type) - (process-adaptive-read-buffering nil) - ;; There are unfortunate settings for "cmdproxy" on - ;; W32 systems. - (process-coding-system-alist nil) - (coding-system-for-read nil) - (extra-args (tramp-get-sh-extra-args tramp-encoding-shell)) - ;; This must be done in order to avoid our file - ;; name handler. - (p (let ((default-directory - tramp-compat-temporary-file-directory)) - (apply - #'start-process - (tramp-get-connection-name vec) - (tramp-get-connection-buffer vec) - (append - (list tramp-encoding-shell) - (and extra-args (split-string extra-args)) - (and tramp-encoding-command-interactive - (list tramp-encoding-command-interactive))))))) - - ;; Set sentinel and query flag. Initialize variables. - (set-process-sentinel p #'tramp-process-sentinel) - (process-put p 'tramp-vector vec) - ;; This is needed for ssh or PuTTY based processes, and - ;; only if the respective options are set. Perhaps, - ;; the setting could be more fine-grained. - ;; (process-put p 'tramp-shared-socket t) - (process-put p 'adjust-window-size-function #'ignore) - (set-process-query-on-exit-flag p nil) - (setq tramp-current-connection (cons vec (current-time))) - - (tramp-message vec 6 "%s" (string-join (process-command p) " ")) - - ;; Set connection-local variables. - (tramp-set-connection-local-variables vec) - - ;; Check whether process is alive. - (tramp-barf-if-no-shell-prompt - p 10 - "Couldn't find local shell prompt for %s" tramp-encoding-shell) - - ;; Now do all the connections as specified. - (while target-alist - (let* ((hop (car target-alist)) - (l-method (tramp-file-name-method hop)) - (l-user (tramp-file-name-user hop)) - (l-domain (tramp-file-name-domain hop)) - (l-host (tramp-file-name-host hop)) - (l-port (tramp-file-name-port hop)) - (remote-shell - (tramp-get-method-parameter hop 'tramp-remote-shell)) - (extra-args (tramp-get-sh-extra-args remote-shell)) - (async-args - (tramp-compat-flatten-tree - (tramp-get-method-parameter hop 'tramp-async-args))) - (connection-timeout - (tramp-get-method-parameter - hop 'tramp-connection-timeout)) - (command - (tramp-get-method-parameter hop 'tramp-login-program)) - ;; We don't create the temporary file. In - ;; fact, it is just a prefix for the - ;; ControlPath option of ssh; the real - ;; temporary file has another name, and it is - ;; created and protected by ssh. It is also - ;; removed by ssh when the connection is - ;; closed. The temporary file name is cached - ;; in the main connection process, therefore - ;; we cannot use `tramp-get-connection-process'. - (tmpfile - (with-tramp-connection-property - (tramp-get-process vec) "temp-file" - (tramp-compat-make-temp-name))) - r-shell) - - ;; Check, whether there is a restricted shell. - (dolist (elt tramp-restricted-shell-hosts-alist) - (when (string-match-p elt current-host) - (setq r-shell t))) - (setq current-host l-host) - - ;; Set password prompt vector. - (tramp-set-connection-property - p "password-vector" - (if (tramp-get-method-parameter - hop 'tramp-password-previous-hop) - (let ((pv (copy-tramp-file-name previous-hop))) - (setf (tramp-file-name-method pv) l-method) - pv) - (make-tramp-file-name - :method l-method :user l-user :domain l-domain - :host l-host :port l-port))) - - ;; Set session timeout. - (when (tramp-get-method-parameter - hop 'tramp-session-timeout) + (tramp-file-name-method vec))) + + (catch 'uname-changed + ;; Start new process. + (when (and p (processp p)) + (delete-process p)) + (setenv "TERM" tramp-terminal-type) + (setenv "LC_ALL" (tramp-get-local-locale vec)) + (if (stringp tramp-histfile-override) + (setenv "HISTFILE" tramp-histfile-override) + (if tramp-histfile-override + (progn + (setenv "HISTFILE") + (setenv "HISTFILESIZE" "0") + (setenv "HISTSIZE" "0")))) + (setenv "PROMPT_COMMAND") + (setenv "PS1" tramp-initial-end-of-output) + (unless (stringp tramp-encoding-shell) + (tramp-error vec 'file-error "`tramp-encoding-shell' not set")) + (let* ((current-host tramp-system-name) + (target-alist (tramp-compute-multi-hops vec)) + (previous-hop tramp-null-hop) + ;; We will apply `tramp-ssh-controlmaster-options' + ;; only for the first hop. + (options (tramp-ssh-controlmaster-options vec)) + (process-connection-type tramp-process-connection-type) + (process-adaptive-read-buffering nil) + ;; There are unfortunate settings for + ;; "cmdproxy" on W32 systems. + (process-coding-system-alist nil) + (coding-system-for-read nil) + (extra-args + (tramp-get-sh-extra-args tramp-encoding-shell)) + ;; This must be done in order to avoid our file + ;; name handler. + (p (let ((default-directory + tramp-compat-temporary-file-directory)) + (apply + #'start-process + (tramp-get-connection-name vec) + (tramp-get-connection-buffer vec) + (append + `(,tramp-encoding-shell) + (and extra-args (split-string extra-args)) + (and tramp-encoding-command-interactive + `(,tramp-encoding-command-interactive))))))) + + ;; This is needed for ssh or PuTTY based processes, + ;; and only if the respective options are set. + ;; Perhaps, the setting could be more fine-grained. + ;; (process-put p 'tramp-shared-socket t) + ;; Set sentinel. Initialize variables. + (set-process-sentinel p #'tramp-process-sentinel) + (tramp-post-process-creation p vec) + (setq tramp-current-connection (cons vec (current-time))) + + ;; Set connection-local variables. + (tramp-set-connection-local-variables vec) + + ;; Check whether process is alive. + (tramp-barf-if-no-shell-prompt + p 10 + "Couldn't find local shell prompt for %s" + tramp-encoding-shell) + + ;; Now do all the connections as specified. + (while target-alist + (let* ((hop (car target-alist)) + (l-method (tramp-file-name-method hop)) + (l-user (tramp-file-name-user hop)) + (l-domain (tramp-file-name-domain hop)) + (l-host (tramp-file-name-host hop)) + (l-port (tramp-file-name-port hop)) + (remote-shell + (tramp-get-method-parameter hop 'tramp-remote-shell)) + (extra-args (tramp-get-sh-extra-args remote-shell)) + (async-args + (flatten-tree + (tramp-get-method-parameter hop 'tramp-async-args))) + (connection-timeout + (tramp-get-method-parameter + hop 'tramp-connection-timeout + tramp-connection-timeout)) + (command + (tramp-get-method-parameter + hop 'tramp-login-program)) + ;; We don't create the temporary file. In + ;; fact, it is just a prefix for the + ;; ControlPath option of ssh; the real + ;; temporary file has another name, and it + ;; is created and protected by ssh. It is + ;; also removed by ssh when the connection + ;; is closed. The temporary file name is + ;; cached in the main connection process, + ;; therefore we cannot use + ;; `tramp-get-connection-process'. + (tmpfile + (with-tramp-connection-property + (tramp-get-process vec) "temp-file" + (tramp-compat-make-temp-name))) + r-shell) + + ;; Check, whether there is a restricted shell. + (dolist (elt tramp-restricted-shell-hosts-alist) + (when (string-match-p elt current-host) + (setq r-shell t))) + (setq current-host l-host) + + ;; Set password prompt vector. (tramp-set-connection-property - p "session-timeout" - (tramp-get-method-parameter - hop 'tramp-session-timeout))) - - ;; Replace `login-args' place holders. - (setq - command - (mapconcat - #'identity - (append - ;; We do not want to see the trailing local - ;; prompt in `start-file-process'. - (unless r-shell '("exec")) - `(,command) - ;; Add arguments for asynchronous processes. - (when process-name async-args) - (tramp-expand-args - hop 'tramp-login-args - ?h (or l-host "") ?u (or l-user "") ?p (or l-port "") - ?c (format-spec options (format-spec-make ?t tmpfile)) - ?n (concat - "2>" (tramp-get-remote-null-device previous-hop)) - ?l (concat remote-shell " " extra-args " -i")) - ;; A restricted shell does not allow "exec". - (when r-shell '("&&" "exit")) '("||" "exit")) - " ")) - - ;; Send the command. - (tramp-message vec 3 "Sending command `%s'" command) - (tramp-send-command vec command t t) - (tramp-process-actions - p vec - (min - pos (with-current-buffer (process-buffer p) (point-max))) - tramp-actions-before-shell - (or connection-timeout tramp-connection-timeout)) - (tramp-message - vec 3 "Found remote shell prompt on `%s'" l-host) - - ;; Next hop. - (setq options "" - target-alist (cdr target-alist) - previous-hop hop))) - - ;; Activate session timeout. - (when (tramp-get-connection-property p "session-timeout") - (run-at-time - (tramp-get-connection-property p "session-timeout") nil - #'tramp-timeout-session vec)) - - ;; Make initial shell settings. - (tramp-open-connection-setup-interactive-shell p vec) - - ;; Mark it as connected. - (tramp-set-connection-property p "connected" t))))) - - ;; Cleanup, and propagate the signal. - ((error quit) - (tramp-cleanup-connection vec t) - (signal (car err) (cdr err)))))) + p "password-vector" + (if (tramp-get-method-parameter + hop 'tramp-password-previous-hop) + (let ((pv (copy-tramp-file-name previous-hop))) + (setf (tramp-file-name-method pv) l-method) + pv) + (make-tramp-file-name + :method l-method :user l-user :domain l-domain + :host l-host :port l-port))) + + ;; Set session timeout. + (when (tramp-get-method-parameter + hop 'tramp-session-timeout) + (tramp-set-connection-property + p "session-timeout" + (tramp-get-method-parameter + hop 'tramp-session-timeout))) + + ;; Replace `login-args' place holders. + (setq + command + (string-join + (append + ;; We do not want to see the trailing local + ;; prompt in `start-file-process'. + (unless r-shell '("exec")) + `(,command) + ;; Add arguments for asynchronous processes. + (when process-name async-args) + (tramp-expand-args + hop 'tramp-login-args nil + ?h (or l-host "") ?u (or l-user "") ?p (or l-port "") + ?c (format-spec options (format-spec-make ?t tmpfile)) + ?n (concat + "2>" (tramp-get-remote-null-device previous-hop)) + ?l (concat remote-shell " " extra-args " -i")) + ;; A restricted shell does not allow "exec". + (when r-shell '("&&" "exit")) '("||" "exit")) + " ")) + + ;; Send the command. + (tramp-message vec 3 "Sending command `%s'" command) + (tramp-send-command vec command t t) + (tramp-process-actions + p vec + (min + pos (with-current-buffer (process-buffer p) (point-max))) + tramp-actions-before-shell connection-timeout) + (tramp-message + vec 3 "Found remote shell prompt on `%s'" l-host) + + ;; Next hop. + (setq options "" + target-alist (cdr target-alist) + previous-hop hop))) + + ;; Activate session timeout. + (when (tramp-get-connection-property p "session-timeout") + (run-at-time + (tramp-get-connection-property p "session-timeout") nil + #'tramp-timeout-session vec)) + + ;; Make initial shell settings. + (tramp-open-connection-setup-interactive-shell p vec) + + ;; Mark it as connected. + (tramp-set-connection-property p "connected" t))))) + + ;; Cleanup, and propagate the signal. + ((error quit) + (tramp-cleanup-connection vec t) + (signal (car err) (cdr err))))))) (defun tramp-send-command (vec command &optional neveropen nooutput) "Send the COMMAND to connection VEC. @@ -5424,7 +5435,7 @@ function waits for output unless NOOUTPUT is set." ;; ignored. Busyboxes built with the EDITING_ASK_TERMINAL ;; config option send also ANSI control escape sequences, ;; which must be ignored. - (regexp (tramp-compat-rx + (regexp (rx (* (not (any "#$\n"))) (literal tramp-end-of-output) (? (regexp ansi-color-control-seq-regexp)) @@ -5432,14 +5443,14 @@ function waits for output unless NOOUTPUT is set." ;; Sometimes, the commands do not return a newline but a ;; null byte before the shell prompt, for example "git ;; ls-files -c -z ...". - (regexp1 (tramp-compat-rx (| bol "\000") (regexp regexp))) + (regexp1 (rx (| bol "\000") (regexp regexp))) (found (tramp-wait-for-regexp proc timeout regexp1))) (if found (let ((inhibit-read-only t)) ;; A simple-minded busybox has sent " ^H" sequences. ;; Delete them. (goto-char (point-min)) - (when (re-search-forward + (when (search-forward-regexp (rx bol (+ nonl "\b") eol) (line-end-position) t) (forward-line 1) (delete-region (point-min) (point))) @@ -5472,8 +5483,7 @@ the exit status." (let (cmd data) (if (and (stringp command) (string-match - (tramp-compat-rx - (* nonl) "<<'" (literal tramp-end-of-heredoc) "'" (* nonl)) + (rx (* nonl) "<<'" (literal tramp-end-of-heredoc) "'" (* nonl)) command)) (setq cmd (match-string 0 command) data (substring command (match-end 0))) @@ -5522,7 +5532,7 @@ raises an error." ;; Read the marker. (when (stringp marker) (condition-case nil - (re-search-forward marker) + (search-forward-regexp marker) (error (unless noerror (tramp-error vec 'file-error @@ -5535,7 +5545,7 @@ raises an error." (unless noerror signal-hook-function))) (read (current-buffer))) ;; Error handling. - (when (re-search-forward (rx (not space)) (line-end-position) t) + (when (search-forward-regexp (rx (not space)) (line-end-position) t) (error nil))) (error (unless noerror (tramp-error @@ -5553,8 +5563,8 @@ raises an error." string "")) -(defun tramp-make-copy-program-file-name (vec) - "Create a file name suitable for `scp', `pscp', or `nc' and workalikes." +(defun tramp-make-copy-file-name (vec) + "Create a file name suitable for out-of-band methods." (let ((method (tramp-file-name-method vec)) (user (tramp-file-name-user vec)) (host (tramp-file-name-host vec)) @@ -5565,13 +5575,13 @@ raises an error." ;; This does not work for MS Windows scp, if there are characters ;; to be quoted. OpenSSH 8 supports disabling of strict file name ;; checking in scp, we use it when available. - (unless (string-match-p (rx "ftp" eos) method) + (unless (string-match-p (rx (| "dockercp" "podmancp" "ftp") eos) method) (setq localname (tramp-unquote-shell-quote-argument localname))) - (cond - ((tramp-get-method-parameter vec 'tramp-remote-copy-program) - localname) - ((tramp-string-empty-or-nil-p user) (format "%s:%s" host localname)) - (t (format "%s@%s:%s" user host localname))))) + (string-join + (apply #'tramp-expand-args vec + 'tramp-copy-file-name tramp-default-copy-file-name + (list ?h (or host "") ?u (or user "") ?f localname)) + ""))) (defun tramp-method-out-of-band-p (vec size) "Return t if this is an out-of-band method, nil otherwise." @@ -5592,6 +5602,7 @@ raises an error." "Check whether REGEXP matches the connection property \"uname\"." (string-match-p regexp (tramp-get-connection-property vec "uname" ""))) +;;;###tramp-autoload (defun tramp-get-remote-path (vec) "Compile list of remote directories for PATH. Nonexistent directories are removed from spec." @@ -5634,16 +5645,14 @@ Nonexistent directories are removed from spec." (format "%s %s %s 'echo %s \\\"$PATH\\\"'" (tramp-get-method-parameter vec 'tramp-remote-shell) - (mapconcat - #'identity + (string-join (tramp-get-method-parameter vec 'tramp-remote-shell-login) " ") - (mapconcat - #'identity + (string-join (tramp-get-method-parameter vec 'tramp-remote-shell-args) " ") (tramp-shell-quote-argument tramp-end-of-heredoc)) - 'noerror (tramp-compat-rx (literal tramp-end-of-heredoc))) + 'noerror (rx (literal tramp-end-of-heredoc))) (progn (tramp-message vec 2 "Could not retrieve `tramp-own-remote-path'") @@ -5666,22 +5675,37 @@ Nonexistent directories are removed from spec." (setq remote-path (delq 'tramp-own-remote-path remote-path))) ;; Remove double entries. - (setq elt1 remote-path) - (while (consp elt1) - (while (and (car elt1) (setq elt2 (member (car elt1) (cdr elt1)))) - (setcar elt2 nil)) - (setq elt1 (cdr elt1))) + (setq remote-path + (cl-remove-duplicates + remote-path :test #'string-equal :from-end t)) ;; Remove non-existing directories. - (delq - nil - (mapcar - (lambda (x) - (and - (stringp x) - (file-directory-p (tramp-make-tramp-file-name vec x)) - x)) - remote-path)))))) + (let (remote-file-name-inhibit-cache) + (tramp-bundle-read-file-names vec remote-path) + (cl-remove-if + (lambda (x) (not (tramp-get-file-property vec x "file-directory-p"))) + remote-path)))))) + +;; The PIPE_BUF in POSIX [1] can be as low as 512 [2]. Here are the values +;; on various platforms: +;; - 512 on macOS, FreeBSD, NetBSD, OpenBSD, MirBSD, native Windows. +;; - 4 KiB on Linux, OSF/1, Cygwin, Haiku. +;; - 5 KiB on Solaris. +;; - 8 KiB on HP-UX, Plan9. +;; - 10 KiB on IRIX. +;; - 32 KiB on AIX, Minix. +;; [1] https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html +;; [2] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html +;; See Bug#65324. +;;;###tramp-autoload +(defun tramp-get-remote-pipe-buf (vec) + "Return PIPE_BUF config from the remote side." + (with-tramp-connection-property vec "pipe-buf" + (tramp-send-command-and-read + vec + (format "getconf PIPE_BUF / 2>%s || echo 4096" + (tramp-get-remote-null-device vec)) + 'noerror))) (defun tramp-get-remote-locale (vec) "Determine remote locale, supporting UTF8 if possible." @@ -5693,8 +5717,7 @@ Nonexistent directories are removed from spec." (while candidates (goto-char (point-min)) (if (string-match-p - (tramp-compat-rx bol (literal (car candidates)) (? "\r") eol) - (buffer-string)) + (rx bol (literal (car candidates)) (? "\r") eol) (buffer-string)) (setq locale (car candidates) candidates nil) (setq candidates (cdr candidates))))) @@ -5707,7 +5730,10 @@ Nonexistent directories are removed from spec." (tramp-message vec 5 "Finding a suitable `ls' command") (or (catch 'ls-found - (dolist (cmd '("ls" "gnuls" "gls")) + (dolist (cmd + ;; Prefer GNU ls on *BSD and macOS. + (if (tramp-check-remote-uname vec tramp-bsd-unames) + '( "gls" "ls" "gnuls") '("ls" "gnuls" "gls"))) (let ((dl (tramp-get-remote-path vec)) result) (while (and dl (setq result (tramp-find-executable vec cmd dl t t))) @@ -5772,7 +5798,7 @@ Nonexistent directories are removed from spec." vec (format "( %s / -nt / )" (tramp-get-test-command vec))) (with-current-buffer (tramp-get-buffer vec) (goto-char (point-min)) - (when (looking-at-p (tramp-compat-rx (literal tramp-end-of-output))) + (when (looking-at-p (rx (literal tramp-end-of-output))) (format "%s %%s -nt %%s" (tramp-get-test-command vec))))) (progn (tramp-send-command @@ -5855,14 +5881,6 @@ Nonexistent directories are removed from spec." vec (format "%s --canonicalize-missing /" result))) result)))) -(defun tramp-get-remote-trash (vec) - "Determine remote `trash' command. -This command is returned only if `delete-by-moving-to-trash' is non-nil." - (and delete-by-moving-to-trash - (with-tramp-connection-property vec "trash" - (tramp-message vec 5 "Finding a suitable `trash' command") - (tramp-find-executable vec "trash" (tramp-get-remote-path vec))))) - (defun tramp-get-remote-touch (vec) "Determine remote `touch' command." (with-tramp-connection-property vec "touch" |