Mercurial > emacs
changeset 45861:7b663a89ef2a
*** empty log message ***
author | Kai Großjohann <kgrossjo@eu.uu.net> |
---|---|
date | Mon, 17 Jun 2002 11:47:23 +0000 |
parents | 0dcc2162a55f |
children | 1bcf7f390ca2 |
files | lisp/net/tramp-vc.el lisp/net/tramp.el man/tramp.texi |
diffstat | 3 files changed, 7258 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/net/tramp-vc.el Mon Jun 17 11:47:23 2002 +0000 @@ -0,0 +1,480 @@ +;;; tramp-vc.el --- Version control integration for TRAMP.el + +;; Copyright (C) 2000 by Free Software Foundation, Inc. + +;; Author: Daniel Pittman <daniel@danann.net> +;; Keywords: comm, processes + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; See the main module, 'tramp.el' for discussion of the purpose of TRAMP. +;; This module provides integration between remote files accessed by TRAMP and +;; the Emacs version control system. + +;;; Code: + +(eval-when-compile + (require 'cl)) +(require 'vc) +;; Old VC defines vc-rcs-release in vc.el, new VC requires extra module. +(unless (boundp 'vc-rcs-release) + (require 'vc-rcs)) +(require 'tramp) + +;; -- vc -- + +;; This used to blow away the file-name-handler-alist and reinstall +;; TRAMP into it. This was intended to let VC work remotely. It didn't, +;; at least not in my XEmacs 21.2 install. +;; +;; In any case, tramp-run-real-handler now deals correctly with disabling +;; the things that should be, making this a no-op. +;; +;; I have removed it from the tramp-file-name-handler-alist because the +;; shortened version does nothing. This is for reference only now. +;; +;; Daniel Pittman <daniel@danann.net> +;; +;; (defun tramp-handle-vc-registered (file) +;; "Like `vc-registered' for tramp files." +;; (tramp-run-real-handler 'vc-registered (list file))) + +;; `vc-do-command' +;; This function does not deal well with remote files, so we define +;; our own version and make a backup of the original function and +;; call our version for tramp files and the original version for +;; normal files. + +;; The following function is pretty much copied from vc.el, but +;; the part that actually executes a command is changed. +;; CCC: this probably works for Emacs 21, too. +(defun tramp-vc-do-command (buffer okstatus command file last &rest flags) + "Like `vc-do-command' but invoked for tramp files. +See `vc-do-command' for more information." + (save-match-data + (and file (setq file (tramp-handle-expand-file-name file))) + (if (not buffer) (setq buffer "*vc*")) + (if vc-command-messages + (message "Running `%s' on `%s'..." command file)) + (let ((obuf (current-buffer)) (camefrom (current-buffer)) + (squeezed nil) + (olddir default-directory) + vc-file status) + (let* ((v (tramp-dissect-file-name (tramp-handle-expand-file-name file))) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v))) + (set-buffer (get-buffer-create buffer)) + (set (make-local-variable 'vc-parent-buffer) camefrom) + (set (make-local-variable 'vc-parent-buffer-name) + (concat " from " (buffer-name camefrom))) + (setq default-directory olddir) + + (erase-buffer) + + (mapcar + (function + (lambda (s) (and s (setq squeezed (append squeezed (list s)))))) + flags) + (if (and (eq last 'MASTER) file + (setq vc-file (vc-name file))) + (setq squeezed + (append squeezed + (list (tramp-file-name-path + (tramp-dissect-file-name vc-file)))))) + (if (and file (eq last 'WORKFILE)) + (progn + (let* ((pwd (expand-file-name default-directory)) + (preflen (length pwd))) + (if (string= (substring file 0 preflen) pwd) + (setq file (substring file preflen)))) + (setq squeezed (append squeezed (list file))))) + ;; Unless we (save-window-excursion) the layout of windows in + ;; the current frame changes. This is painful, at best. + ;; + ;; As a point of note, (save-excursion) is still here only because + ;; it preserves (point) in the current buffer. (save-window-excursion) + ;; does not, at least under XEmacs 21.2. + ;; + ;; I trust that the FSF support this as well. I can't find useful + ;; documentation to check :( + ;; + ;; Daniel Pittman <daniel@danann.net> + (save-excursion + (save-window-excursion + ;; Actually execute remote command + (tramp-handle-shell-command + (mapconcat 'tramp-shell-quote-argument + (cons command squeezed) " ") t) + ;;(tramp-wait-for-output) + ;; Get status from command + (tramp-send-command multi-method method user host "echo $?") + (tramp-wait-for-output) + ;; Make sure to get status from last line of output. + (goto-char (point-max)) (forward-line -1) + (setq status (read (current-buffer))) + (message "Command %s returned status %d." command status))) + (goto-char (point-max)) + (set-buffer-modified-p nil) + (forward-line -1) + (if (or (not (integerp status)) (and okstatus (< okstatus status))) + (progn + (pop-to-buffer buffer) + (goto-char (point-min)) + (shrink-window-if-larger-than-buffer) + (error "Running `%s'...FAILED (%s)" command + (if (integerp status) + (format "status %d" status) + status)) + ) + (if vc-command-messages + (message "Running %s...OK" command)) + ) + (set-buffer obuf) + status)) + )) + +;; Following code snarfed from Emacs 21 vc.el and slightly tweaked. +(defun tramp-vc-do-command-new (buffer okstatus command file &rest flags) + "Like `vc-do-command' but for TRAMP files. +This function is for the new VC which comes with Emacs 21. +Since TRAMP doesn't do async commands yet, this function doesn't, either." + (and file (setq file (expand-file-name file))) + (if vc-command-messages + (message "Running %s on %s..." command file)) + (save-current-buffer + (unless (eq buffer t) (vc-setup-buffer buffer)) + (let ((squeezed nil) + (inhibit-read-only t) + (status 0)) + (let* ((v (when file (tramp-dissect-file-name file))) + (multi-method (when file (tramp-file-name-multi-method v))) + (method (when file (tramp-file-name-method v))) + (user (when file (tramp-file-name-user v))) + (host (when file (tramp-file-name-host v))) + (path (when file (tramp-file-name-path v)))) + (setq squeezed (delq nil (copy-sequence flags))) + (when file + (setq squeezed (append squeezed (list path)))) + (let ((w32-quote-process-args t)) + (when (eq okstatus 'async) + (message "Tramp doesn't do async commands, running synchronously.")) + (setq status (tramp-handle-shell-command + (mapconcat 'tramp-shell-quote-argument + (cons command squeezed) " ") t)) + (when (or (not (integerp status)) (and okstatus (< okstatus status))) + (pop-to-buffer (current-buffer)) + (goto-char (point-min)) + (shrink-window-if-larger-than-buffer) + (error "Running %s...FAILED (%s)" command + (if (integerp status) (format "status %d" status) status)))) + (if vc-command-messages + (message "Running %s...OK" command)) + (vc-exec-after + `(run-hook-with-args + 'vc-post-command-functions ',command ',path ',flags)) + status)))) + + +;; The context for a VC command is the current buffer. +;; That makes a test on the buffers file more reliable than a test on the +;; arguments. +;; This is needed to handle remote VC correctly - else we test against the +;; local VC system and get things wrong... +;; Daniel Pittman <daniel@danann.net> +;;-(if (fboundp 'vc-call-backend) +;;- () ;; This is the new VC for which we don't have an appropriate advice yet +(if (fboundp 'vc-call-backend) + (defadvice vc-do-command + (around tramp-advice-vc-do-command + (buffer okstatus command file &rest flags) + activate) + "Invoke tramp-vc-do-command for tramp files." + (let ((file (symbol-value 'file))) ;pacify byte-compiler + (if (or (and (stringp file) (tramp-tramp-file-p file)) + (and (buffer-file-name) (tramp-tramp-file-p (buffer-file-name)))) + (setq ad-return-value + (apply 'tramp-vc-do-command-new buffer okstatus command + file ;(or file (buffer-file-name)) + flags)) + ad-do-it))) + (defadvice vc-do-command + (around tramp-advice-vc-do-command + (buffer okstatus command file last &rest flags) + activate) + "Invoke tramp-vc-do-command for tramp files." + (let ((file (symbol-value 'file))) ;pacify byte-compiler + (if (or (and (stringp file) (tramp-tramp-file-p file)) + (and (buffer-file-name) (tramp-tramp-file-p (buffer-file-name)))) + (setq ad-return-value + (apply 'tramp-vc-do-command buffer okstatus command + (or file (buffer-file-name)) last flags)) + ad-do-it)))) +;;-) + + +;; XEmacs uses this to do some of its work. Like vc-do-command, we +;; need to enhance it to make VC work via TRAMP-mode. +;; +;; Like the previous function, this is a cut-and-paste job from the VC +;; file. It's based on the vc-do-command code. +;; CCC: this isn't used in Emacs 21, so do as before. +(defun tramp-vc-simple-command (okstatus command file &rest args) + ;; Simple version of vc-do-command, for use in vc-hooks only. + ;; Don't switch to the *vc-info* buffer before running the + ;; command, because that would change its default directory + (save-match-data + (let* ((v (tramp-dissect-file-name (tramp-handle-expand-file-name file))) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v))) + (save-excursion (set-buffer (get-buffer-create "*vc-info*")) + (erase-buffer)) + (let ((exec-path (append vc-path exec-path)) exec-status + ;; Add vc-path to PATH for the execution of this command. + (process-environment + (cons (concat "PATH=" (getenv "PATH") + path-separator + (mapconcat 'identity vc-path path-separator)) + process-environment))) + ;; Call the actual process. See tramp-vc-do-command for discussion of + ;; why this does both (save-window-excursion) and (save-excursion). + ;; + ;; As a note, I don't think that the process-environment stuff above + ;; has any effect on the remote system. This is a hard one though as + ;; there is no real reason to expect local and remote paths to be + ;; identical... + ;; + ;; Daniel Pittman <daniel@danann.net> + (save-excursion + (save-window-excursion + ;; Actually execute remote command + (tramp-handle-shell-command + (mapconcat 'tramp-shell-quote-argument + (append (list command) args (list path)) " ") + (get-buffer-create"*vc-info*")) + ;(tramp-wait-for-output) + ;; Get status from command + (tramp-send-command multi-method method user host "echo $?") + (tramp-wait-for-output) + (setq exec-status (read (current-buffer))) + (message "Command %s returned status %d." command exec-status))) + + (cond ((> exec-status okstatus) + (switch-to-buffer (get-file-buffer file)) + (shrink-window-if-larger-than-buffer + (display-buffer "*vc-info*")) + (error "Couldn't find version control information"))) + exec-status)))) + +;; This function does not exist any more in Emacs-21's VC +(defadvice vc-simple-command + (around tramp-advice-vc-simple-command + (okstatus command file &rest args) + activate) + "Invoke tramp-vc-simple-command for tramp files." + (let ((file (symbol-value 'file))) ;pacify byte-compiler + (if (or (and (stringp file) (tramp-tramp-file-p file)) + (and (buffer-file-name) (tramp-tramp-file-p (buffer-file-name)))) + (setq ad-return-value + (apply 'tramp-vc-simple-command okstatus command + (or file (buffer-file-name)) args)) + ad-do-it))) + + +;; `vc-workfile-unchanged-p' +;; This function does not deal well with remote files, so we do the +;; same as for `vc-do-command'. + +;; `vc-workfile-unchanged-p' checks the modification time, we cannot +;; do that for remote files, so here's a version which relies on diff. +;; CCC: this one probably works for Emacs 21, too. +(defun tramp-vc-workfile-unchanged-p + (filename &optional want-differences-if-changed) + (if (fboundp 'vc-backend-diff) + ;; Old VC. Call `vc-backend-diff'. + (let ((status (funcall (symbol-function 'vc-backend-diff) + filename nil nil + (not want-differences-if-changed)))) + (zerop status)) + ;; New VC. Call `vc-default-workfile-unchanged-p'. + (vc-default-workfile-unchanged-p (vc-backend file) filename))) + +(defadvice vc-workfile-unchanged-p + (around tramp-advice-vc-workfile-unchanged-p + (filename &optional want-differences-if-changed) + activate) + "Invoke tramp-vc-workfile-unchanged-p for tramp files." + (if (and (stringp filename) + (tramp-tramp-file-p filename) + (not + (let ((v (tramp-dissect-file-name filename))) + (tramp-get-remote-perl (tramp-file-name-multi-method v) + (tramp-file-name-method v) + (tramp-file-name-user v) + (tramp-file-name-host v))))) + (setq ad-return-value + (tramp-vc-workfile-unchanged-p filename want-differences-if-changed)) + ad-do-it)) + + +;; Redefine a function from vc.el -- allow tramp files. +;; `save-match-data' seems not to be required -- it isn't in +;; the original version, either. +;; CCC: this might need some work -- how does the Emacs 21 version +;; work, anyway? Does it work over ange-ftp? Hm. +(if (not (fboundp 'vc-backend-checkout)) + () ;; our replacement won't work and is unnecessary anyway +(defun vc-checkout (filename &optional writable rev) + "Retrieve a copy of the latest version of the given file." + ;; If ftp is on this system and the name matches the ange-ftp format + ;; for a remote file, the user is trying something that won't work. + (funcall (symbol-function 'vc-backend-checkout) filename writable rev) + (vc-resynch-buffer filename t t)) +) + + +;; Do we need to advise the vc-user-login-name function anyway? +;; This will return the correct login name for the owner of a +;; file. It does not deal with the default remote user name... +;; +;; That is, when vc calls (vc-user-login-name), we return the +;; local login name, something that may be different to the remote +;; default. +;; +;; The remote VC operations will occur as the user that we logged +;; in with however - not always the same as the local user. +;; +;; In the end, I did advise the function. This is because, well, +;; the thing didn't work right otherwise ;) +;; +;; Daniel Pittman <daniel@danann.net> + +(defun tramp-handle-vc-user-login-name (&optional uid) + "Return the default user name on the remote machine. +Whenever VC calls this function, `file' is bound to the file name +in question. If no uid is provided or the uid is equal to the uid +owning the file, then we return the user name given in the file name. + +This should only be called when `file' is bound to the +filename we are thinking about..." + ;; Pacify byte-compiler; this symbol is bound in the calling + ;; function. CCC: Maybe it would be better to move the + ;; boundness-checking into this function? + (let ((file (symbol-value 'file))) + (if (and uid (/= uid (nth 2 (file-attributes file)))) + (error "tramp-handle-vc-user-login-name cannot map a uid to a name") + (let* ((v (tramp-dissect-file-name (tramp-handle-expand-file-name file))) + (u (tramp-file-name-user v))) + (cond ((stringp u) u) + ((vectorp u) (elt u (1- (length u)))) + ((null u) (user-login-name)) + (t (error "tramp-handle-vc-user-login-name cannot cope!"))))))) + + +(defadvice vc-user-login-name + (around tramp-vc-user-login-name activate) + "Support for files on remote machines accessed by TRAMP." + ;; We rely on the fact that `file' is bound when this is called. + ;; This appears to be the case everywhere in vc.el and vc-hooks.el + ;; as of Emacs 20.5. + ;; + ;; CCC TODO there should be a real solution! Talk to Andre Spiegel + ;; about this. + (let ((file (when (boundp 'file) + (symbol-value 'file)))) ;pacify byte-compiler + (or (and (stringp file) + (tramp-tramp-file-p file) ; tramp file + (setq ad-return-value + (save-match-data + (tramp-handle-vc-user-login-name uid)))) ; get the owner name + ad-do-it))) ; else call the original + + +;; Determine the name of the user owning a file. +(defun tramp-file-owner (filename) + "Return who owns FILE (user name, as a string)." + (let ((v (tramp-dissect-file-name + (tramp-handle-expand-file-name filename)))) + (if (not (tramp-handle-file-exists-p filename)) + nil ; file cannot be opened + ;; file exists, find out stuff + (save-excursion + (tramp-send-command + (tramp-file-name-multi-method v) (tramp-file-name-method v) + (tramp-file-name-user v) (tramp-file-name-host v) + (format "%s -Lld %s" + (tramp-get-ls-command (tramp-file-name-multi-method v) + (tramp-file-name-method v) + (tramp-file-name-user v) + (tramp-file-name-host v)) + (tramp-shell-quote-argument (tramp-file-name-path v)))) + (tramp-wait-for-output) + ;; parse `ls -l' output ... + ;; ... file mode flags + (read (current-buffer)) + ;; ... number links + (read (current-buffer)) + ;; ... uid (as a string) + (symbol-name (read (current-buffer))))))) + +;; Wire ourselves into the VC infrastructure... +;; This function does not exist any more in Emacs-21's VC +;; CCC: it appears that no substitute is needed for Emacs 21. +(defadvice vc-file-owner + (around tramp-vc-file-owner activate) + "Support for files on remote machines accessed by TRAMP." + (let ((filename (ad-get-arg 0))) + (or (and (tramp-file-name-p filename) ; tramp file + (setq ad-return-value + (save-match-data + (tramp-file-owner filename)))) ; get the owner name + ad-do-it))) ; else call the original + + +;; We need to make the version control software backend version +;; information local to the current buffer. This is because each TRAMP +;; buffer can (theoretically) have a different VC version and I am +;; *way* too lazy to try and push the correct value into each new +;; buffer. +;; +;; Remote VC costs will just have to be paid, at least for the moment. +;; Well, at least, they will right until I feel guilty about doing a +;; botch job here and fix it. :/ +;; +;; Daniel Pittman <daniel@danann.net> +;; CCC: this is probably still needed for Emacs 21. +(defun tramp-vc-setup-for-remote () + "Make the backend release variables buffer local. +This makes remote VC work correctly at the cost of some processing time." + (when (and (buffer-file-name) + (tramp-tramp-file-p (buffer-file-name))) + (make-local-variable 'vc-rcs-release) + (setq vc-rcs-release nil))) +(add-hook 'find-file-hooks 'tramp-vc-setup-for-remote t) + +;; No need to load this again if anyone asks. +(provide 'tramp-vc) + +;;; tramp-vc.el ends here
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/net/tramp.el Mon Jun 17 11:47:23 2002 +0000 @@ -0,0 +1,5152 @@ +;;; tramp.el --- Transparent Remote Access, Multiple Protocol -*- coding: iso-8859-1; -*- + +;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + +;; Author: Kai.Grossjohann@CS.Uni-Dortmund.DE +;; Keywords: comm, processes + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; This package provides remote file editing, similar to ange-ftp. +;; The difference is that ange-ftp uses FTP to transfer files between +;; the local and the remote host, whereas tramp.el uses a combination +;; of rsh and rcp or other work-alike programs, such as ssh/scp. +;; +;; For more detailed instructions, please see the info file, which is +;; included in the file `tramp.tar.gz' mentioned below. +;; +;; Notes: +;; ----- +;; +;; This package only works for Emacs 20 and higher, and for XEmacs 21 +;; and higher. (XEmacs 20 is missing the `with-timeout' macro. Emacs +;; 19 is reported to have other problems. For XEmacs 21, you need the +;; package `fsf-compat' for the `with-timeout' macro.) +;; +;; This version might not work with pre-Emacs 21 VC unless VC is +;; loaded before tramp.el. Could you please test this and tell me about +;; the result? Thanks. +;; +;; Also see the todo list at the bottom of this file. +;; +;; The current version of tramp.el can be retrieved from the following +;; URL: ftp://ls6-ftp.cs.uni-dortmund.de/pub/src/emacs/tramp.tar.gz +;; For your convenience, the *.el file is available separately from +;; the same directory. +;; +;; There's a mailing list for this, as well. Its name is: +;; tramp-devel@lists.sourceforge.net +;; Send a mail with `help' in the subject (!) to the administration +;; address for instructions on joining the list. The administration +;; address is: +;; tramp-devel-request@lists.sourceforge.net +;; You can also use the Web to subscribe, under the following URL: +;; http://lists.sourceforge.net/lists/listinfo/tramp-devel +;; +;; For the adventurous, the current development sources are available +;; via CVS. You can find instructions about this at the following URL: +;; http://sourceforge.net/projects/tramp/ +;; Click on "CVS" in the navigation bar near the top. +;; +;; Don't forget to put on your asbestos longjohns, first! + +;;; Code: + +(defconst tramp-version "2.0.0" + "This version of tramp.") +(defconst tramp-bug-report-address "tramp-devel@mail.freesoftware.fsf.org" + "Email address to send bug reports to.") + +(require 'timer) +(require 'format-spec) ;from Gnus 5.8, also in tar ball +(require 'base64) ;for the mimencode methods +(require 'shell) +(require 'advice) + +;; ;; It does not work to load EFS after loading TRAMP. +;; (when (fboundp 'efs-file-handler-function) +;; (require 'efs)) + +(eval-when-compile + (require 'cl) + (require 'custom) + ;; Emacs 19.34 compatibility hack -- is this needed? + (or (>= emacs-major-version 20) + (load "cl-seq"))) + +(unless (boundp 'custom-print-functions) + (defvar custom-print-functions nil)) ; not autoloaded before Emacs 20.4 + +;;; User Customizable Internal Variables: + +(defgroup tramp nil + "Edit remote files with a combination of rsh and rcp or similar programs." + :group 'files) + +(defcustom tramp-verbose 10 + "*Verbosity level for tramp.el. 0 means be silent, 10 is most verbose." + :group 'tramp + :type 'integer) + +(defcustom tramp-debug-buffer nil + "*Whether to send all commands and responses to a debug buffer." + :group 'tramp + :type 'boolean) + +(defcustom tramp-auto-save-directory nil + "*Put auto-save files in this directory, if set. +The idea is to use a local directory so that auto-saving is faster." + :group 'tramp + :type '(choice (const nil) + string)) + +(defcustom tramp-sh-program "/bin/sh" + "*Use this program for shell commands on the local host. +This MUST be a Bourne-like shell. This shell is used to execute +the encoding and decoding command on the local host, so if you +want to use `~' in those commands, you should choose a shell here +which groks tilde expansion. `/bin/sh' normally does not +understand tilde expansion. + +Note that this variable is not used for remote commands. There are +mechanisms in tramp.el which automatically determine the right shell to +use for the remote host." + :group 'tramp + :type '(file :must-match t)) + +;; CCC I have changed all occurrences of comint-quote-filename with +;; tramp-shell-quote-argument, except in tramp-handle-expand-many-files. +;; There, comint-quote-filename was removed altogether. If it turns +;; out to be necessary there, something will need to be done. +;;-(defcustom tramp-file-name-quote-list +;;- '(?] ?[ ?\| ?& ?< ?> ?\( ?\) ?\; ?\ ?\* ?\? ?\! ?\" ?\' ?\` ?# ?\@ ?\+ ) +;;- "*Protect these characters from the remote shell. +;;-Any character in this list is quoted (preceded with a backslash) +;;-because it means something special to the shell. This takes effect +;;-when sending file and directory names to the remote shell. +;;- +;;-See `comint-file-name-quote-list' for details." +;;- :group 'tramp +;;- :type '(repeat character)) + +(defcustom tramp-methods + '( ("rcp" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "rsh") + (tramp-rcp-program "rcp") + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg "-p") + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command nil) + (tramp-decoding-command nil) + (tramp-encoding-function nil) + (tramp-decoding-function nil) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("scp" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh") + (tramp-rcp-program "scp") + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg "-p") + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command nil) + (tramp-decoding-command nil) + (tramp-encoding-function nil) + (tramp-decoding-function nil) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("scp1" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh1") + (tramp-rcp-program "scp1") + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg "-p") + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command nil) + (tramp-decoding-command nil) + (tramp-encoding-function nil) + (tramp-decoding-function nil) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("scp2" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh2") + (tramp-rcp-program "scp2") + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg "-p") + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command nil) + (tramp-decoding-command nil) + (tramp-encoding-function nil) + (tramp-decoding-function nil) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("rsync" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh") + (tramp-rcp-program "rsync") + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args ("-e" "ssh")) + (tramp-rcp-keep-date-arg "-t") + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command nil) + (tramp-decoding-command nil) + (tramp-encoding-function nil) + (tramp-decoding-function nil) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("ru" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "rsh") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("su" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("su1" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh1") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("su2" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh2") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("rm" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "rsh") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("sm" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("smp" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "tramp_mimencode") + (tramp-decoding-command "tramp_mimedecode") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil)) + ("sm1" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh1") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("sm2" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh2") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("tm" (tramp-connection-function tramp-open-connection-telnet) + (tramp-rsh-program nil) + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program "telnet") + (tramp-telnet-args nil)) + ("tu" (tramp-connection-function tramp-open-connection-telnet) + (tramp-rsh-program nil) + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program "telnet") + (tramp-telnet-args nil)) + ("sum" (tramp-connection-function tramp-open-connection-su) + (tramp-rsh-program nil) + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program "su") + (tramp-su-args ("-" "%u")) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("suu" (tramp-connection-function tramp-open-connection-su) + (tramp-rsh-program nil) + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program "su") + (tramp-su-args ("-" "%u")) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("sudm" (tramp-connection-function tramp-open-connection-su) + (tramp-rsh-program nil) + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program "sudo") + (tramp-su-args ("-u" "%u" "-s")) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("sudu" (tramp-connection-function tramp-open-connection-su) + (tramp-rsh-program nil) + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program "sudo") + (tramp-su-args ("-u" "%u" "-s")) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("multi" (tramp-connection-function tramp-open-connection-multi) + (tramp-rsh-program nil) + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("multiu" (tramp-connection-function tramp-open-connection-multi) + (tramp-rsh-program nil) + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args nil) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("scpx" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh") + (tramp-rcp-program "scp") + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none" "-t" "-t" "/bin/sh")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg "-p") + (tramp-encoding-command nil) + (tramp-decoding-command nil) + (tramp-encoding-function nil) + (tramp-decoding-function nil) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("smx" (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "ssh") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-e" "none" "-t" "-t" "/bin/sh")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("km" + (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "krlogin") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-x")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("plinku" + (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "plink") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-ssh")) ;optionally add "-v" + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "uuencode xxx") + (tramp-decoding-command + "( uudecode -o - 2>/dev/null || uudecode -p 2>/dev/null )") + (tramp-encoding-function nil) + (tramp-decoding-function uudecode-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("plinkm" + (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "plink") + (tramp-rcp-program nil) + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-ssh")) ;optionally add "-v" + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg nil) + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command "mimencode -b") + (tramp-decoding-command "mimencode -u -b") + (tramp-encoding-function base64-encode-region) + (tramp-decoding-function base64-decode-region) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("pscp" + (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "plink") + (tramp-rcp-program "pscp") + (tramp-remote-sh "/bin/sh") + (tramp-rsh-args ("-ssh")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg "-p") + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command nil) + (tramp-decoding-command nil) + (tramp-encoding-function nil) + (tramp-decoding-function nil) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ("fcp" + (tramp-connection-function tramp-open-connection-rsh) + (tramp-rsh-program "fsh") + (tramp-rcp-program "fcp") + (tramp-remote-sh "/bin/sh -i") + (tramp-rsh-args ("sh" "-i")) + (tramp-rcp-args nil) + (tramp-rcp-keep-date-arg "-p") + (tramp-su-program nil) + (tramp-su-args nil) + (tramp-encoding-command nil) + (tramp-decoding-command nil) + (tramp-encoding-function nil) + (tramp-decoding-function nil) + (tramp-telnet-program nil) + (tramp-telnet-args nil)) + ) + "*Alist of methods for remote files. +This is a list of entries of the form (NAME PARAM1 PARAM2 ...). +Each NAME stands for a remote access method. Each PARAM is a +pair of the form (KEY VALUE). The following KEYs are defined: + * `tramp-connection-function' + This specifies the function to use to connect to the remote host. + Currently, `tramp-open-connection-rsh', `tramp-open-connection-telnet' + and `tramp-open-connection-su' are defined. See the documentation + of these functions for more details. + * `tramp-remote-sh' + This specifies the Bourne shell to use on the remote host. This + MUST be a Bourne-like shell. It is normally not necessary to set + this to any value other than \"/bin/sh\": tramp wants to use a shell + which groks tilde expansion, but it can search for it. Also note + that \"/bin/sh\" exists on all Unixen, this might not be true for + the value that you decide to use. You Have Been Warned. + * `tramp-rsh-program' + This specifies the name of the program to use for rsh; this might be + the full path to rsh or the name of a workalike program. + * `tramp-rsh-args' + This specifies the list of arguments to pass to the above + mentioned program. Please note that this is a list of arguments, + that is, normally you don't want to put \"-a -b\" or \"-f foo\" + here. Instead, you want two list elements, one for \"-a\" and one + for \"-b\", or one for \"-f\" and one for \"foo\". + * `tramp-rcp-program' + This specifies the name of the program to use for rcp; this might be + the full path to rcp or the name of a workalike program. + * `tramp-rcp-args' + This specifies the list of parameters to pass to the above mentioned + program, the hints for `tramp-rsh-args' also apply here. + * `tramp-rcp-keep-date-arg' + This specifies the parameter to use for `rcp' when the timestamp + of the original file should be kept. For `rcp', use `-p', for + `rsync', use `-t'. + * `tramp-su-program' + This specifies the name of the program to use for `su'. + * `tramp-su-args' + This specifies the list of arguments to pass to `su'. + \"%u\" is replaced by the user name, use \"%%\" for a literal + percent character. + * `tramp-encoding-command' + This specifies a command to use to encode the file contents for + transfer. The command should read the raw file contents from + standard input and write the encoded file contents to standard + output. In this string, the percent escape \"%f\" should be used + to indicate the file to convert. Use \"%%\" if you need a literal + percent character in your command. + * `tramp-decoding-command' + This specifies a command to use to decode file contents encoded + with `tramp-encoding-command'. The command should read from standard + input and write to standard output. + * `tramp-encoding-function' + This specifies a function to be called to encode the file contents + on the local side. This function should accept two arguments + START and END, the beginning and end of the region to encode. The + region should be replaced with the encoded contents. + * `tramp-decoding-function' + Same for decoding on the local side. + * `tramp-telnet-program' + Specifies the telnet program to use when using + `tramp-open-connection-telnet' to log in. + * `tramp-telnet-args' + Specifies list of arguments to pass to `telnet'. The hints for + `tramp-rsh-args' also apply here. + +What does all this mean? Well, you should specify `tramp-rsh-program', +`tramp-telnet-program' or `tramp-su-program' for all methods; this program +is used to log in to the remote site. Then, there are two ways to +actually transfer the files between the local and the remote side. +One way is using an additional rcp-like program. If you want to do +this, set `tramp-rcp-program' in the method. + +Another possibility for file transfer is inline transfer, i.e. the +file is passed through the same buffer used by `tramp-rsh-program'. In +this case, the file contents need to be protected since the +`tramp-rsh-program' might use escape codes or the connection might not +be eight-bit clean. Therefore, file contents are encoded for transit. + +Two possibilities for encoding are uuencode/uudecode and mimencode. +For uuencode/uudecode you want to set `tramp-encoding-command' to +something like \"uuencode\" and `tramp-decoding-command' to \"uudecode +-p\". For mimencode you want to set `tramp-encoding-command' to +something like \"mimencode -b\" and `tramp-decoding-command' to +\"mimencode -b -u\". + +When using inline transfer, you can use a program or a Lisp function +on the local side to encode or decode the file contents. Set the +`tramp-encoding-function' and `tramp-decoding-function' parameters to nil +in order to use the commands or to the function to use. It is +possible to specify one function and the other parameter as nil. + +So, to summarize: if the method is an inline method, you must specify +`tramp-encoding-command' and `tramp-decoding-command', and +`tramp-rcp-program' must be nil. If the method is out of band, then +you must specify `tramp-rcp-program' and `tramp-rcp-args' and +`tramp-encoding-command' and `tramp-decoding-command' must be nil. +Every method, inline or out of band, must specify +`tramp-connection-function' plus the associated arguments (for +example, the telnet program if you chose +`tramp-open-connection-telnet'). + +Notes: + +When using `tramp-open-connection-su' the phrase `open connection to a +remote host' sounds strange, but it is used nevertheless, for +consistency. No connection is opened to a remote host, but `su' is +started on the local host. You are not allowed to specify a remote +host other than `localhost' or the name of the local host. + +Using a uuencode/uudecode inline method is discouraged, please use one +of the base64 methods instead since base64 encoding is much more +reliable and the commands are more standardized between the different +Unix versions. But if you can't use base64 for some reason, please +note that the default uudecode command does not work well for some +Unices, in particular AIX and Irix. For AIX, you might want to use +the following command for uudecode: + + sed '/^begin/d;/^[` ]$/d;/^end/d' | iconv -f uucode -t ISO8859-1 + +For Irix, no solution is known yet." + :group 'tramp + :type '(repeat + (cons string + (set (list (const tramp-connection-function) function) + (list (const tramp-rsh-program) + (choice (const nil) string)) + (list (const tramp-rcp-program) + (choice (const nil) string)) + (list (const tramp-remote-sh) + (choice (const nil) string)) + (list (const tramp-rsh-args) (repeat string)) + (list (const tramp-rcp-args) (repeat string)) + (list (const tramp-rcp-keep-date-arg) + (choice (const nil) string)) + (list (const tramp-su-program) + (choice (const nil) string)) + (list (const tramp-su-args) (repeat string)) + (list (const tramp-encoding-command) + (choice (const nil) string)) + (list (const tramp-decoding-command) + (choice (const nil) string)) + (list (const tramp-encoding-function) + (choice (const nil) function)) + (list (const tramp-decoding-function) + (choice (const nil) function)) + (list (const tramp-telnet-program) + (choice (const nil) string)) + (list (const tramp-telnet-args) (repeat string)))))) + +(defcustom tramp-multi-methods '("multi" "multiu") + "*List of multi-hop methods. +Each entry in this list should be a method name as mentioned in the +variable `tramp-methods'." + :group 'tramp + :type '(repeat string)) + +(defcustom tramp-multi-connection-function-alist + '(("telnet" tramp-multi-connect-telnet "telnet %h%n") + ("rsh" tramp-multi-connect-rlogin "rsh %h -l %u%n") + ("ssh" tramp-multi-connect-rlogin "ssh %h -l %u%n") + ("su" tramp-multi-connect-su "su - %u%n") + ("sudo" tramp-multi-connect-su "sudo -u %u -s%n")) + "*List of connection functions for multi-hop methods. +Each list item is a list of three items (METHOD FUNCTION COMMAND), +where METHOD is the name as used in the file name, FUNCTION is the +function to be executed, and COMMAND is the shell command used for +connecting. + +COMMAND may contain percent escapes. `%u' will be replaced with the +user name, `%h' will be replaced with the host name, and `%n' will be +replaced with an end-of-line character, as specified in the variable +`tramp-rsh-end-of-line'. Use `%%' for a literal percent character. +Note that the interpretation of the percent escapes also depends on +the FUNCTION. For example, the `%u' escape is forbidden with the +function `tramp-multi-connect-telnet'. See the documentation of the +various functions for details." + :group 'tramp + :type '(repeat (list string function string))) + +(defcustom tramp-default-method "rcp" + "*Default method to use for transferring files. +See `tramp-methods' for possibilities." + :group 'tramp + :type 'string) + +(defcustom tramp-rsh-end-of-line "\n" + "*String used for end of line in rsh connections. +I don't think this ever needs to be changed, so please tell me about it +if you need to change this." + :group 'tramp + :type 'string) + +(defcustom tramp-remote-path + '("/bin" "/usr/bin" "/usr/sbin" "/usr/local/bin" "/usr/ccs/bin" + "/local/bin" "/local/freeware/bin" "/local/gnu/bin" + "/usr/freeware/bin" "/usr/pkg/bin" "/usr/contrib/bin") + "*List of directories to search for executables on remote host. +Please notify me about other semi-standard directories to include here. + +You can use `~' in this list, but when searching for a shell which groks +tilde expansion, all directory names starting with `~' will be ignored." + :group 'tramp + :type '(repeat string)) + +(defcustom tramp-login-prompt-regexp + ".*ogin: *$" + "*Regexp matching login-like prompts. +The regexp should match the whole line." + :group 'tramp + :type 'regexp) + +(defcustom tramp-password-prompt-regexp + "^.*\\([pP]assword\\|passphrase.*\\):\^@? *$" + "*Regexp matching password-like prompts. +The regexp should match the whole line. + +The `sudo' program appears to insert a `^@' character into the prompt." + :group 'tramp + :type 'regexp) + +(defcustom tramp-wrong-passwd-regexp + (concat "^.*\\(Permission denied.\\|Login [Ii]ncorrect\\|" + "Received signal [0-9]+\\|Connection \\(refused\\|closed\\)\\|" + "Sorry, try again.\\|Name or service not known\\).*$") + "*Regexp matching a `login failed' message. +The regexp should match the whole line." + :group 'tramp + :type 'regexp) + +(defcustom tramp-temp-name-prefix "tramp." + "*Prefix to use for temporary files. +If this is a relative file name (such as \"tramp.\"), it is considered +relative to the directory name returned by the function +`tramp-temporary-file-directory' (which see). It may also be an +absolute file name; don't forget to include a prefix for the filename +part, though." + :group 'tramp + :type 'string) + +(defcustom tramp-discard-garbage nil + "*If non-nil, try to discard garbage sent by remote shell. +Some shells send such garbage upon connection setup." + :group 'tramp + :type 'boolean) + +;; File name format. + +(defcustom tramp-file-name-structure + (list "\\`/\\[\\(\\([a-zA-Z0-9]+\\)/\\)?\\(\\([-a-zA-Z0-9_#/:]+\\)@\\)?\\([-a-zA-Z0-9_#/:@.]+\\)\\]\\(.*\\)\\'" + 2 4 5 6) + "*List of five elements (REGEXP METHOD USER HOST FILE), detailing \ +the tramp file name structure. + +The first element REGEXP is a regular expression matching a tramp file +name. The regex should contain parentheses around the method name, +the user name, the host name, and the file name parts. + +The second element METHOD is a number, saying which pair of +parentheses matches the method name. The third element USER is +similar, but for the user name. The fourth element HOST is similar, +but for the host name. The fifth element FILE is for the file name. +These numbers are passed directly to `match-string', which see. That +means the opening parentheses are counted to identify the pair. + +See also `tramp-file-name-regexp' and `tramp-make-tramp-file-format'." + :group 'tramp + :type '(list (regexp :tag "File name regexp") + (integer :tag "Paren pair for method name") + (integer :tag "Paren pair for user name ") + (integer :tag "Paren pair for host name ") + (integer :tag "Paren pair for file name "))) + +;;;###autoload +(defcustom tramp-file-name-regexp "\\`/\\[.*\\]" + "*Regular expression matching file names handled by tramp. +This regexp should match tramp file names but no other file names. +\(When tramp.el is loaded, this regular expression is prepended to +`file-name-handler-alist', and that is searched sequentially. Thus, +if the tramp entry appears rather early in the `file-name-handler-alist' +and is a bit too general, then some files might be considered tramp +files which are not really tramp files. + +Please note that the entry in `file-name-handler-alist' is made when +this file (tramp.el) is loaded. This means that this variable must be set +before loading tramp.el. Alternatively, `file-name-handler-alist' can be +updated after changing this variable. + +Also see `tramp-file-name-structure' and `tramp-make-tramp-file-format'." + :group 'tramp + :type 'regexp) + +(defcustom tramp-make-tramp-file-format "/[%m/%u@%h]%p" + "*Format string saying how to construct tramp file name. +`%m' is replaced by the method name. +`%u' is replaced by the user name. +`%h' is replaced by the host name. +`%p' is replaced by the file name. +`%%' is replaced by %. + +Also see `tramp-file-name-structure' and `tramp-file-name-regexp'." + :group 'tramp + :type 'string) + +;; HHH: New. This format spec is made to handle the cases where the +;; user does not provide a user name for the connection. +(defcustom tramp-make-tramp-file-user-nil-format "/[%m/%h]%p" + "*Format string saying how to construct tramp file name when the user name is not known. +`%m' is replaced by the method name. +`%h' is replaced by the host name. +`%p' is replaced by the file name. +`%%' is replaced by %. + +Also see `tramp-make-tramp-file-format', `tramp-file-name-structure', and `tramp-file-name-regexp'." + :group 'tramp + :type 'string) + +(defcustom tramp-multi-file-name-structure + (list (concat + ;; prefix + "\\`/\\[\\(\\([a-z0-9]+\\)\\)?" + ;; regexp specifying a hop + "\\(\\(%s\\)+\\)" + ;; path name + "\\]\\(.*\\)\\'") + 2 ;number of pair to match method + 3 ;number of pair to match hops + -1) ;number of pair to match path + + "*Describes the file name structure of `multi' files. +Multi files allow you to contact a remote host in several hops. +This is a list of four elements (REGEXP METHOD HOP PATH). + +The first element, REGEXP, gives a regular expression to match against +the file name. In this regular expression, `%s' is replaced with the +value of `tramp-multi-file-name-hop-structure'. (Note: in order to +allow multiple hops, you normally want to use something like +\"\\\\(\\\\(%s\\\\)+\\\\)\" in the regular expression. The outer pair +of parentheses is used for the HOP element, see below.) + +All remaining elements are numbers. METHOD gives the number of the +paren pair which matches the method name. HOP gives the number of the +paren pair which matches the hop sequence. PATH gives the number of +the paren pair which matches the path name on the remote host. + +PATH can also be negative, which means to count from the end. Ie, a +value of -1 means the last paren pair. + +I think it would be good if the regexp matches the whole of the +string, but I haven't actually tried what happens if it doesn't..." + :group 'tramp + :type '(list (regexp :tag "File name regexp") + (integer :tag "Paren pair for method name") + (integer :tag "Paren pair for hops") + (integer :tag "Paren pair to match path"))) + +(defcustom tramp-multi-file-name-hop-structure + (list "/\\([a-z0-9_]+\\):\\([a-z0-9_]+\\)@\\([a-z0-9.-]+\\)" + 1 2 3) + "*Describes the structure of a hop in multi files. +This is a list of four elements (REGEXP METHOD USER HOST). First +element REGEXP is used to match against the hop. Pair number METHOD +matches the method of one hop, pair number USER matches the user of +one hop, pair number HOST matches the host of one hop. + +This regular expression should match exactly all of one hop." + :group 'tramp + :type '(list (regexp :tag "Hop regexp") + (integer :tag "Paren pair for method name") + (integer :tag "Paren pair for user name") + (integer :tag "Paren pair for host name"))) + +(defcustom tramp-make-multi-tramp-file-format + (list "/[%m" "/%m:%u@%h" "]%p") + "*Describes how to construct a `multi' file name. +This is a list of three elements PREFIX, HOP and PATH. + +The first element PREFIX says how to construct the prefix, the second +element HOP specifies what each hop looks like, and the final element +PATH says how to construct the path name. + +In PREFIX, `%%' means `%' and `%m' means the method name. + +In HOP, `%%' means `%' and `%m', `%u', `%h' mean the hop method, hop +user and hop host, respectively. + +In PATH, `%%' means `%' and `%p' means the path name. + +The resulting file name always contains one copy of PREFIX and one +copy of PATH, but there is one copy of HOP for each hop in the file +name. + +Note: the current implementation requires the prefix to contain the +method name, followed by all the hops, and the path name must come +last." + :group 'tramp + :type '(list string string string)) + +(defcustom tramp-terminal-type "dumb" + "*Value of TERM environment variable for logging in to remote host. +Because Tramp wants to parse the output of the remote shell, it is easily +confused by ANSI color escape sequences and suchlike. Often, shell init +files conditionalize this setup based on the TERM environment variable." + :group 'tramp + :type 'string) + +(defcustom tramp-completion-without-shell-p nil + "*If nil, use shell wildcards for completion, else rely on Lisp only. +Using shell wildcards for completions has the advantage that it can be +fast even in large directories, but completion is always +case-sensitive. Relying on Lisp only means that case-insensitive +completion is possible (subject to the variable `completion-ignore-case'), +but it might be slow on large directories." + :group 'tramp + :type 'boolean) + +;;; Internal Variables: + +(defvar tramp-buffer-file-attributes nil + "Holds the `ls -ild' output for the current buffer. +This variable is local to each buffer. It is not used if the remote +machine groks Perl. If it is used, it's used as an emulation for +the visited file modtime.") +(make-variable-buffer-local 'tramp-buffer-file-attributes) + +(defvar tramp-end-of-output "/////" + "String used to recognize end of output.") + +(defvar tramp-connection-function nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-remote-sh nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-rsh-program nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-rsh-args nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-rcp-program nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-rcp-args nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-rcp-keep-date-arg nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-encoding-command nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-decoding-command nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-encoding-function nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-decoding-function nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-telnet-program nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +(defvar tramp-telnet-args nil + "This internal variable holds a parameter for `tramp-methods'. +In the connection buffer, this variable has the value of the like-named +method parameter, as specified in `tramp-methods' (which see).") + +;; CCC `local in each buffer'? +(defvar tramp-ls-command nil + "This command is used to get a long listing with numeric user and group ids. +This variable is automatically made buffer-local to each rsh process buffer +upon opening the connection.") + +(defvar tramp-current-multi-method nil + "Name of `multi' connection method for this *tramp* buffer, or nil if not multi. +This variable is automatically made buffer-local to each rsh process buffer +upon opening the connection.") + +(defvar tramp-current-method nil + "Connection method for this *tramp* buffer. +This variable is automatically made buffer-local to each rsh process buffer +upon opening the connection.") + +(defvar tramp-current-user nil + "Remote login name for this *tramp* buffer. +This variable is automatically made buffer-local to each rsh process buffer +upon opening the connection.") + +(defvar tramp-current-host nil + "Remote host for this *tramp* buffer. +This variable is automatically made buffer-local to each rsh process buffer +upon opening the connection.") + +(defvar tramp-test-groks-nt nil + "Whether the `test' command groks the `-nt' switch. +\(`test A -nt B' tests if file A is newer than file B.) +This variable is automatically made buffer-local to each rsh process buffer +upon opening the connection.") + +(defvar tramp-file-exists-command nil + "Command to use for checking if a file exists. +This variable is automatically made buffer-local to each rsh process buffer +upon opening the connection.") + +;; Perl script to implement `file-attributes' in a Lisp `read'able output. +;; If you are hacking on this, note that you get *no* output unless this +;; spits out a complete line, including the '\n' at the end. +(defconst tramp-perl-file-attributes (concat + "$f = $ARGV[0]; +@s = lstat($f); +if (($s[2] & 0170000) == 0120000) { $l = readlink($f); $l = \"\\\"$l\\\"\"; } +elsif (($s[2] & 0170000) == 040000) { $l = \"t\"; } +else { $l = \"nil\" }; +printf(\"(%s %u %u %u (%u %u) (%u %u) (%u %u) %u %u t (%u . %u) (%u %u))\\n\", +$l, $s[3], $s[4], $s[5], $s[8] >> 16 & 0xffff, $s[8] & 0xffff, +$s[9] >> 16 & 0xffff, $s[9] & 0xffff, $s[10] >> 16 & 0xffff, $s[10] & 0xffff, +$s[7], $s[2], $s[1] >> 16 & 0xffff, $s[1] & 0xffff, $s[0] >> 16 & 0xffff, $s[0] & 0xffff);" + ) + "Perl script to produce output suitable for use with `file-attributes' +on the remote file system.") + +;; Perl script to implement `mime-encode' +(defvar tramp-perl-mime-encode (concat + "sub encode_base64 ($); + my $buf; + while(read(STDIN, $buf, 60*57)) { print encode_base64($buf) } + sub encode_base64 ($) { + my $res = \"\"; + my $eol = \"\n\"; + pos($_[0]) = 0; # ensure start at the beginning + while ($_[0] =~ /(.{1,45})/gs) { + $res .= substr(pack(\"u\", $1), 1); + chop($res); + } + $res =~ tr|` -_|AA-Za-z0-9+/|; # `# help emacs + # fix padding at the end + my $padding = (3 - length($_[0]) % 3) % 3; + $res =~ s/.{$padding}$/\"=\" x $padding/e if $padding; + # break encoded string into lines of no more than 76 characters each + if (length $eol) { + $res =~ s/(.{1,76})/$1$eol/g; + } + $res;}")) + +;; Perl script to implement `mime-decode' +(defvar tramp-perl-mime-decode (concat + "sub decode_base64 ($); + my $buf; + while(read(STDIN, $buf, 60*57)) { print decode_base64($buf) } + sub decode_base64 ($) { + local($^W) = 0; # unpack(\"u\",...) gives bogus warning in 5.00[123] + + my $str = shift; + my $res = \"\"; + + $str =~ tr|A-Za-z0-9+=/||cd; # remove non-base64 chars + if (length($str) % 4) { + warn(\"Length of base64 data not a multiple of 4\") + } + $str =~ s/=+$//; # remove padding + $str =~ tr|A-Za-z0-9+/| -_|; # convert to uuencoded format + while ($str =~ /(.{1,60})/gs) { + my $len = chr(32 + length($1)*3/4); # compute length byte + $res .= unpack(\"u\", $len . $1 ); # uudecode + } + $res;}")) + +; These values conform to `file-attributes' from XEmacs 21.2. +; GNU Emacs and other tools not checked. +(defconst tramp-file-mode-type-map '((0 . "-") ; Normal file (SVID-v2 and XPG2) + (1 . "p") ; fifo + (2 . "c") ; character device + (3 . "m") ; multiplexed character device (v7) + (4 . "d") ; directory + (5 . "?") ; Named special file (XENIX) + (6 . "b") ; block device + (7 . "?") ; multiplexed block device (v7) + (8 . "-") ; regular file + (9 . "n") ; network special file (HP-UX) + (10 . "l") ; symlink + (11 . "?") ; ACL shadow inode (Solaris, not userspace) + (12 . "s") ; socket + (13 . "D") ; door special (Solaris) + (14 . "w")) ; whiteout (BSD) + "A list of file types returned from the `stat' system call. +This is used to map a mode number to a permission string.") + +(defvar tramp-dos-coding-system + (if (and (fboundp 'coding-system-p) + (funcall 'coding-system-p '(dos))) + 'dos + 'undecided-dos) + "Some Emacsen know the `dos' coding system, others need `undecided-dos'.") + + +;; New handlers should be added here. The following operations can be +;; handled using the normal primitives: file-name-as-directory, +;; file-name-directory, file-name-nondirectory, +;; file-name-sans-versions, get-file-buffer. +(defconst tramp-file-name-handler-alist + '( + (load . tramp-handle-load) + (make-symbolic-link . tramp-handle-make-symbolic-link) + (file-name-directory . tramp-handle-file-name-directory) + (file-name-nondirectory . tramp-handle-file-name-nondirectory) + (file-truename . tramp-handle-file-truename) + (file-exists-p . tramp-handle-file-exists-p) + (file-directory-p . tramp-handle-file-directory-p) + (file-executable-p . tramp-handle-file-executable-p) + (file-accessible-directory-p . tramp-handle-file-accessible-directory-p) + (file-readable-p . tramp-handle-file-readable-p) + (file-regular-p . tramp-handle-file-regular-p) + (file-symlink-p . tramp-handle-file-symlink-p) + (file-writable-p . tramp-handle-file-writable-p) + (file-ownership-preserved-p . tramp-handle-file-ownership-preserved-p) + (file-newer-than-file-p . tramp-handle-file-newer-than-file-p) + (file-attributes . tramp-handle-file-attributes) + (file-modes . tramp-handle-file-modes) + (file-directory-files . tramp-handle-file-directory-files) + (directory-files . tramp-handle-directory-files) + (file-name-all-completions . tramp-handle-file-name-all-completions) + (file-name-completion . tramp-handle-file-name-completion) + (add-name-to-file . tramp-handle-add-name-to-file) + (copy-file . tramp-handle-copy-file) + (rename-file . tramp-handle-rename-file) + (set-file-modes . tramp-handle-set-file-modes) + (make-directory . tramp-handle-make-directory) + (delete-directory . tramp-handle-delete-directory) + (delete-file . tramp-handle-delete-file) + (directory-file-name . tramp-handle-directory-file-name) + (shell-command . tramp-handle-shell-command) + (insert-directory . tramp-handle-insert-directory) + (expand-file-name . tramp-handle-expand-file-name) + (file-local-copy . tramp-handle-file-local-copy) + (insert-file-contents . tramp-handle-insert-file-contents) + (write-region . tramp-handle-write-region) + (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory) + (dired-call-process . tramp-handle-dired-call-process) + (dired-recursive-delete-directory + . tramp-handle-dired-recursive-delete-directory) + (set-visited-file-modtime . tramp-handle-set-visited-file-modtime) + (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)) + "Alist of handler functions. +Operations not mentioned here will be handled by the normal Emacs functions.") + +;;; For better error reporting. + +(defun tramp-version (arg) + "Print version number of tramp.el in minibuffer or current buffer." + (interactive "P") + (if arg (insert tramp-version) (message tramp-version))) + +;;; Internal functions which must come first. + +(defsubst tramp-message (level fmt-string &rest args) + "Emit a message depending on verbosity level. +First arg LEVEL says to be quiet if `tramp-verbose' is less than LEVEL. The +message is emitted only if `tramp-verbose' is greater than or equal to LEVEL. +Calls function `message' with FMT-STRING as control string and the remaining +ARGS to actually emit the message (if applicable). + +This function expects to be called from the tramp buffer only!" + (when (<= level tramp-verbose) + (apply #'message (concat "tramp: " fmt-string) args) + (when tramp-debug-buffer + (save-excursion + (set-buffer + (tramp-get-debug-buffer + tramp-current-multi-method tramp-current-method + tramp-current-user tramp-current-host)) + (goto-char (point-max)) + (tramp-insert-with-face + 'italic + (concat "# " (apply #'format fmt-string args) "\n")))))) + +(defun tramp-message-for-buffer + (multi-method method user host level fmt-string &rest args) + "Like `tramp-message' but temporarily switches to the tramp buffer. +First three args METHOD, USER, and HOST identify the tramp buffer to use, +remaining args passed to `tramp-message'." + (save-excursion + (set-buffer (tramp-get-buffer multi-method method user host)) + (apply 'tramp-message level fmt-string args))) + +(defsubst tramp-line-end-position nil + "Return point at end of line. +Calls `line-end-position' or `point-at-eol' if defined, else +own implementation." + (cond + ((fboundp 'line-end-position) (funcall 'line-end-position)) + ((fboundp 'point-at-eol) (funcall 'point-at-eol)) + (t (save-excursion (end-of-line) (point))))) + +;;; File Name Handler Functions: + +;; The following file name handler ops are not implemented (yet?). + +(defun tramp-handle-make-symbolic-link + (filename linkname &optional ok-if-already-exists) + "Like `make-symbolic-link' for tramp files. +This function will raise an error if FILENAME and LINKNAME are not +on the same remote host." + (unless (or (tramp-tramp-file-p filename) + (tramp-tramp-file-p linkname)) + (tramp-run-real-handler 'make-symbolic-link + (list filename linkname ok-if-already-exists))) + (let* ((file (tramp-dissect-file-name filename)) + (link (tramp-dissect-file-name linkname)) + (multi (tramp-file-name-multi-method file)) + (method (tramp-file-name-method file)) + (user (tramp-file-name-user file)) + (host (tramp-file-name-host file)) + (l-multi (tramp-file-name-multi-method link)) + (l-meth (tramp-file-name-method link)) + (l-user (tramp-file-name-user link)) + (l-host (tramp-file-name-host link)) + (ln (tramp-get-remote-ln multi method user host)) + (cwd (file-name-directory (tramp-file-name-path file)))) + (unless ln + (signal 'file-error (list "Making a symbolic link." + "ln(1) does not exist on the remote host."))) + + ;; Check that method, user, host are the same. + (unless (equal host l-host) + (signal 'file-error (list "Can't make symlink across hosts" host l-host))) + (unless (equal user l-user) + (signal 'file-error (list "Can't make symlink for different users" + user l-user))) + (unless (and (equal multi l-multi) + (equal method l-meth)) + (signal 'file-error (list "Method must be the same for making symlinks" + multi l-multi method l-meth))) + + ;; Do the 'confirm if exists' thing. + (when (file-exists-p (tramp-file-name-path link)) + ;; 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? " + (tramp-file-name-path link)))))) + (signal 'file-already-exists (list "File already exists" + (tramp-file-name-path link))))) + + ;; 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 FILENAME belongs to. + (zerop + (tramp-send-command-and-check + multi method user host + (format "cd %s && %s -sf %s %s" + cwd ln + (tramp-file-name-path file) ; target + (tramp-file-name-path link)) ; link name + t)))) + + +(defun tramp-handle-load (file &optional noerror nomessage nosuffix must-suffix) + "Like `load' for tramp files. Not implemented!" + (unless (file-name-absolute-p file) + (error "Tramp cannot `load' files without absolute path name")) + (unless nosuffix + (cond ((file-exists-p (concat file ".elc")) + (setq file (concat file ".elc"))) + ((file-exists-p (concat file ".el")) + (setq file (concat file ".el"))))) + (when must-suffix + ;; The first condition is always true for absolute file names. + ;; Included for safety's sake. + (unless (or (file-name-directory file) + (string-match "\\.elc?\\'" file)) + (error "File `%s' does not include a `.el' or `.elc' suffix" + file))) + (unless noerror + (when (not (file-exists-p file)) + (error "Cannot load nonexistant file `%s'" file))) + (if (not (file-exists-p file)) + nil + (unless nomessage + (message "Loading %s..." file)) + (let ((local-copy (file-local-copy file))) + ;; MUST-SUFFIX doesn't exist on XEmacs, so let it default to nil. + (load local-copy noerror t t) + (delete-file local-copy)) + (unless nomessage + (message "Loading %s...done" file)) + t)) + +;; Path manipulation functions that grok TRAMP paths... +(defun tramp-handle-file-name-directory (file) + "Like `file-name-directory' but aware of TRAMP files." + ;; everything except the last filename thing is the directory + (let* ((v (tramp-dissect-file-name file)) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v))) + (if (or (string= path "") (string= path "/")) + ;; For a filename like "/[foo]", we return "/". The `else' + ;; case would return "/[foo]" unchanged. But if we do that, + ;; then `file-expand-wildcards' ceases to work. It's not + ;; quite clear to me what's the intuition that tells that this + ;; behavior is the right behavior, but oh, well. + "/" + ;; run the command on the path portion only + ;; CCC: This should take into account the remote machine type, no? + ;; --daniel <daniel@danann.net> + (tramp-make-tramp-file-name multi-method method user host + ;; This will not recurse... + (or (file-name-directory path) ""))))) + +(defun tramp-handle-file-name-nondirectory (file) + "Like `file-name-nondirectory' but aware of TRAMP files." + (let ((v (tramp-dissect-file-name file))) + (file-name-nondirectory (tramp-file-name-path v)))) + +(defun tramp-handle-file-truename (filename &optional counter prev-dirs) + "Like `file-truename' for tramp files." + (let* ((v (tramp-dissect-file-name (tramp-handle-expand-file-name filename))) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v)) + (steps (tramp-split-string path "/")) + (pathdir (let ((directory-sep-char ?/)) + (file-name-as-directory path))) + (is-dir (string= path pathdir)) + (thisstep nil) + (numchase 0) + ;; Don't make the following value larger than necessary. + ;; People expect an error message in a timely fashion when + ;; something is wrong; otherwise they might think that Emacs + ;; is hung. Of course, correctness has to come first. + (numchase-limit 20) + (result nil) ;result steps in reverse order + (curstri "") + symlink-target) + (tramp-message-for-buffer + multi-method method user host + 10 "Finding true name for `%s'" filename) + (while (and steps (< numchase numchase-limit)) + (setq thisstep (pop steps)) + (tramp-message-for-buffer + multi-method method user host + 10 "Check %s" + (mapconcat 'identity + (append '("") (reverse result) (list thisstep)) + "/")) + (setq symlink-target + (nth 0 (tramp-handle-file-attributes + (tramp-make-tramp-file-name + multi-method method user host + (mapconcat 'identity + (append '("") (reverse result) (list thisstep)) + "/"))))) + (cond ((string= "." thisstep) + (tramp-message-for-buffer multi-method method user host + 10 "Ignoring step `.'")) + ((string= ".." thisstep) + (tramp-message-for-buffer multi-method method user host + 10 "Processing step `..'") + (pop result)) + ((stringp symlink-target) + ;; It's a symlink, follow it. + (tramp-message-for-buffer + multi-method method user host + 10 "Follow symlink to %s" symlink-target) + (setq numchase (1+ numchase)) + (when (file-name-absolute-p symlink-target) + (setq result nil)) + (setq steps + (append (tramp-split-string symlink-target "/") steps))) + (t + ;; It's a file. + (setq result (cons thisstep result))))) + (when (>= numchase numchase-limit) + (error "Maximum number (%d) of symlinks exceeded" numchase-limit)) + (setq result (reverse result)) + (tramp-message-for-buffer + multi-method method user host + 10 "True name of `%s' is `%s'" + filename (mapconcat 'identity (cons "" result) "/")) + (tramp-make-tramp-file-name + multi-method method user host + (concat (mapconcat 'identity (cons "" result) "/") + (if is-dir "/" ""))))) + +;; Basic functions. + +(defun tramp-handle-file-exists-p (filename) + "Like `file-exists-p' for tramp files." + (let ((v (tramp-dissect-file-name (tramp-handle-expand-file-name filename))) + multi-method method user host path) + (setq multi-method (tramp-file-name-multi-method v)) + (setq method (tramp-file-name-method v)) + (setq user (tramp-file-name-user v)) + (setq host (tramp-file-name-host v)) + (setq path (tramp-file-name-path v)) + (save-excursion + (zerop (tramp-send-command-and-check + multi-method method user host + (format + (tramp-get-file-exists-command multi-method method user host) + (tramp-shell-quote-argument path))))))) + +;; CCC: This should check for an error condition and signal failure +;; when something goes wrong. +;; Daniel Pittman <daniel@danann.net> +(defun tramp-handle-file-attributes (filename &optional nonnumeric) + "Like `file-attributes' for tramp files. +Optional argument NONNUMERIC means return user and group name +rather than as numbers." + (if (tramp-handle-file-exists-p filename) + ;; file exists, find out stuff + (save-excursion + (let* ((v (tramp-dissect-file-name (tramp-handle-expand-file-name filename))) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v))) + (if (tramp-get-remote-perl multi-method method user host) + (tramp-handle-file-attributes-with-perl multi-method method user host path nonnumeric) + (tramp-handle-file-attributes-with-ls multi-method method user host path nonnumeric)))) + nil)) ; no file + + +(defun tramp-handle-file-attributes-with-ls + (multi-method method user host path &optional nonnumeric) + "Implement `file-attributes' for tramp files using the ls(1) command." + (let (symlinkp dirp + res-inode res-filemodes res-numlinks + res-uid res-gid res-size res-symlink-target) + (tramp-send-command + multi-method method user host + (format "%s %s %s" + (tramp-get-ls-command multi-method method user host) + (if nonnumeric "-ild" "-ildn") + (tramp-shell-quote-argument path))) + (tramp-wait-for-output) + ;; parse `ls -l' output ... + ;; ... inode + (setq res-inode + (condition-case err + (read (current-buffer)) + (invalid-read-syntax + (when (and (equal (cadr err) + "Integer constant overflow in reader") + (string-match + "^[0-9]+\\([0-9][0-9][0-9][0-9][0-9]\\)\\'" + (caddr err))) + (let* ((big (read (substring (caddr err) 0 + (match-beginning 1)))) + (small (read (match-string 1 (caddr err)))) + (twiddle (/ small 65536))) + (cons (+ big twiddle) + (- small (* twiddle 65536)))))))) + ;; ... file mode flags + (setq res-filemodes (symbol-name (read (current-buffer)))) + ;; ... number links + (setq res-numlinks (read (current-buffer))) + ;; ... uid and gid + (setq res-uid (read (current-buffer))) + (setq res-gid (read (current-buffer))) + (unless nonnumeric + (unless (numberp res-uid) (setq res-uid -1)) + (unless (numberp res-gid) (setq res-gid -1))) + ;; ... size + (setq res-size (read (current-buffer))) + ;; From the file modes, figure out other stuff. + (setq symlinkp (eq ?l (aref res-filemodes 0))) + (setq dirp (eq ?d (aref res-filemodes 0))) + ;; if symlink, find out file name pointed to + (when symlinkp + (search-forward "-> ") + (setq res-symlink-target + (buffer-substring (point) + (tramp-line-end-position)))) + ;; return data gathered + (list + ;; 0. t for directory, string (name linked to) for symbolic + ;; link, or nil. + (or dirp res-symlink-target nil) + ;; 1. Number of links to file. + res-numlinks + ;; 2. File uid. + res-uid + ;; 3. File gid. + res-gid + ;; 4. Last access time, as a list of two integers. First + ;; integer has high-order 16 bits of time, second has low 16 + ;; bits. + ;; 5. Last modification time, likewise. + ;; 6. Last status change time, likewise. + '(0 0) '(0 0) '(0 0) ;CCC how to find out? + ;; 7. Size in bytes (-1, if number is out of range). + res-size + ;; 8. File modes, as a string of ten letters or dashes as in ls -l. + res-filemodes + ;; 9. t iff file's gid would change if file were deleted and + ;; recreated. + nil ;hm? + ;; 10. inode number. + res-inode + ;; 11. Device number. + -1 ;hm? + ))) + +(defun tramp-handle-file-attributes-with-perl + (multi-method method user host path &optional nonnumeric) + "Implement `file-attributes' for tramp files using a Perl script. + +The Perl command is sent to the remote machine when the connection +is initially created and is kept cached by the remote shell." + (tramp-send-command + multi-method method user host + (format "tramp_file_attributes %s" + (tramp-shell-quote-argument path))) + (tramp-wait-for-output) + (let ((result (read (current-buffer)))) + (setcar (nthcdr 8 result) + (tramp-file-mode-from-int (nth 8 result))) + result)) + +(defun tramp-handle-set-visited-file-modtime (&optional time-list) + "Like `set-visited-file-modtime' for tramp files." + (unless (buffer-file-name) + (error "Can't set-visited-file-modtime: buffer `%s' not visiting a file" + (buffer-name))) + (when time-list + (tramp-run-real-handler 'set-visited-file-modtime (list time-list))) + (let* ((coding-system-used nil) + (f (buffer-file-name)) + (v (tramp-dissect-file-name f)) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v)) + (attr (file-attributes f)) + (modtime (nth 5 attr))) + ;; We use '(0 0) as a don't-know value. See also + ;; `tramp-handle-file-attributes-with-ls'. + (when (boundp 'last-coding-system-used) + (setq coding-system-used last-coding-system-used)) + (if (not (equal modtime '(0 0))) + (tramp-run-real-handler 'set-visited-file-modtime (list modtime)) + (save-excursion + (tramp-send-command + multi-method method user host + (format "%s -ild %s" + (tramp-get-ls-command multi-method method user host) + (tramp-shell-quote-argument path))) + (tramp-wait-for-output) + (setq attr (buffer-substring (point) + (progn (end-of-line) (point))))) + (setq tramp-buffer-file-attributes attr)) + (when (boundp 'last-coding-system-used) + (setq last-coding-system-used coding-system-used)) + nil)) + +;; This function makes the same assumption as +;; `tramp-handle-set-visited-file-modtime'. +(defun tramp-handle-verify-visited-file-modtime (buf) + "Like `verify-visited-file-modtime' for tramp files." + (with-current-buffer buf + (let* ((f (buffer-file-name)) + (v (tramp-dissect-file-name f)) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v)) + (attr (file-attributes f)) + (modtime (nth 5 attr))) + (if attr + (if (not (equal modtime '(0 0))) + ;; Why does `file-attributes' return a list (HIGH LOW), but + ;; `visited-file-modtime' returns a cons (HIGH . LOW)? + (let ((mt (visited-file-modtime))) + (< (abs (tramp-time-diff modtime (list (car mt) (cdr mt)))) 2)) + (save-excursion + (tramp-send-command + multi-method method user host + (format "%s -ild %s" + (tramp-get-ls-command multi-method method user host) + (tramp-shell-quote-argument path))) + (tramp-wait-for-output) + (setq attr (buffer-substring (point) + (progn (end-of-line) (point))))) + (equal tramp-buffer-file-attributes attr)) + ;; If file does not exist, say it is not modified. + nil)))) + +(defadvice clear-visited-file-modtime (after tramp activate) + "Set `tramp-buffer-file-attributes' back to nil. +Tramp uses this variable as an emulation for the actual modtime of the file, +if the remote host can't provide the modtime." + (setq tramp-buffer-file-attributes nil)) + +(defun tramp-handle-set-file-modes (filename mode) + "Like `set-file-modes' for tramp files." + (let ((v (tramp-dissect-file-name filename))) + (save-excursion + (unless (zerop (tramp-send-command-and-check + (tramp-file-name-multi-method v) + (tramp-file-name-method v) + (tramp-file-name-user v) + (tramp-file-name-host v) + (format "chmod %s %s" + (tramp-decimal-to-octal mode) + (tramp-shell-quote-argument + (tramp-file-name-path v))))) + (signal 'file-error + (list "Doing chmod" + ;; FIXME: extract the proper text from chmod's stderr. + "error while changing file's mode" + filename)))))) + +;; Simple functions using the `test' command. + +(defun tramp-handle-file-executable-p (filename) + "Like `file-executable-p' for tramp files." + (zerop (tramp-run-test "-x" filename))) + +(defun tramp-handle-file-readable-p (filename) + "Like `file-readable-p' for tramp files." + (zerop (tramp-run-test "-r" filename))) + +(defun tramp-handle-file-accessible-directory-p (filename) + "Like `file-accessible-directory-p' for tramp files." + (and (zerop (tramp-run-test "-d" filename)) + (zerop (tramp-run-test "-r" filename)) + (zerop (tramp-run-test "-x" filename)))) + +;; When the remote shell is started, it looks for a shell which groks +;; tilde expansion. Here, we assume that all shells which grok tilde +;; expansion will also provide a `test' command which groks `-nt' (for +;; newer than). If this breaks, tell me about it and I'll try to do +;; something smarter about it. +(defun tramp-handle-file-newer-than-file-p (file1 file2) + "Like `file-newer-than-file-p' for tramp files." + (cond ((not (file-exists-p file1)) + nil) + ((not (file-exists-p file2)) + t) + ;; We are sure both files exist at this point. + (t + (save-excursion + (let* ((v1 (tramp-dissect-file-name file1)) + (mm1 (tramp-file-name-multi-method v1)) + (m1 (tramp-file-name-method v1)) + (u1 (tramp-file-name-user v1)) + (h1 (tramp-file-name-host v1)) + (v2 (tramp-dissect-file-name file2)) + (mm2 (tramp-file-name-multi-method v2)) + (m2 (tramp-file-name-method v2)) + (u2 (tramp-file-name-user v2)) + (h2 (tramp-file-name-host v2))) + (unless (and (equal mm1 mm2) + (equal m1 m2) + (equal u1 u2) + (equal h1 h2)) + (signal 'file-error + (list "Files must have same method, user, host" + file1 file2))) + (unless (and (tramp-tramp-file-p file1) + (tramp-tramp-file-p file2)) + (signal 'file-error + (list "Files must be tramp files on same host" + file1 file2))) + (if (tramp-get-test-groks-nt mm1 m1 u1 h1) + (zerop (tramp-run-test2 "test" file1 file2 "-nt")) + (zerop (tramp-run-test2 "tramp_test_nt" file1 file2)))))))) + +;; Functions implemented using the basic functions above. + +(defun tramp-handle-file-modes (filename) + "Like `file-modes' for tramp files." + (when (file-exists-p filename) + (tramp-mode-string-to-int + (nth 8 (tramp-handle-file-attributes filename))))) + +(defun tramp-handle-file-directory-p (filename) + "Like `file-directory-p' for tramp files." + ;; Care must be taken that this function returns `t' for symlinks + ;; pointing to directories. Surely the most obvious implementation + ;; would be `test -d', but that returns false for such symlinks. + ;; CCC: Stefan Monnier says that `test -d' follows symlinks. And + ;; I now think he's right. So we could be using `test -d', couldn't + ;; we? + ;; + ;; Alternatives: `cd %s', `test -d %s' + (save-excursion + (let ((v (tramp-dissect-file-name filename))) + (zerop + (tramp-send-command-and-check + (tramp-file-name-multi-method v) (tramp-file-name-method v) + (tramp-file-name-user v) (tramp-file-name-host v) + (format "test -d %s" + (tramp-shell-quote-argument (tramp-file-name-path v))) + t))))) ;run command in subshell + +(defun tramp-handle-file-regular-p (filename) + "Like `file-regular-p' for tramp files." + (and (tramp-handle-file-exists-p filename) + (eq ?- (aref (nth 8 (tramp-handle-file-attributes filename)) 0)))) + +(defun tramp-handle-file-symlink-p (filename) + "Like `file-symlink-p' for tramp files." + (let ((x (car (tramp-handle-file-attributes filename)))) + (when (stringp x) x))) + +(defun tramp-handle-file-writable-p (filename) + "Like `file-writable-p' for tramp files." + (if (tramp-handle-file-exists-p filename) + ;; Existing files must be writable. + (zerop (tramp-run-test "-w" filename)) + ;; If file doesn't exist, check if directory is writable. + (and (zerop (tramp-run-test "-d" (tramp-handle-file-name-directory filename))) + (zerop (tramp-run-test "-w" (tramp-handle-file-name-directory filename)))))) + +(defun tramp-handle-file-ownership-preserved-p (filename) + "Like `file-ownership-preserved-p' for tramp files." + (or (not (tramp-handle-file-exists-p filename)) + ;; Existing files must be writable. + (zerop (tramp-run-test "-O" filename)))) + +;; Other file name ops. + +;; ;; Matthias Köppe <mkoeppe@mail.math.uni-magdeburg.de> +;; (defun tramp-handle-directory-file-name (directory) +;; "Like `directory-file-name' for tramp files." +;; (if (and (eq (aref directory (- (length directory) 1)) ?/) +;; (not (eq (aref directory (- (length directory) 2)) ?:))) +;; (substring directory 0 (- (length directory) 1)) +;; directory)) + +;; Philippe Troin <phil@fifi.org> +(defun tramp-handle-directory-file-name (directory) + "Like `directory-file-name' for tramp files." + (let ((directory-length-1 (1- (length directory)))) + (save-match-data + (if (and (eq (aref directory directory-length-1) ?/) + (eq (string-match tramp-file-name-regexp directory) 0) + (/= (match-end 0) directory-length-1)) + (substring directory 0 directory-length-1) + directory)))) + +;; Directory listings. + +(defun tramp-handle-directory-files (directory &optional full match nosort) + "Like `directory-files' for tramp files." + (let ((v (tramp-dissect-file-name (tramp-handle-expand-file-name directory))) + multi-method method user host path result x) + (setq multi-method (tramp-file-name-multi-method v)) + (setq method (tramp-file-name-method v)) + (setq user (tramp-file-name-user v)) + (setq host (tramp-file-name-host v)) + (setq path (tramp-file-name-path v)) + (save-excursion + (tramp-barf-unless-okay multi-method method user host + (concat "cd " (tramp-shell-quote-argument path)) + nil + 'file-error + "tramp-handle-directory-files: couldn't `cd %s'" + (tramp-shell-quote-argument path)) + (tramp-send-command + multi-method method user host + (concat (tramp-get-ls-command multi-method method user host) + " -a | cat")) + (tramp-wait-for-output) + (goto-char (point-max)) + (while (zerop (forward-line -1)) + (setq x (buffer-substring (point) + (tramp-line-end-position))) + (when (or (not match) (string-match match x)) + (if full + (push (concat (file-name-as-directory directory) + x) + result) + (push x result)))) + (tramp-send-command multi-method method user host "cd") + (tramp-wait-for-output)) + result)) + +;; This function should return "foo/" for directories and "bar" for +;; files. We use `ls -ad' to get a list of files (including +;; directories), and `find . -type d \! -name . -prune' to get a list +;; of directories. +(defun tramp-handle-file-name-all-completions (filename directory) + "Like `file-name-all-completions' for tramp files." + (unless (save-match-data (string-match "/" filename)) + (let* ((v (tramp-dissect-file-name directory)) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v)) + (nowild tramp-completion-without-shell-p) + result) + (save-excursion + (tramp-barf-unless-okay + multi-method method user host + (format "cd %s" (tramp-shell-quote-argument path)) + nil 'file-error + "tramp-handle-file-name-all-completions: Couldn't `cd %s'" + (tramp-shell-quote-argument path)) + + ;; Get a list of directories and files, including reliably + ;; tagging the directories with a trailing '/'. Because I + ;; rock. --daniel@danann.net + (tramp-send-command + multi-method method user host + (format (concat "%s -a %s 2>/dev/null | while read f; do " + "if test -d \"$f\" 2>/dev/null; " + "then echo \"$f/\"; else echo \"$f\"; fi; done") + (tramp-get-ls-command multi-method method user host) + (if (or nowild (zerop (length filename))) + "" + (format "-d %s*" (tramp-shell-quote-argument filename))))) + + ;; Now grab the output. + (tramp-wait-for-output) + (goto-char (point-max)) + (while (zerop (forward-line -1)) + (push (buffer-substring (point) + (tramp-line-end-position)) + result)) + + (tramp-send-command multi-method method user host "cd") + (tramp-wait-for-output) + + ;; Return the list. + (if nowild + (all-completions filename (mapcar 'list result)) + result))))) + + +;; The following isn't needed for Emacs 20 but for 19.34? +(defun tramp-handle-file-name-completion (filename directory) + "Like `file-name-completion' for tramp files." + (unless (tramp-tramp-file-p directory) + (error + "tramp-handle-file-name-completion invoked on non-tramp directory `%s'" + directory)) + ;(setq directory (tramp-handle-expand-file-name directory)) + (try-completion + filename + (mapcar (lambda (x) (cons x nil)) + (tramp-handle-file-name-all-completions filename directory)))) + +;; cp, mv and ln + +(defun tramp-handle-add-name-to-file + (filename newname &optional ok-if-already-exists) + "Like `add-name-to-file' for tramp files." + (let* ((v1 (when (tramp-tramp-file-p filename) + (tramp-dissect-file-name (tramp-handle-expand-file-name filename)))) + (v2 (when (tramp-tramp-file-p newname) + (tramp-dissect-file-name (tramp-handle-expand-file-name newname)))) + (mmeth1 (when v1 (tramp-file-name-multi-method v1))) + (mmeth2 (when v2 (tramp-file-name-multi-method v2))) + (meth1 (when v1 (tramp-file-name-method v1))) + (meth2 (when v2 (tramp-file-name-method v2))) + (user1 (when v1 (tramp-file-name-user v1))) + (user2 (when v2 (tramp-file-name-user v2))) + (host1 (when v1 (tramp-file-name-host v1))) + (host2 (when v2 (tramp-file-name-host v2))) + (path1 (when v1 (tramp-file-name-path v1))) + (path2 (when v2 (tramp-file-name-path v2))) + (ln (when v1 (tramp-get-remote-ln mmeth1 meth1 user1 host1)))) + (unless (and meth1 meth2 user1 user2 host1 host2 + (equal mmeth1 mmeth2) + (equal meth1 meth2) + (equal user1 user2) + (equal host1 host2)) + (error "add-name-to-file: %s" + "only implemented for same method, same user, same host")) + (when (and (not ok-if-already-exists) + (file-exists-p newname) + (not (numberp ok-if-already-exists)) + (y-or-n-p + (format + "File %s already exists; make it a new name anyway? " + newname))) + (error "add-name-to-file: file %s already exists" newname)) + (tramp-barf-unless-okay + mmeth1 meth1 user1 host1 + (format "%s %s %s" ln (tramp-shell-quote-argument path1) + (tramp-shell-quote-argument path2)) + nil 'file-error + "error with add-name-to-file, see buffer `%s' for details" + (buffer-name)))) + +(defun tramp-handle-copy-file + (filename newname &optional ok-if-already-exists keep-date) + "Like `copy-file' for tramp files." + ;; Check if both files are local -- invoke normal copy-file. + ;; Otherwise, use tramp from local system. + (setq filename (expand-file-name filename)) + (setq newname (expand-file-name newname)) + ;; At least one file a tramp file? + (if (or (tramp-tramp-file-p filename) + (tramp-tramp-file-p newname)) + (tramp-do-copy-or-rename-file + 'copy filename newname ok-if-already-exists keep-date) + (tramp-run-real-handler + 'copy-file + (list filename newname ok-if-already-exists keep-date)))) + +(defun tramp-handle-rename-file + (filename newname &optional ok-if-already-exists) + "Like `rename-file' for tramp files." + ;; Check if both files are local -- invoke normal rename-file. + ;; Otherwise, use tramp from local system. + (setq filename (expand-file-name filename)) + (setq newname (expand-file-name newname)) + ;; At least one file a tramp file? + (if (or (tramp-tramp-file-p filename) + (tramp-tramp-file-p newname)) + (tramp-do-copy-or-rename-file + 'rename filename newname ok-if-already-exists) + (tramp-run-real-handler 'rename-file + (list filename newname ok-if-already-exists)))) + +(defun tramp-do-copy-or-rename-file + (op filename newname &optional ok-if-already-exists keep-date) + "Copy or rename a remote file. +OP must be `copy' or `rename' and indicates the operation to perform. +FILENAME specifies the file to copy or rename, NEWNAME is the name of +the new file (for copy) or the new name of the file (for rename). +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. + +This function is invoked by `tramp-handle-copy-file' and +`tramp-handle-rename-file'. It is an error if OP is neither of `copy' +and `rename'. FILENAME and NEWNAME must be absolute file names." + (unless (memq op '(copy rename)) + (error "Unknown operation `%s', must be `copy' or `rename'" op)) + (unless ok-if-already-exists + (when (file-exists-p newname) + (signal 'file-already-exists + (list newname)))) + (let* ((v1 (when (tramp-tramp-file-p filename) + (tramp-dissect-file-name (tramp-handle-expand-file-name filename)))) + (v2 (when (tramp-tramp-file-p newname) + (tramp-dissect-file-name (tramp-handle-expand-file-name newname)))) + (mmeth1 (when v1 (tramp-file-name-multi-method v1))) + (mmeth2 (when v2 (tramp-file-name-multi-method v2))) + (meth1 (when v1 (tramp-file-name-method v1))) + (meth2 (when v2 (tramp-file-name-method v2))) + (mmeth (tramp-file-name-multi-method (or v1 v2))) + (meth (tramp-file-name-method (or v1 v2))) + (rcp-program (tramp-get-rcp-program mmeth meth)) + (rcp-args (tramp-get-rcp-args mmeth meth)) + (trampbuf (get-buffer-create "*tramp output*"))) + ;; Check if we can use a shortcut. + (if (and meth1 meth2 (equal mmeth1 mmeth2) (equal meth1 meth2) + (equal (tramp-file-name-host v1) + (tramp-file-name-host v2)) + (equal (tramp-file-name-user v1) + (tramp-file-name-user v2))) + ;; Shortcut: if method, host, user are the same for both + ;; files, we invoke `cp' or `mv' on the remote host directly. + (tramp-do-copy-or-rename-file-directly + op + (tramp-file-name-multi-method v1) + (tramp-file-name-method v1) + (tramp-file-name-user v1) + (tramp-file-name-host v1) + (tramp-file-name-path v1) (tramp-file-name-path v2) + keep-date) + ;; New algorithm: copy file first. Then, if operation is + ;; `rename', go back and delete the original file if the copy + ;; was successful. + (if rcp-program + ;; The following code uses a tramp program to copy the file. + (let ((f1 (if (not v1) + filename + (tramp-make-rcp-program-file-name + (tramp-file-name-user v1) + (tramp-file-name-host v1) + (tramp-shell-quote-argument (tramp-file-name-path v1))))) + (f2 (if (not v2) + newname + (tramp-make-rcp-program-file-name + (tramp-file-name-user v2) + (tramp-file-name-host v2) + (tramp-shell-quote-argument (tramp-file-name-path v2))))) + (default-directory + (if (tramp-tramp-file-p default-directory) + (tramp-temporary-file-directory) + default-directory))) + (when keep-date + (add-to-list 'rcp-args (tramp-get-rcp-keep-date-arg mmeth meth))) + (save-excursion (set-buffer trampbuf) (erase-buffer)) + (unless + (equal 0 (apply #'call-process (tramp-get-rcp-program mmeth meth) + nil trampbuf nil (append rcp-args (list f1 f2)))) + (pop-to-buffer trampbuf) + (error (concat "tramp-do-copy-or-rename-file: %s" + " didn't work, see buffer `%s' for details") + (tramp-get-rcp-program mmeth meth) trampbuf))) + ;; The following code uses an inline method for copying. + ;; Let's start with a simple-minded approach: we create a new + ;; buffer, insert the contents of the source file into it, + ;; then write out the buffer. This should work fine, whether + ;; the source or the target files are tramp files. + ;; CCC TODO: error checking + (when keep-date + (tramp-message 1 (concat "Warning: cannot preserve file time stamp" + " with inline copying across machines"))) + (save-excursion + (set-buffer trampbuf) (erase-buffer) + (insert-file-contents-literally filename) + (let ((coding-system-for-write 'no-conversion)) + (write-region (point-min) (point-max) newname)))) + + ;; If the operation was `rename', delete the original file. + (unless (eq op 'copy) + (delete-file filename))))) + +(defun tramp-do-copy-or-rename-file-directly + (op multi-method method user host path1 path2 keep-date) + "Invokes `cp' or `mv' on the remote system. +OP must be one of `copy' or `rename', indicating `cp' or `mv', +respectively. METHOD, USER, and HOST specify the connection. +PATH1 and PATH2 specify the two arguments of `cp' or `mv'. +If KEEP-DATE is non-nil, preserve the time stamp when copying." + ;; CCC: What happens to the timestamp when renaming? + (let ((cmd (cond ((and (eq op 'copy) keep-date) "cp -f -p") + ((eq op 'copy) "cp -f") + ((eq op 'rename) "mv -f") + (t (error + "Unknown operation `%s', must be `copy' or `rename'" + op))))) + (save-excursion + (tramp-barf-unless-okay + multi-method method user host + (format "%s %s %s" + cmd + (tramp-shell-quote-argument path1) + (tramp-shell-quote-argument path2)) + nil 'file-error + "Copying directly failed, see buffer `%s' for details." + (buffer-name))))) + +;; mkdir +(defun tramp-handle-make-directory (dir &optional parents) + "Like `make-directory' for tramp files." + (let ((v (tramp-dissect-file-name (tramp-handle-expand-file-name dir)))) + (tramp-barf-unless-okay + (tramp-file-name-multi-method v) (tramp-file-name-method v) + (tramp-file-name-user v) (tramp-file-name-host v) + (format " %s %s" + (if parents "mkdir -p" "mkdir") + (tramp-shell-quote-argument (tramp-file-name-path v))) + nil 'file-error + "Couldn't make directory %s" dir))) + +;; CCC error checking? +(defun tramp-handle-delete-directory (directory) + "Like `delete-directory' for tramp files." + (let ((v (tramp-dissect-file-name (tramp-handle-expand-file-name directory)))) + (save-excursion + (tramp-send-command + (tramp-file-name-multi-method v) (tramp-file-name-method v) + (tramp-file-name-user v) (tramp-file-name-host v) + (format "rmdir %s ; echo ok" + (tramp-shell-quote-argument (tramp-file-name-path v)))) + (tramp-wait-for-output)))) + +(defun tramp-handle-delete-file (filename) + "Like `delete-file' for tramp files." + (let ((v (tramp-dissect-file-name (tramp-handle-expand-file-name filename)))) + (save-excursion + (unless (zerop (tramp-send-command-and-check + (tramp-file-name-multi-method v) + (tramp-file-name-method v) + (tramp-file-name-user v) + (tramp-file-name-host v) + (format "rm -f %s" + (tramp-shell-quote-argument + (tramp-file-name-path v))))) + (signal 'file-error "Couldn't delete Tramp file"))))) + +;; Dired. + +;; CCC: This does not seem to be enough. Something dies when +;; we try and delete two directories under TRAMP :/ +(defun tramp-handle-dired-recursive-delete-directory (filename) + "Recursively delete the directory given. +This is like `dired-recursive-delete-directory' for tramp files." + (let* ((v (tramp-dissect-file-name (tramp-handle-expand-file-name filename))) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v))) + ;; run a shell command 'rm -r <path>' + ;; Code shamelessly stolen for the dired implementation and, um, hacked :) + (or (tramp-handle-file-exists-p filename) + (signal + 'file-error + (list "Removing old file name" "no such directory" filename))) + ;; Which is better, -r or -R? (-r works for me <daniel@danann.net>) + (tramp-send-command multi-method method user host + (format "rm -r %s" (tramp-shell-quote-argument path))) + ;; Wait for the remote system to return to us... + ;; This might take a while, allow it plenty of time. + (tramp-wait-for-output 120) + ;; Make sure that it worked... + (and (tramp-handle-file-exists-p filename) + (error "Failed to recusively delete %s" filename)))) + + +(defun tramp-handle-dired-call-process (program discard &rest arguments) + "Like `dired-call-process' for tramp files." + (let ((v (tramp-dissect-file-name + (tramp-handle-expand-file-name default-directory))) + multi-method method user host path) + (setq multi-method (tramp-file-name-multi-method v)) + (setq method (tramp-file-name-method v)) + (setq user (tramp-file-name-user v)) + (setq host (tramp-file-name-host v)) + (setq path (tramp-file-name-path v)) + (save-excursion + (tramp-barf-unless-okay + multi-method method user host + (format "cd %s" (tramp-shell-quote-argument path)) + nil 'file-error + "tramp-handle-dired-call-process: Couldn't `cd %s'" + (tramp-shell-quote-argument path)) + (tramp-send-command + multi-method method user host + (mapconcat #'tramp-shell-quote-argument (cons program arguments) " ")) + (tramp-wait-for-output)) + (unless discard + (insert-buffer (tramp-get-buffer multi-method method user host))) + (save-excursion + (prog1 + (tramp-send-command-and-check multi-method method user host nil) + (tramp-send-command multi-method method user host "cd") + (tramp-wait-for-output))))) + +;; Pacify byte-compiler. The function is needed on XEmacs only. I'm +;; not sure at all that this is the right way to do it, but let's hope +;; it works for now, and wait for a guru to point out the Right Way to +;; achieve this. +;;(eval-when-compile +;; (unless (fboundp 'dired-insert-set-properties) +;; (fset 'dired-insert-set-properties 'ignore))) +;; Gerd suggests this: +(eval-when-compile (require 'dired)) +;; Note that dired is required at run-time, too, when it is needed. +;; It is only needed on XEmacs for the function +;; `dired-insert-set-properties'. + +(defun tramp-handle-insert-directory + (filename switches &optional wildcard full-directory-p) + "Like `insert-directory' for tramp files." + (let ((v (tramp-dissect-file-name (tramp-handle-expand-file-name filename))) + multi-method method user host path) + (setq multi-method (tramp-file-name-multi-method v)) + (setq method (tramp-file-name-method v)) + (setq user (tramp-file-name-user v)) + (setq host (tramp-file-name-host v)) + (setq path (tramp-file-name-path v)) + (tramp-message-for-buffer + multi-method method user host 10 + "Inserting directory `ls %s %s', wildcard %s, fulldir %s" + switches filename (if wildcard "yes" "no") + (if full-directory-p "yes" "no")) + (when wildcard + (setq wildcard (file-name-nondirectory path)) + (setq path (file-name-directory path))) + (when (listp switches) + (setq switches (mapconcat 'identity switches " "))) + (unless full-directory-p + (setq switches (concat "-d " switches))) + (when wildcard + (setq switches (concat switches " " wildcard))) + (save-excursion + ;; If `full-directory-p', we just say `ls -l FILENAME'. + ;; Else we chdir to the parent directory, then say `ls -ld BASENAME'. + (if full-directory-p + (tramp-send-command + multi-method method user host + (format "%s %s %s" + (tramp-get-ls-command multi-method method user host) + switches + (if wildcard + path + (tramp-shell-quote-argument (concat path "."))))) + (tramp-barf-unless-okay + multi-method method user host + (format "cd %s" (tramp-shell-quote-argument + (file-name-directory path))) + nil 'file-error + "Couldn't `cd %s'" + (tramp-shell-quote-argument (file-name-directory path))) + (tramp-send-command + multi-method method user host + (format "%s %s %s" + (tramp-get-ls-command multi-method method user host) + switches + (if full-directory-p + ;; Add "/." to make sure we got complete dir + ;; listing for symlinks, too. + (concat (file-name-as-directory + (file-name-nondirectory path)) ".") + (file-name-nondirectory path))))) + (sit-for 1) ;needed for rsh but not ssh? + (tramp-wait-for-output)) + (insert-buffer (tramp-get-buffer multi-method method user host)) + ;; On XEmacs, we want to call (exchange-point-and-mark t), but + ;; that doesn't exist on Emacs, so we use this workaround instead. + ;; Since zmacs-region-stays doesn't exist in Emacs, this ought to + ;; be safe. Thanks to Daniel Pittman <daniel@danann.net>. + (let ((zmacs-region-stays t)) + (exchange-point-and-mark)) + (save-excursion + (tramp-send-command multi-method method user host "cd") + (tramp-wait-for-output)) + ;; Another XEmacs specialty follows. What's the right way to do + ;; it? + (when (and (featurep 'xemacs) + (eq major-mode 'dired-mode)) + (save-excursion + (require 'dired) + (dired-insert-set-properties (point) (mark t)))))) + +;; Continuation of kluge to pacify byte-compiler. +;;(eval-when-compile +;; (when (eq (symbol-function 'dired-insert-set-properties) 'ignore) +;; (fmakunbound 'dired-insert-set-properties))) + +;; CCC is this the right thing to do? +(defun tramp-handle-unhandled-file-name-directory (filename) + "Like `unhandled-file-name-directory' for tramp files." + (expand-file-name "~/")) + +;; Canonicalization of file names. + +(defun tramp-drop-volume-letter (name) + "Cut off unnecessary drive letter from file NAME. +The function `tramp-handle-expand-file-name' calls `expand-file-name' +locally on a remote file name. When the local system is a W32 system +but the remote system is Unix, this introduces a superfluous drive +letter into the file name. This function removes it. + +Doesn't do anything if the NAME does not start with a drive letter." + (if (and (> (length name) 1) + (char-equal (aref name 1) ?:) + (let ((c1 (aref name 0))) + (or (and (>= c1 ?A) (<= c1 ?Z)) + (and (>= c1 ?a) (<= c1 ?z))))) + (substring name 2) + name)) + +(defun tramp-handle-expand-file-name (name &optional dir) + "Like `expand-file-name' for tramp files." + ;; If DIR is not given, use DEFAULT-DIRECTORY or "/". + (setq dir (or dir default-directory "/")) + ;; Unless NAME is absolute, concat DIR and NAME. + (unless (file-name-absolute-p name) + (setq name (concat (file-name-as-directory dir) name))) + ;; If NAME is not a tramp file, run the real handler + (if (not (tramp-tramp-file-p name)) + (tramp-run-real-handler 'expand-file-name + (list name nil)) + ;; Dissect NAME. + (let* ((v (tramp-dissect-file-name name)) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v))) + (unless (file-name-absolute-p path) + (setq path (concat "~/" path))) + (save-excursion + ;; Tilde expansion if necessary. This needs a shell which + ;; groks tilde expansion! The function `tramp-find-shell' is + ;; 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 "\\`\\(~[^/]*\\)\\(.*\\)\\'" path) + (let ((uname (match-string 1 path)) + (fname (match-string 2 path))) + ;; CCC fanatic error checking? + (set-buffer (tramp-get-buffer multi-method method user host)) + (erase-buffer) + (tramp-send-command + multi-method method user host + (format "cd %s; pwd" uname) + t) + (tramp-wait-for-output) + (goto-char (point-min)) + (setq uname (buffer-substring (point) (tramp-line-end-position))) + (setq path (concat uname fname)) + (erase-buffer))) + ;; No tilde characters in file name, do normal + ;; expand-file-name (this does "/./" and "/../"). We bind + ;; directory-sep-char here for XEmacs on Windows, which would + ;; otherwise use backslash. + (let ((directory-sep-char ?/)) + (tramp-make-tramp-file-name + multi-method method user host + (tramp-drop-volume-letter + (tramp-run-real-handler 'expand-file-name (list path))))))))) + +;; Remote commands. + +(defun tramp-handle-shell-command (command &optional output-buffer error-buffer) + "Like `shell-command' for tramp files. +This will break if COMMAND prints a newline, followed by the value of +`tramp-end-of-output', followed by another newline." + (if (tramp-tramp-file-p default-directory) + (let* ((v (tramp-dissect-file-name + (tramp-handle-expand-file-name default-directory))) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v)) + status) + (when (string-match "&[ \t]*\\'" command) + (error "Tramp doesn't grok asynchronous shell commands, yet")) + (when error-buffer + (error "Tramp doesn't grok optional third arg ERROR-BUFFER, yet")) + (save-excursion + (tramp-barf-unless-okay + multi-method method user host + (format "cd %s" (tramp-shell-quote-argument path)) + nil 'file-error + "tramp-handle-shell-command: Couldn't `cd %s'" + (tramp-shell-quote-argument path)) + (tramp-send-command multi-method method user host + (concat command "; tramp_old_status=$?")) + ;; This will break if the shell command prints "/////" + ;; somewhere. Let's just hope for the best... + (tramp-wait-for-output)) + (unless output-buffer + (setq output-buffer (get-buffer-create "*Shell Command Output*")) + (set-buffer output-buffer) + (erase-buffer)) + (unless (bufferp output-buffer) + (setq output-buffer (current-buffer))) + (set-buffer output-buffer) + (insert-buffer (tramp-get-buffer multi-method method user host)) + (save-excursion + (tramp-send-command multi-method method user host "cd") + (tramp-wait-for-output) + (tramp-send-command + multi-method method user host + "tramp_set_exit_status $tramp_old_status; echo tramp_exit_status $?") + (tramp-wait-for-output) + (goto-char (point-max)) + (unless (search-backward "tramp_exit_status " nil t) + (error "Couldn't find exit status of `%s'" command)) + (skip-chars-forward "^ ") + (setq status (read (current-buffer)))) + (unless (zerop (buffer-size)) + (pop-to-buffer output-buffer)) + status) + ;; The following is only executed if something strange was + ;; happening. Emit a helpful message and do it anyway. + (message "tramp-handle-shell-command called with non-tramp directory: `%s'" + default-directory) + (tramp-run-real-handler 'shell-command + (list command output-buffer error-buffer)))) + +;; File Editing. + +(defsubst tramp-make-temp-file () + (funcall (if (fboundp 'make-temp-file) 'make-temp-file 'make-temp-name) + (expand-file-name tramp-temp-name-prefix + (tramp-temporary-file-directory)))) + +(defun tramp-handle-file-local-copy (filename) + "Like `file-local-copy' for tramp files." + (let* ((v (tramp-dissect-file-name (tramp-handle-expand-file-name filename))) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v)) + (trampbuf (get-buffer-create "*tramp output*")) + tmpfil) + (unless (file-exists-p filename) + (error "Cannot make local copy of non-existing file `%s'" + filename)) + (setq tmpfil (tramp-make-temp-file)) + (cond ((tramp-get-rcp-program multi-method method) + ;; Use tramp-like program for file transfer. + (tramp-message-for-buffer + multi-method method user host + 5 "Fetching %s to tmp file %s..." filename tmpfil) + (save-excursion (set-buffer trampbuf) (erase-buffer)) + (unless (equal 0 + (apply #'call-process + (tramp-get-rcp-program multi-method method) + nil trampbuf nil + (append (tramp-get-rcp-args multi-method method) + (list + (tramp-make-rcp-program-file-name + user host + (tramp-shell-quote-argument path)) + tmpfil)))) + (pop-to-buffer trampbuf) + (error (concat "tramp-handle-file-local-copy: `%s' didn't work, " + "see buffer `%s' for details") + (tramp-get-rcp-program multi-method method) trampbuf)) + (tramp-message-for-buffer + multi-method method user host + 5 "Fetching %s to tmp file %s...done" filename tmpfil)) + ((and (tramp-get-encoding-command multi-method method) + (tramp-get-decoding-command multi-method method)) + ;; Use inline encoding for file transfer. + (save-excursion + ;; Following line for setting tramp-current-method, + ;; tramp-current-user, tramp-current-host. + (set-buffer (tramp-get-buffer multi-method method user host)) + (tramp-message 5 "Encoding remote file %s..." filename) + (tramp-barf-unless-okay + multi-method method user host + (concat (tramp-get-encoding-command multi-method method) + " < " (tramp-shell-quote-argument path)) + nil 'file-error + "Encoding remote file failed, see buffer `%s' for details" + (tramp-get-buffer multi-method method user host)) + ;; Remove trailing status code + (goto-char (point-max)) + (delete-region (point) (progn (forward-line -1) (point))) + + (tramp-message 5 "Decoding remote file %s..." filename) + (if (and (tramp-get-decoding-function multi-method method) + (fboundp (tramp-get-decoding-function multi-method method))) + ;; If tramp-decoding-function is defined for this + ;; method, we call it. + (let ((tmpbuf (get-buffer-create " *tramp tmp*"))) + (set-buffer tmpbuf) + (erase-buffer) + (insert-buffer (tramp-get-buffer multi-method method + user host)) + (tramp-message-for-buffer + multi-method method user host + 6 "Decoding remote file %s with function %s..." + filename + (tramp-get-decoding-function multi-method method)) + (set-buffer tmpbuf) + (let ((coding-system-for-write 'no-conversion)) + (funcall (tramp-get-decoding-function multi-method method) + (point-min) + (point-max)) + (write-region (point-min) (point-max) tmpfil)) + (kill-buffer tmpbuf)) + ;; If tramp-decoding-function is not defined for this + ;; method, we invoke tramp-decoding-command instead. + (let ((tmpfil2 (tramp-make-temp-file))) + (write-region (point-min) (point-max) tmpfil2) + (tramp-message + 6 "Decoding remote file %s with command %s..." + filename + (tramp-get-decoding-command multi-method method)) + (call-process + tramp-sh-program + tmpfil2 ;input + nil ;output + nil ;display + "-c" (concat (tramp-get-decoding-command multi-method method) + " > " tmpfil)) + (delete-file tmpfil2))) + (tramp-message-for-buffer + multi-method method user host + 5 "Decoding remote file %s...done" filename))) + + (t (error "Wrong method specification for `%s'" method))) + tmpfil)) + + +(defun tramp-handle-insert-file-contents + (filename &optional visit beg end replace) + "Like `insert-file-contents' for tramp files." + (barf-if-buffer-read-only) + (setq filename (expand-file-name filename)) + (let* ((v (tramp-dissect-file-name (tramp-handle-expand-file-name filename))) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v))) + (if (not (tramp-handle-file-exists-p filename)) + (progn + (when visit + (setq buffer-file-name filename) + (set-visited-file-modtime) + (set-buffer-modified-p nil)) + (signal 'file-error + (format "File `%s' not found on remote host" filename)) + (list (tramp-handle-expand-file-name filename) 0)) + (let ((local-copy (tramp-handle-file-local-copy filename)) + (coding-system-used nil) + (result nil)) + (when visit + (setq buffer-file-name filename) + (set-visited-file-modtime) + (set-buffer-modified-p nil)) + (tramp-message-for-buffer + multi-method method user host + 9 "Inserting local temp file `%s'..." local-copy) + (setq result + (tramp-run-real-handler 'insert-file-contents + (list local-copy nil beg end replace))) + ;; Now `last-coding-system-used' has right value. Remember it. + (when (boundp 'last-coding-system-used) + (setq coding-system-used last-coding-system-used)) + (tramp-message 9 "Inserting local temp file `%s'...done" local-copy) + (delete-file local-copy) + (when (boundp 'last-coding-system-used) + (setq last-coding-system-used coding-system-used)) + (list (expand-file-name filename) + (second result)))))) + +;; CCC grok APPEND, LOCKNAME, CONFIRM +(defun tramp-handle-write-region + (start end filename &optional append visit lockname confirm) + "Like `write-region' for tramp files." + (unless (eq append nil) + (error "Cannot append to file using tramp (`%s')" filename)) + (setq filename (expand-file-name filename)) +;; Following part commented out because we don't know what to do about +;; file locking, and it does not appear to be a problem to ignore it. +;; Ange-ftp ignores it, too. +; (when (and lockname (stringp lockname)) +; (setq lockname (expand-file-name lockname))) +; (unless (or (eq lockname nil) +; (string= lockname filename)) +; (error "tramp-handle-write-region: LOCKNAME must be nil or equal FILENAME")) + ;; XEmacs takes a coding system as the sevent argument, not `confirm' + (when (and (not (featurep 'xemacs)) + confirm (file-exists-p filename)) + (unless (y-or-n-p (format "File %s exists; overwrite anyway? " + filename)) + (error "File not overwritten"))) + (let* ((curbuf (current-buffer)) + (v (tramp-dissect-file-name filename)) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v)) + (rcp-program (tramp-get-rcp-program multi-method method)) + (rcp-args (tramp-get-rcp-args multi-method method)) + (encoding-command (tramp-get-encoding-command multi-method method)) + (encoding-function (tramp-get-encoding-function multi-method method)) + (decoding-command (tramp-get-decoding-command multi-method method)) + (trampbuf (get-buffer-create "*tramp output*")) + ;; We use this to save the value of `last-coding-system-used' + ;; after writing the tmp file. At the end of the function, + ;; we set `last-coding-system-used' to this saved value. + ;; This way, any intermediary coding systems used while + ;; talking to the remote shell or suchlike won't hose this + ;; variable. This approach was snarfed from ange-ftp.el. + coding-system-used + tmpfil) + ;; Write region into a tmp file. This isn't really needed if we + ;; use an encoding function, but currently we use it always + ;; because this makes the logic simpler. + (setq tmpfil (tramp-make-temp-file)) + ;; We say `no-message' here because we don't want the visited file + ;; modtime data to be clobbered from the temp file. We call + ;; `set-visited-file-modtime' ourselves later on. + (tramp-run-real-handler + 'write-region + (if confirm ; don't pass this arg unless defined for backward compat. + (list start end tmpfil append 'no-message lockname confirm) + (list start end tmpfil append 'no-message lockname))) + ;; Now, `last-coding-system-used' has the right value. Remember it. + (when (boundp 'last-coding-system-used) + (setq coding-system-used last-coding-system-used)) + ;; This is a bit lengthy due to the different methods possible for + ;; file transfer. First, we check whether the method uses an rcp + ;; program. If so, we call it. Otherwise, both encoding and + ;; decoding command must be specified. However, if the method + ;; _also_ specifies an encoding function, then that is used for + ;; encoding the contents of the tmp file. + (cond (rcp-program + ;; use rcp-like program for file transfer + (let ((argl (append rcp-args + (list + tmpfil + (tramp-make-rcp-program-file-name + user host + (tramp-shell-quote-argument path)))))) + (tramp-message-for-buffer + multi-method method user host + 6 "Writing tmp file using `%s'..." rcp-program) + (save-excursion (set-buffer trampbuf) (erase-buffer)) + (when tramp-debug-buffer + (save-excursion + (set-buffer (tramp-get-debug-buffer multi-method + method user host)) + (goto-char (point-max)) + (tramp-insert-with-face + 'bold (format "$ %s %s\n" rcp-program + (mapconcat 'identity argl " "))))) + (unless (equal 0 + (apply #'call-process + rcp-program nil trampbuf nil argl)) + (pop-to-buffer trampbuf) + (error "Cannot write region to file `%s', command `%s' failed" + filename rcp-program)) + (tramp-message-for-buffer multi-method method user host + 6 "Transferring file using `%s'...done" + rcp-program))) + ((and encoding-command decoding-command) + ;; Use inline file transfer + (let ((tmpbuf (get-buffer-create " *tramp file transfer*"))) + (save-excursion + ;; Encode tmpfil into tmpbuf + (tramp-message-for-buffer multi-method method user host + 5 "Encoding region...") + (set-buffer tmpbuf) + (erase-buffer) + ;; Use encoding function or command. + (if (and encoding-function + (fboundp encoding-function)) + (progn + (tramp-message-for-buffer + multi-method method user host + 6 "Encoding region using function...") + (insert-file-contents-literally tmpfil) + ;; CCC. The following `let' is a workaround for + ;; the base64.el that comes with pgnus-0.84. If + ;; both of the following conditions are + ;; satisfied, it tries to write to a local file + ;; in default-directory, but at this point, + ;; default-directory is remote. + ;; (CALL-PROCESS-REGION can't write to remote + ;; files, it seems.) The file in question is a + ;; tmp file anyway. + (let ((default-directory (tramp-temporary-file-directory))) + (funcall encoding-function (point-min) (point-max))) + (goto-char (point-max)) + (unless (bolp) + (newline))) + (tramp-message-for-buffer multi-method method user host + 6 "Encoding region using command...") + (unless (equal 0 + (call-process + tramp-sh-program + tmpfil ;input = local tmp file + t ;output is current buffer + nil ;don't redisplay + "-c" + encoding-command)) + (pop-to-buffer trampbuf) + (error (concat "Cannot write to `%s', local encoding" + " command `%s' failed") + filename encoding-command))) + ;; Send tmpbuf into remote decoding command which + ;; writes to remote file. Because this happens on the + ;; remote host, we cannot use the function. + (tramp-message-for-buffer + multi-method method user host + 5 "Decoding region into remote file %s..." filename) + (tramp-send-command + multi-method method user host + (format "%s >%s <<'EOF'" + decoding-command + (tramp-shell-quote-argument path))) + (set-buffer tmpbuf) + (tramp-message-for-buffer + multi-method method user host + 6 "Sending data to remote host...") + (tramp-send-region multi-method method user host + (point-min) (point-max)) + ;; wait for remote decoding to complete + (tramp-message-for-buffer + multi-method method user host 6 "Sending end of data token...") + (tramp-send-command + multi-method method user host "EOF") + (tramp-message-for-buffer + multi-method method user host 6 + "Waiting for remote host to process data...") + (set-buffer (tramp-get-buffer multi-method method user host)) + (tramp-wait-for-output) + (tramp-barf-unless-okay + multi-method method user host nil nil 'file-error + (concat "Couldn't write region to `%s'," + " decode using `%s' failed") + filename decoding-command) + (tramp-message 5 "Decoding region into remote file %s...done" + filename) + (kill-buffer tmpbuf)))) + (t + (error + (concat "Method `%s' should specify both encoding and " + "decoding command or an rcp program") + method))) + (delete-file tmpfil) + (unless (equal curbuf (current-buffer)) + (error "Buffer has changed from `%s' to `%s'" + curbuf (current-buffer))) + (when (eq visit t) + (set-visited-file-modtime)) + ;; Make `last-coding-system-used' have the right value. + (when (boundp 'last-coding-system-used) + (setq last-coding-system-used coding-system-used)) + (when (or (eq visit t) + (eq visit nil) + (stringp visit)) + (message "Wrote %s" filename)))) + +;; Call down to the real handler. +;; Because EFS does not play nicely with TRAMP (both systems match an +;; TRAMP path) it is needed to disable efs as well as tramp for the +;; operation. +;; +;; Other than that, this is the canon file-handler code that the doco +;; says should be used here. Which is nice. +;; +;; Under XEmacs current, EFS also hooks in as +;; efs-sifn-handler-function to handle any path with environment +;; variables. This has two implications: +;; 1) That EFS may not be completely dead (yet) for TRAMP paths +;; 2) That TRAMP might want to do the same thing. +;; Details as they come in. +;; +;; Daniel Pittman <daniel@danann.net> + +;; (defun tramp-run-real-handler (operation args) +;; "Invoke normal file name handler for OPERATION. +;; This inhibits EFS and Ange-FTP, too, because they conflict with tramp. +;; First arg specifies the OPERATION, remaining ARGS are passed to the +;; OPERATION." +;; (let ((inhibit-file-name-handlers +;; (list 'tramp-file-name-handler +;; 'efs-file-handler-function +;; 'ange-ftp-hook-function +;; (and (eq inhibit-file-name-operation operation) +;; inhibit-file-name-handlers))) +;; (inhibit-file-name-operation operation)) +;; (apply operation args))) + +(defun tramp-run-real-handler (operation args) + "Invoke normal file name handler for OPERATION. +First arg specifies the OPERATION, remaining ARGS are passed to the +OPERATION." + (let ((inhibit-file-name-handlers + (list 'tramp-file-name-handler + (and (eq inhibit-file-name-operation operation) + inhibit-file-name-handlers))) + (inhibit-file-name-operation operation)) + (apply operation args))) + + +;; Main function. +;;;###autoload +(defun tramp-file-name-handler (operation &rest args) + "Invoke tramp file name handler. +Falls back to normal file name handler if no tramp file name handler exists." + (let ((fn (assoc operation tramp-file-name-handler-alist))) + ;(message "Handling %s using %s" operation fn) + (if fn + (save-match-data + (apply (cdr fn) args)) + (tramp-run-real-handler operation args)))) + +;; Register in file name handler alist +;;;###autoload +(add-to-list 'file-name-handler-alist + (cons tramp-file-name-regexp 'tramp-file-name-handler)) + +;; If jka-compr is already loaded, move it to the front of +;; `file-name-handler-alist'. On Emacs 21.3 or so this will not be +;; necessary anymore. +(let ((jka (rassoc 'jka-compr-handler file-name-handler-alist))) + (when jka + (setq file-name-handler-alist + (cons jka (delete jka file-name-handler-alist))))) + +;;; Interactions with other packages: + +;; -- complete.el -- + +;; This function contributed by Ed Sabol +(defun tramp-handle-expand-many-files (name) + "Like `PC-expand-many-files' for tramp files." + (save-match-data + (if (or (string-match "\\*" name) + (string-match "\\?" name) + (string-match "\\[.*\\]" name)) + (save-excursion + ;; Dissect NAME. + (let* ((v (tramp-dissect-file-name name)) + (multi-method (tramp-file-name-multi-method v)) + (method (tramp-file-name-method v)) + (user (tramp-file-name-user v)) + (host (tramp-file-name-host v)) + (path (tramp-file-name-path v)) + bufstr) + ;; CCC: To do it right, we should quote certain characters + ;; in the file name, but since the echo command is going to + ;; break anyway when there are spaces in the file names, we + ;; don't bother. + ;;-(let ((comint-file-name-quote-list + ;;- (set-difference tramp-file-name-quote-list + ;;- '(?\* ?\? ?[ ?])))) + ;;- (tramp-send-command + ;;- multi-method method user host + ;;- (format "echo %s" (comint-quote-filename path))) + ;;- (tramp-wait-for-output)) + (tramp-send-command multi-method method user host + (format "echo %s" path)) + (tramp-wait-for-output) + (setq bufstr (buffer-substring (point-min) + (tramp-line-end-position))) + (goto-char (point-min)) + (if (string-equal path bufstr) + nil + (insert "(\"") + (while (search-forward " " nil t) + (delete-backward-char 1) + (insert "\" \"")) + (goto-char (point-max)) + (delete-backward-char 1) + (insert "\")") + (goto-char (point-min)) + (mapcar + (function (lambda (x) + (tramp-make-tramp-file-name multi-method method + user host x))) + (read (current-buffer)))))) + (list (tramp-handle-expand-file-name name))))) + +;; Check for complete.el and override PC-expand-many-files if appropriate. +(eval-when-compile + (defun tramp-save-PC-expand-many-files (name))); avoid compiler warning + +(defun tramp-setup-complete () + (fset 'tramp-save-PC-expand-many-files + (symbol-function 'PC-expand-many-files)) + (defun PC-expand-many-files (name) + (if (tramp-tramp-file-p name) + (tramp-handle-expand-many-files name) + (tramp-save-PC-expand-many-files name)))) + +;; Why isn't eval-after-load sufficient? +(if (fboundp 'PC-expand-many-files) + (tramp-setup-complete) + (eval-after-load "complete" '(tramp-setup-complete))) + + + + +;;; Internal Functions: + +(defun tramp-set-auto-save () + (when (and (buffer-file-name) + (tramp-tramp-file-p (buffer-file-name)) + auto-save-default) + (auto-save-mode 1))) +(add-hook 'find-file-hooks 'tramp-set-auto-save t) + +(defun tramp-run-test (switch filename) + "Run `test' on the remote system, given a SWITCH and a FILENAME. +Returns the exit code of the `test' program." + (let ((v (tramp-dissect-file-name filename))) + (save-excursion + (tramp-send-command-and-check + (tramp-file-name-multi-method v) (tramp-file-name-method v) + (tramp-file-name-user v) (tramp-file-name-host v) + (format "test %s %s" switch + (tramp-shell-quote-argument (tramp-file-name-path v))))))) + +(defun tramp-run-test2 (program file1 file2 &optional switch) + "Run `test'-like PROGRAM on the remote system, given FILE1, FILE2. +The optional SWITCH is inserted between the two files. +Returns the exit code of the `test' PROGRAM. Barfs if the methods, +hosts, or files, disagree." + (let* ((v1 (tramp-dissect-file-name file1)) + (v2 (tramp-dissect-file-name file2)) + (mmethod1 (tramp-file-name-multi-method v1)) + (mmethod2 (tramp-file-name-multi-method v2)) + (method1 (tramp-file-name-method v1)) + (method2 (tramp-file-name-method v2)) + (user1 (tramp-file-name-user v1)) + (user2 (tramp-file-name-user v2)) + (host1 (tramp-file-name-host v1)) + (host2 (tramp-file-name-host v2)) + (path1 (tramp-file-name-path v1)) + (path2 (tramp-file-name-path v2))) + (unless (and method1 method2 host1 host2 + (equal mmethod1 mmethod2) + (equal method1 method2) + (equal user1 user2) + (equal host1 host2)) + (error "tramp-run-test2: %s" + "only implemented for same method, same user, same host")) + (save-excursion + (tramp-send-command-and-check + mmethod1 method1 user1 host1 + (format "%s %s %s %s" + program + (tramp-shell-quote-argument path1) + (or switch "") + (tramp-shell-quote-argument path2)))))) + +(defun tramp-buffer-name (multi-method method user host) + "A name for the connection buffer for USER at HOST using METHOD." + (cond (multi-method + (tramp-buffer-name-multi-method "tramp" multi-method method user host)) + (user + (format "*tramp/%s %s@%s*" method user host)) + (t + (format "*tramp/%s %s*" method host)))) + +(defun tramp-buffer-name-multi-method (prefix multi-method method user host) + "A name for the multi method connection buffer. +MULTI-METHOD gives the multi method, METHOD the array of methods, +USER the array of user names, HOST the array of host names." + (unless (and (= (length method) (length user)) + (= (length method) (length host))) + (error "Syntax error in multi method (implementation error)")) + (let ((len (length method)) + (i 0) + string-list) + (while (< i len) + (setq string-list + (cons (if (aref user i) + (format "%s#%s@%s:" (aref method i) + (aref user i) (aref host i)) + (format "%s@%s:" (aref method i) (aref host i))) + string-list)) + (incf i)) + (format "*%s/%s %s*" + prefix multi-method + (apply 'concat (reverse string-list))))) + +(defun tramp-get-buffer (multi-method method user host) + "Get the connection buffer to be used for USER at HOST using METHOD." + (get-buffer-create (tramp-buffer-name multi-method method user host))) + +(defun tramp-debug-buffer-name (multi-method method user host) + "A name for the debug buffer for USER at HOST using METHOD." + (cond (multi-method + (tramp-buffer-name-multi-method "debug tramp" + multi-method method user host)) + (user + (format "*debug tramp/%s %s@%s*" method user host)) + (t + (format "*debug tramp/%s %s*" method host)))) + +(defun tramp-get-debug-buffer (multi-method method user host) + "Get the debug buffer for USER at HOST using METHOD." + (get-buffer-create (tramp-debug-buffer-name multi-method method user host))) + +(defun tramp-find-executable (multi-method method user host + progname dirlist ignore-tilde) + "Searches for PROGNAME in all directories mentioned in DIRLIST. +First args METHOD, USER and HOST specify the connection, PROGNAME +is the program to search for, and DIRLIST gives the list of directories +to search. If IGNORE-TILDE is non-nil, directory names starting +with `~' will be ignored. + +Returns the full path name of PROGNAME, if found, and nil otherwise. + +This function expects to be in the right *tramp* buffer." + (let (result) + (when ignore-tilde + ;; Remove all ~/foo directories from dirlist. In Emacs 20, + ;; `remove' is in CL, and we want to avoid CL dependencies. + (let (newdl d) + (while dirlist + (setq d (car dirlist)) + (setq dirlist (cdr dirlist)) + (unless (char-equal ?~ (aref d 0)) + (setq newdl (cons d newdl)))) + (setq dirlist (nreverse newdl)))) + (tramp-send-command + multi-method method user host + (format (concat "while read d; " + "do if test -x $d/%s -a -f $d/%s; " + "then echo tramp_executable $d/%s; " + "break; fi; done <<'EOF'") + progname progname progname)) + (mapcar (lambda (d) + (tramp-send-command multi-method method user host d)) + dirlist) + (tramp-send-command multi-method method user host "EOF") + (tramp-wait-for-output) + (goto-char (point-max)) + (when (search-backward "tramp_executable " nil t) + (skip-chars-forward "^ ") + (skip-chars-forward " ") + (buffer-substring (point) (tramp-line-end-position))))) + +(defun tramp-set-remote-path (multi-method method user host var dirlist) + "Sets the remote environment VAR to existing directories from DIRLIST. +I.e., for each directory in DIRLIST, it is tested whether it exists and if +so, it is added to the environment variable VAR." + (let ((existing-dirs + (mapcar + (lambda (x) + (when (and + (file-exists-p + (tramp-make-tramp-file-name multi-method method user host x)) + (file-directory-p + (tramp-make-tramp-file-name multi-method method user host x))) + x)) + dirlist))) + (tramp-send-command + multi-method method user host + (concat var "=" + (mapconcat 'identity (delq nil existing-dirs) ":") + "; export " var)) + (tramp-wait-for-output))) + +;; -- communication with external shell -- + +(defun tramp-find-file-exists-command (multi-method method user host) + "Find a command on the remote host for checking if a file exists. +Here, we are looking for a command which has zero exit status if the +file exists and nonzero exit status otherwise." + (make-local-variable 'tramp-file-exists-command) + (tramp-message 10 "Finding command to check if file exists") + (let ((existing + (tramp-make-tramp-file-name + multi-method method user host + "/")) ;assume this file always exists + (nonexisting + (tramp-make-tramp-file-name + multi-method method user host + "/ this file does not exist "))) ;assume this never exists + ;; The algorithm is as follows: we try a list of several commands. + ;; For each command, we first run `$cmd /' -- this should return + ;; true, as the root directory always exists. And then we run + ;; `$cmd /this\ file\ does\ not\ exist', hoping that the file indeed + ;; does not exist. This should return false. We use the first + ;; command we find that seems to work. + ;; The list of commands to try is as follows: + ;; `ls -d' This works on most systems, but NetBSD 1.4 + ;; has a bug: `ls' always returns zero exit + ;; status, even for files which don't exist. + ;; `test -e' Some Bourne shells have a `test' builtin + ;; which does not know the `-e' option. + ;; `/bin/test -e' For those, the `test' binary on disk normally + ;; provides the option. Alas, the binary + ;; is sometimes `/bin/test' and sometimes it's + ;; `/usr/bin/test'. + ;; `/usr/bin/test -e' In case `/bin/test' does not exist. + (unless (or + (and (setq tramp-file-exists-command "ls -d %s") + (tramp-handle-file-exists-p existing) + (not (tramp-handle-file-exists-p nonexisting))) + (and (setq tramp-file-exists-command "test -e %s") + (tramp-handle-file-exists-p existing) + (not (tramp-handle-file-exists-p nonexisting))) + (and (setq tramp-file-exists-command "/bin/test -e %s") + (tramp-handle-file-exists-p existing) + (not (tramp-handle-file-exists-p nonexisting))) + (and (setq tramp-file-exists-command "/usr/bin/test -e %s") + (tramp-handle-file-exists-p existing) + (not (tramp-handle-file-exists-p nonexisting)))) + (error "Couldn't find command to check if file exists.")))) + + +;; CCC test ksh or bash found for tilde expansion? +(defun tramp-find-shell (multi-method method user host) + "Find a shell on the remote host which groks tilde expansion." + (let ((shell nil)) + (tramp-send-command multi-method method user host "echo ~root") + (tramp-wait-for-output) + (cond + ((string-match "^~root$" (buffer-string)) + (setq shell + (or (tramp-find-executable multi-method method user host + "bash" tramp-remote-path t) + (tramp-find-executable multi-method method user host + "ksh" tramp-remote-path t))) + (unless shell + (error "Couldn't find a shell which groks tilde expansion")) + ;; Hack: avoid reading of ~/.bashrc. What we should do is have an + ;; alist for extra args to give to each shell... + (when (string-match "/bash\\'" shell) + (setq shell (concat shell " --norc"))) + (tramp-message + 5 "Starting remote shell `%s' for tilde expansion..." shell) + (tramp-send-command + multi-method method user host + (concat "PS1='$ ' ; exec " shell)) + (unless (tramp-wait-for-regexp + (get-buffer-process (current-buffer)) + 60 (format "\\(\\$ *\\|\\(%s\\)\\'\\)" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Couldn't find remote `%s' prompt." shell)) + (process-send-string nil (format "PS1='%s%s%s'; PS2=''; PS3=''%s" + tramp-rsh-end-of-line + tramp-end-of-output + tramp-rsh-end-of-line + tramp-rsh-end-of-line)) + (tramp-wait-for-output) + (tramp-send-command multi-method method user host "echo hello") + (tramp-message 5 "Waiting for remote `%s' to start up..." shell) + (unless (tramp-wait-for-output 5) + (unless (tramp-wait-for-output 5) + (pop-to-buffer (buffer-name)) + (error "Couldn't start remote `%s', see buffer `%s' for details" + shell (buffer-name)))) + (tramp-message 5 "Waiting for remote `%s' to start up...done" shell)) + (t (tramp-message 5 "Remote `%s' groks tilde expansion, good" + (tramp-get-remote-sh multi-method method)))))) + +(defun tramp-check-ls-command (multi-method method user host cmd) + "Checks whether the given `ls' executable groks `-n'. +METHOD, USER and HOST specify the connection, CMD (the full path name of) +the `ls' executable. Returns t if CMD supports the `-n' option, nil +otherwise." + (tramp-message 9 "Checking remote `%s' command for `-n' option" + cmd) + (when (tramp-handle-file-executable-p + (tramp-make-tramp-file-name multi-method method user host cmd)) + (let ((result nil)) + (tramp-message 7 "Testing remote command `%s' for -n..." cmd) + (setq result + (tramp-send-command-and-check + multi-method method user host + (format "%s -lnd / >/dev/null" + cmd))) + (tramp-message 7 "Testing remote command `%s' for -n...%s" + cmd + (if (zerop result) "okay" "failed")) + (zerop result)))) + +(defun tramp-check-ls-commands (multi-method method user host cmd dirlist) + "Checks whether the given `ls' executable in one of the dirs groks `-n'. +Returns nil if none was found, else the command is returned." + (let ((dl dirlist) + (result nil)) + ;; It would be better to use the CL function `find', but + ;; we don't want run-time dependencies on CL. + (while (and dl (not result)) + (let ((x (concat (file-name-as-directory (car dl)) cmd))) + (when (tramp-check-ls-command multi-method method user host x) + (setq result x))) + (setq dl (cdr dl))) + result)) + +(defun tramp-find-ls-command (multi-method method user host) + "Finds an `ls' command which groks the `-n' option, returning nil if failed. +\(This option prints numeric user and group ids in a long listing.)" + (tramp-message 9 "Finding a suitable `ls' command") + (or + (tramp-check-ls-commands multi-method method user host "ls" tramp-remote-path) + (tramp-check-ls-commands multi-method method user host "gnuls" tramp-remote-path) + (tramp-check-ls-commands multi-method method user host "gls" tramp-remote-path))) + +;; ------------------------------------------------------------ +;; -- Functions for establishing connection -- +;; ------------------------------------------------------------ + +(defun tramp-process-actions + (multi-method method user host actions &optional timeout) + "Process given ACTIONS for login specified via first four args. +ACTIONS is a list of items (REGEXP FUN), where REGEXP specifies what +output from the remote end to look for, and FUN specifies the action +to take when the regexp matches." + nil) + +(defun tramp-open-connection-telnet (multi-method method user host) + "Open a connection using a telnet METHOD. +This starts the command `telnet HOST ARGS'[*], then waits for a remote +login prompt, then sends the user name USER, then waits for a remote +password prompt. It queries the user for the password, then sends the +password to the remote host. + +If USER is nil, uses value returned by `(user-login-name)' instead. + +Recognition of the remote shell prompt is based on the variable +`shell-prompt-pattern' which must be set up correctly. + +Please note that it is NOT possible to use this connection method +together with an out-of-band transfer method! You must use an inline +transfer method. + +Maybe the different regular expressions need to be tuned. + +* Actually, the telnet program as well as the args to be used can be + specified in the method parameters, see the variable `tramp-methods'." + (save-match-data + (when (tramp-method-out-of-band-p multi-method method) + (error "Cannot use out-of-band method `%s' with telnet connection method" + method)) + (when multi-method + (error "Cannot multi-connect using telnet connection method")) + (tramp-pre-connection multi-method method user host) + (tramp-message 7 "Opening connection for %s@%s using %s..." + (or user (user-login-name)) host method) + (let ((process-environment (copy-sequence process-environment))) + (setenv "TERM" tramp-terminal-type) + (let* ((default-directory (tramp-temporary-file-directory)) + (coding-system-for-read (unless (and (not (featurep 'xemacs)) + (> emacs-major-version 20)) + tramp-dos-coding-system)) + (p (apply 'start-process + (tramp-buffer-name multi-method method user host) + (tramp-get-buffer multi-method method user host) + (tramp-get-telnet-program multi-method method) + host + (tramp-get-telnet-args multi-method method))) + (found nil) + (pw nil)) + (process-kill-without-query p) + (tramp-message 9 "Waiting for login prompt...") + (unless (tramp-wait-for-regexp p nil tramp-login-prompt-regexp) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote login prompt")) + (erase-buffer) + ;; Remote login defaults to local one. + (tramp-message 9 "Sending login name %s" (or user (user-login-name))) + (process-send-string p (concat (or user (user-login-name)) + tramp-rsh-end-of-line)) + (tramp-message 9 "Waiting for password prompt...") + (unless (setq found (tramp-wait-for-regexp + p nil tramp-password-prompt-regexp)) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote password prompt")) + (erase-buffer) + (setq pw (tramp-read-passwd (car found))) + (tramp-message 9 "Sending password") + (process-send-string p (concat pw tramp-rsh-end-of-line)) + (tramp-message 9 "Waiting 30s for remote shell to come up...") + (unless (setq found + (tramp-wait-for-regexp + p 30 (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-wrong-passwd-regexp + shell-prompt-pattern))) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote shell prompt")) + (when (nth 1 found) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Login failed: %s" (nth 1 found))) + (tramp-open-connection-setup-interactive-shell + p multi-method method user host) + (tramp-post-connection multi-method method user host))))) + +;; HHH: Changed to handle the case when USER is nil. +(defun tramp-open-connection-rsh (multi-method method user host) + "Open a connection using an rsh METHOD. +This starts the command `rsh HOST -l USER'[*], then waits for a remote +password or shell prompt. If a password prompt is seen, the user is +queried for a password, this function sends the password to the remote +host and waits for a shell prompt. + +If USER is nil, start the command `rsh HOST'[*] instead + +Recognition of the remote shell prompt is based on the variable +`shell-prompt-pattern' which must be set up correctly. + +Please note that it is NOT possible to use this connection method with +an out-of-band transfer method if this function asks the user for a +password! You must use an inline transfer method in this case. +Sadly, the transfer method cannot be switched on the fly, instead you +must specify the right method in the file name. + +* Actually, the rsh program to be used can be specified in the + method parameters, see the variable `tramp-methods'." + (save-match-data + (when multi-method + (error "Cannot multi-connect using rsh connection method")) + (tramp-pre-connection multi-method method user host) + (if user + (tramp-message 7 "Opening connection for %s@%s using %s..." + user host method) + (tramp-message 7 "Opening connection at %s using %s..." host method)) + (let ((process-environment (copy-sequence process-environment))) + (setenv "TERM" tramp-terminal-type) + (let* ((default-directory (tramp-temporary-file-directory)) + (coding-system-for-read (unless (and (not (featurep 'xemacs)) + (> emacs-major-version 20)) + tramp-dos-coding-system)) + (p (if user + (apply #'start-process + (tramp-buffer-name multi-method method user host) + (tramp-get-buffer multi-method method user host) + (tramp-get-rsh-program multi-method method) + host "-l" user + (tramp-get-rsh-args multi-method method)) + (apply #'start-process + (tramp-buffer-name multi-method method user host) + (tramp-get-buffer multi-method method user host) + (tramp-get-rsh-program multi-method method) + host + (tramp-get-rsh-args multi-method method)))) + (found nil)) + (process-kill-without-query p) + (tramp-message 9 "Waiting 60s for shell or passwd prompt from %s" host) + (setq found + (tramp-wait-for-regexp + p 60 + (format + "\\(%s\\)\\|\\(%s\\)\\'" + tramp-password-prompt-regexp + shell-prompt-pattern))) + (unless found + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote shell or passwd prompt")) + (when (nth 1 found) + (when (tramp-method-out-of-band-p multi-method method) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error (concat "Out of band method `%s' not applicable" + " for remote shell asking for a password") + method)) + (erase-buffer) + (tramp-message 9 "Sending password...") + (tramp-enter-password p (nth 1 found)) + (tramp-message 9 "Sent password, waiting 60s for remote shell prompt") + (setq found (tramp-wait-for-regexp p 60 + (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-wrong-passwd-regexp + shell-prompt-pattern)))) + (unless found + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote shell prompt")) + (when (nth 1 found) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Login failed: %s" (nth 1 found))) + (tramp-message 7 "Initializing remote shell") + (tramp-open-connection-setup-interactive-shell + p multi-method method user host) + (tramp-post-connection multi-method method user host))))) + +;; HHH: Changed. Now utilizes (or user (user-login-name)) instead of USER. +(defun tramp-open-connection-su (multi-method method user host) + "Open a connection using the `su' program with METHOD. +This starts `su - USER', then waits for a password prompt. The HOST +name must be equal to the local host name or to `localhost'. + +If USER is nil, uses value returned by user-login-name instead. + +Recognition of the remote shell prompt is based on the variable +`shell-prompt-pattern' which must be set up correctly. Note that the +other user may have a different shell prompt than you do, so it is not +at all unlikely that this variable is set up wrongly!" + (save-match-data + (when (tramp-method-out-of-band-p multi-method method) + (error "Cannot use out-of-band method `%s' with `su' connection method" + method)) + (unless (or (string-match (concat "^" (regexp-quote host)) + (system-name)) + (string= "localhost" host)) + (error + "Cannot connect to different host `%s' with `su' connection method" + host)) + (when (not user) + (error "Must give user name for `su' connection method")) + (tramp-pre-connection multi-method method user host) + (tramp-message 7 "Opening connection for `%s' using `%s'..." + (or user (user-login-name)) method) + (let ((process-environment (copy-sequence process-environment))) + (setenv "TERM" tramp-terminal-type) + (let* ((default-directory (tramp-temporary-file-directory)) + (coding-system-for-read (unless (and (not (featurep 'xemacs)) + (> emacs-major-version 20)) + tramp-dos-coding-system)) + (p (apply 'start-process + (tramp-buffer-name multi-method method + user host) + (tramp-get-buffer multi-method method + user host) + (tramp-get-su-program multi-method method) + (mapcar + '(lambda (x) + (format-spec + x (list (cons ?u user)))) + (tramp-get-su-args multi-method method)))) + (found nil) + (pw nil)) + (process-kill-without-query p) + (tramp-message 9 "Waiting 30s for shell or password prompt...") + (unless (setq found (tramp-wait-for-regexp + p 30 + (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-password-prompt-regexp + shell-prompt-pattern))) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find shell or password prompt")) + (when (nth 1 found) + (erase-buffer) + (setq pw (tramp-read-passwd (car found))) + (tramp-message 9 "Sending password") + (process-send-string p (concat pw tramp-rsh-end-of-line)) + (tramp-message 9 "Waiting 30s for remote shell to come up...") + (unless (setq found + (tramp-wait-for-regexp + p 30 (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-wrong-passwd-regexp + shell-prompt-pattern))) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote shell prompt")) + (when (nth 1 found) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "`su' failed: %s" (nth 1 found)))) + (tramp-open-connection-setup-interactive-shell + p multi-method method user host) + (tramp-post-connection multi-method method + user host))))) + +;; HHH: Not Changed. Multi method. It is not clear to me how this can +;; handle not giving a user name in the "file name". +;; +;; This is more difficult than for the single-hop method. In the +;; multi-hop-method, the desired behaviour should be that the +;; user must specify names for the telnet hops of which the user +;; name is different than the "original" name (or different from +;; the previous hop. +(defun tramp-open-connection-multi (multi-method method user host) + "Open a multi-hop connection using METHOD. +This uses a slightly changed file name syntax. The idea is to say + [multi/telnet:u1@h1/rsh:u2@h2]/path/to/file +This will use telnet to log in as u1 to h1, then use rsh from there to +log in as u2 to h2." + (save-match-data + (unless multi-method + (error "Multi-hop open connection function called on non-multi method")) + (when (tramp-method-out-of-band-p multi-method method) + (error "No out of band multi-hop connections")) + (unless (and (arrayp method) (not (stringp method))) + (error "METHOD must be an array of strings for multi methods")) + (unless (and (arrayp user) (not (stringp user))) + (error "USER must be an array of strings for multi methods")) + (unless (and (arrayp host) (not (stringp host))) + (error "HOST must be an array of strings for multi methods")) + (unless (and (= (length method) (length user)) + (= (length method) (length host))) + (error "Arrays METHOD, USER, HOST must have equal length")) + (tramp-pre-connection multi-method method user host) + (tramp-message 7 "Opening `%s' connection..." multi-method) + (let ((process-environment (copy-sequence process-environment))) + (setenv "TERM" tramp-terminal-type) + (let* ((default-directory (tramp-temporary-file-directory)) + (coding-system-for-read (unless (and (not (featurep 'xemacs)) + (> emacs-major-version 20)) + tramp-dos-coding-system)) + (p (start-process (tramp-buffer-name multi-method method user host) + (tramp-get-buffer multi-method method user host) + tramp-sh-program)) + (num-hops (length method)) + (i 0)) + (process-kill-without-query p) + (tramp-message 9 "Waiting 60s for local shell to come up...") + (unless (tramp-wait-for-regexp + p 60 (format "%s\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find local shell prompt")) + ;; Now do all the connections as specified. + (while (< i num-hops) + (let* ((m (aref method i)) + (u (aref user i)) + (h (aref host i)) + (entry (assoc m tramp-multi-connection-function-alist)) + (multi-func (nth 1 entry)) + (command (nth 2 entry))) + ;; The multi-funcs don't need to do save-match-data, as that + ;; is done here. + (funcall multi-func p m u h command) + (erase-buffer) + (incf i))) + (tramp-open-connection-setup-interactive-shell + p multi-method method user host) + (tramp-post-connection multi-method method user host))))) + +;; HHH: Changed. Multi method. Don't know how to handle this in the case +;; of no user name provided. Hack to make it work as it did before: +;; changed `user' to `(or user (user-login-name))' in the places where +;; the value is actually used. +(defun tramp-multi-connect-telnet (p method user host command) + "Issue `telnet' command. +Uses shell COMMAND to issue a `telnet' command to log in as USER to +HOST. You can use percent escapes in COMMAND: `%h' is replaced with +the host name, and `%n' is replaced with an end of line character, as +set in `tramp-rsh-end-of-line'. Use `%%' if you want a literal percent +character. + +If USER is nil, uses the return value of (user-login-name) instead." + (let ((cmd (format-spec command (list (cons ?h host) + (cons ?n tramp-rsh-end-of-line)))) + (cmd1 (format-spec command (list (cons ?h host) + (cons ?n "")))) + found pw) + (erase-buffer) + (tramp-message 9 "Sending telnet command `%s'" cmd1) + (process-send-string p cmd) + (tramp-message 9 "Waiting 30s for login prompt from %s" host) + (unless (tramp-wait-for-regexp p 30 tramp-login-prompt-regexp) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find login prompt from host %s" host)) + (erase-buffer) + (tramp-message 9 "Sending login name %s" (or user (user-login-name))) + (process-send-string p (concat (or user (user-login-name)) tramp-rsh-end-of-line)) + (tramp-message 9 "Waiting for password prompt") + (unless (setq found (tramp-wait-for-regexp p nil tramp-password-prompt-regexp)) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find password prompt from host %s" host)) + (erase-buffer) + (setq pw (tramp-read-passwd + (format "Password for %s@%s, %s" (or user (user-login-name)) host found))) + (tramp-message 9 "Sending password") + (process-send-string p (concat pw tramp-rsh-end-of-line)) + (tramp-message 9 "Waiting 60s for remote shell to come up...") + (unless (setq found (tramp-wait-for-regexp + p 60 (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-wrong-passwd-regexp + shell-prompt-pattern))) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find shell prompt from host %s" host)) + (when (nth 1 found) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Login to %s failed: %s" (nth 2 found))))) + +;; HHH: Changed. Multi method. Don't know how to handle this in the case +;; of no user name provided. Hack to make it work as it did before: +;; changed `user' to `(or user (user-login-name))' in the places where +;; the value is actually used. +(defun tramp-multi-connect-rlogin (p method user host command) + "Issue `rlogin' command. +Uses shell COMMAND to issue an `rlogin' command to log in as USER to +HOST. You can use percent escapes in COMMAND. `%u' will be replaced +with the user name, `%h' will be replaced with the host name, and `%n' +will be replaced with the value of `tramp-rsh-end-of-line'. You can use +`%%' if you want to use a literal percent character. + +If USER is nil, uses the return value of (user-login-name) instead." + (let ((cmd (format-spec command (list (cons ?h host) + (cons ?u (or user (user-login-name))) + (cons ?n tramp-rsh-end-of-line)))) + (cmd1 (format-spec command (list (cons ?h host) + (cons ?u (or user (user-login-name))) + (cons ?n "")))) + found) + (erase-buffer) + (tramp-message 9 "Sending rlogin command `%s'" cmd1) + (process-send-string p cmd) + (tramp-message 9 "Waiting 60s for shell or passwd prompt from %s" host) + (unless (setq found + (tramp-wait-for-regexp p 60 + (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-password-prompt-regexp + shell-prompt-pattern))) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote shell or passwd prompt")) + (when (nth 1 found) + (erase-buffer) + (tramp-message 9 "Sending password...") + (tramp-enter-password p (nth 1 found)) + (tramp-message 9 "Sent password, waiting 60s for remote shell prompt") + (setq found (tramp-wait-for-regexp p 60 + (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-wrong-passwd-regexp + shell-prompt-pattern)))) + (unless found + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote shell prompt")) + (when (nth 1 found) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Login failed: %s" (nth 1 found))))) + +;; HHH: Changed. Multi method. Don't know how to handle this in the case +;; of no user name provided. Hack to make it work as it did before: +;; changed `user' to `(or user (user-login-name))' in the places where +;; the value is actually used. +(defun tramp-multi-connect-su (p method user host command) + "Issue `su' command. +Uses shell COMMAND to issue a `su' command to log in as USER on +HOST. The HOST name is ignored, this just changes the user id on the +host currently logged in to. + +If USER is nil, uses the return value of (user-login-name) instead. + +You can use percent escapes in the COMMAND. `%u' is replaced with the +user name, and `%n' is replaced with the value of +`tramp-rsh-end-of-line'. Use `%%' if you want a literal percent +character." + (let ((cmd (format-spec command (list (cons ?u (or user (user-login-name))) + (cons ?n tramp-rsh-end-of-line)))) + (cmd1 (format-spec command (list (cons ?u (or user (user-login-name))) + (cons ?n "")))) + found) + (erase-buffer) + (tramp-message 9 "Sending su command `%s'" cmd1) + (process-send-string p cmd) + (tramp-message 9 "Waiting 60s for shell or passwd prompt for %s" (or user (user-login-name))) + (unless (setq found (tramp-wait-for-regexp + p 60 (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-password-prompt-regexp + shell-prompt-pattern))) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find shell or passwd prompt for %s" + (or user (user-login-name)))) + (when (nth 1 found) + (tramp-message 9 "Sending password...") + (tramp-enter-password p (nth 1 found)) + (erase-buffer) + (tramp-message 9 "Sent password, waiting 60s for remote shell prompt") + (setq found (tramp-wait-for-regexp p 60 + (format "\\(%s\\)\\|\\(%s\\)\\'" + tramp-wrong-passwd-regexp + shell-prompt-pattern)))) + (unless found + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Couldn't find remote shell prompt")) + (when (nth 1 found) + (pop-to-buffer (buffer-name)) + (kill-process p) + (error "Login failed: %s" (nth 1 found))))) + +;; Utility functions. + +(defun tramp-wait-for-regexp (proc timeout regexp) + "Wait for a REGEXP to appear from process PROC within TIMEOUT seconds. +Expects the output of PROC to be sent to the current buffer. Returns +the string that matched, or nil. Waits indefinitely if TIMEOUT is +nil." + (let ((found nil) + (start-time (current-time))) + (cond (timeout + ;; Work around a bug in XEmacs 21, where the timeout + ;; expires faster than it should. This degenerates + ;; to polling for buggy XEmacsen, but oh, well. + (while (and (not found) + (< (tramp-time-diff (current-time) start-time) + timeout)) + (with-timeout (timeout) + (while (not found) + (accept-process-output proc 1) + (goto-char (point-min)) + (setq found (when (re-search-forward regexp nil t) + (tramp-match-string-list))))))) + (t + (while (not found) + (accept-process-output proc 1) + (goto-char (point-min)) + (setq found (when (re-search-forward regexp nil t) + (tramp-match-string-list)))))) + (when tramp-debug-buffer + (append-to-buffer + (tramp-get-debug-buffer tramp-current-multi-method tramp-current-method + tramp-current-user tramp-current-host) + (point-min) (point-max)) + (when (not found) + (save-excursion + (set-buffer + (tramp-get-debug-buffer tramp-current-multi-method tramp-current-method + tramp-current-user tramp-current-host)) + (goto-char (point-max)) + (insert "[[Regexp `" regexp "' not found" + (if timeout (concat " in " timeout " secs") "") + "]]")))) + found)) + +(defun tramp-enter-password (p prompt) + "Prompt for a password and send it to the remote end. +Uses PROMPT as a prompt and sends the password to process P." + (let ((pw (tramp-read-passwd prompt))) + (process-send-string p (concat pw tramp-rsh-end-of-line)))) + +;; HHH: Not Changed. This might handle the case where USER is not +;; given in the "File name" very poorly. Then, the local +;; variable tramp-current user will be set to nil. +(defun tramp-pre-connection (multi-method method user host) + "Do some setup before actually logging in. +METHOD, USER and HOST specify the connection." + (set-buffer (tramp-get-buffer multi-method method user host)) + (set (make-local-variable 'tramp-current-multi-method) multi-method) + (set (make-local-variable 'tramp-current-method) method) + (set (make-local-variable 'tramp-current-user) user) + (set (make-local-variable 'tramp-current-host) host) + (set (make-local-variable 'inhibit-eol-conversion) nil) + (erase-buffer)) + +(defun tramp-open-connection-setup-interactive-shell + (p multi-method method user host) + "Set up an interactive shell. +Mainly sets the prompt and the echo correctly. P is the shell process +to set up. METHOD, USER and HOST specify the connection." + ;; Wait a bit in case the remote end feels like sending a little + ;; junk first. It seems that fencepost.gnu.org does this when doing + ;; a Kerberos login. + (sit-for 1) + (tramp-discard-garbage-erase-buffer p multi-method method user host) + (process-send-string nil (format "exec %s%s" + (tramp-get-remote-sh multi-method method) + tramp-rsh-end-of-line)) + (when tramp-debug-buffer + (save-excursion + (set-buffer (tramp-get-debug-buffer multi-method method user host)) + (goto-char (point-max)) + (tramp-insert-with-face + 'bold (format "$ exec %s\n" (tramp-get-remote-sh multi-method method))))) + (tramp-message 9 "Waiting 30s for remote `%s' to come up..." + (tramp-get-remote-sh multi-method method)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Remote `%s' didn't come up. See buffer `%s' for details" + (tramp-get-remote-sh multi-method method) (buffer-name))) + (tramp-message 9 "Setting up remote shell environment") + (tramp-discard-garbage-erase-buffer p multi-method method user host) + (process-send-string + nil (format "stty -inlcr -echo kill '^U'%s" tramp-rsh-end-of-line)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Couldn't `stty -echo', see buffer `%s'" (buffer-name))) + (erase-buffer) + (process-send-string nil (format "TERM=dumb; export TERM%s" + tramp-rsh-end-of-line)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Couldn't `TERM=dumb; export TERM', see buffer `%s'" (buffer-name))) + ;; Try to set up the coding system correctly. + ;; CCC this can't be the right way to do it. Hm. + (save-excursion + (erase-buffer) + (tramp-message 9 "Determining coding system") + (process-send-string nil (format "echo foo ; echo bar %s" + tramp-rsh-end-of-line)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Couldn't `echo foo; echo bar' to determine line endings'")) + (goto-char (point-min)) + (if (featurep 'mule) + ;; Use MULE to select the right EOL convention for communicating + ;; with the process. + (let* ((cs (or (process-coding-system p) (cons 'undecided 'undecided))) + cs-decode cs-encode) + (when (symbolp cs) (setq cs (cons cs cs))) + (setq cs-decode (car cs)) + (setq cs-encode (cdr cs)) + (unless cs-decode (setq cs-decode 'undecided)) + (unless cs-encode (setq cs-encode 'undecided)) + (setq cs-encode (tramp-coding-system-change-eol-conversion + cs-encode 'unix)) + (when (search-forward "\r" nil t) + (setq cs-decode (tramp-coding-system-change-eol-conversion + cs-decode 'dos))) + (set-buffer-process-coding-system cs-decode cs-encode)) + ;; Look for ^M and do something useful if found. + (when (search-forward "\r" nil t) + ;; We have found a ^M but cannot frob the process coding system + ;; because we're running on a non-MULE Emacs. Let's try + ;; stty, instead. + (tramp-message 9 "Trying `stty -onlcr'") + (process-send-string nil (format "stty -onlcr%s" tramp-rsh-end-of-line)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Couldn't `stty -onlcr', see buffer `%s'" (buffer-name)))))) + (erase-buffer) + (tramp-message + 9 "Waiting 30s for `HISTFILE=$HOME/.tramp_history; HISTSIZE=1'") + (process-send-string + nil (format "HISTFILE=$HOME/.tramp_history; HISTSIZE=1%s" + tramp-rsh-end-of-line)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error (concat "Couldn't `HISTFILE=$HOME/.tramp_history; " + "HISTSIZE=1', see buffer `%s'") + (buffer-name))) + (erase-buffer) + (tramp-message 9 "Waiting 30s for `set +o vi +o emacs'") + (process-send-string + nil (format "set +o vi +o emacs%s" ;mustn't `>/dev/null' with AIX? + tramp-rsh-end-of-line)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Couldn't `set +o vi +o emacs', see buffer `%s'" + (buffer-name))) + (erase-buffer) + (tramp-message 9 "Waiting 30s for `unset MAIL MAILCHECK MAILPATH'") + (process-send-string + nil (format "unset MAIL MAILCHECK MAILPATH 1>/dev/null 2>/dev/null%s" + tramp-rsh-end-of-line)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Couldn't `unset MAIL MAILCHECK MAILPATH', see buffer `%s'" + (buffer-name))) + (erase-buffer) + (tramp-message 9 "Waiting 30s for `unset CDPATH'") + (process-send-string + nil (format "unset CDPATH%s" tramp-rsh-end-of-line)) + (unless (tramp-wait-for-regexp + p 30 (format "\\(\\$ *\\|%s\\)\\'" shell-prompt-pattern)) + (pop-to-buffer (buffer-name)) + (error "Couldn't `unset CDPATH', see buffer `%s'" + (buffer-name))) + (erase-buffer) + (tramp-message 9 "Setting shell prompt") + (tramp-send-command + multi-method method user host + (format "PS1='%s%s%s'; PS2=''; PS3=''" + tramp-rsh-end-of-line + tramp-end-of-output + tramp-rsh-end-of-line)) + (tramp-wait-for-output) + (tramp-send-command multi-method method user host "echo hello") + (tramp-message 9 "Waiting for remote `%s' to come up..." + (tramp-get-remote-sh multi-method method)) + (unless (tramp-wait-for-output 5) + (unless (tramp-wait-for-output 5) + (pop-to-buffer (buffer-name)) + (error "Couldn't set remote shell prompt. See buffer `%s' for details" + (buffer-name)))) + (tramp-message 7 "Waiting for remote `%s' to come up...done" + (tramp-get-remote-sh multi-method method))) + +(defun tramp-post-connection (multi-method method user host) + "Prepare a remote shell before being able to work on it. +METHOD, USER and HOST specify the connection. +Among other things, this finds a shell which groks tilde expansion, +tries to find an `ls' command which groks the `-n' option, sets the +locale to C and sets up the remote shell search path." + ;; Search for a good shell before searching for a command which + ;; checks if a file exists. This is done because Tramp wants to use + ;; "test foo; echo $?" to check if various conditions hold, and + ;; there are buggy /bin/sh implementations which don't execute the + ;; "echo $?" part if the "test" part has an error. In particular, + ;; the Solaris /bin/sh is a problem. I'm betting that all systems + ;; with buggy /bin/sh implementations will have a working bash or + ;; ksh. Whee... + (tramp-find-shell multi-method method user host) + (tramp-find-file-exists-command multi-method method user host) + (sit-for 1) + ;; Without (sit-for 0.1) at least, my machine will almost always blow + ;; up on 'not numberp /root' - a race that causes the 'echo ~root' + ;; output of (tramp-find-shell) to show up along with the output of + ;; (tramp-find-ls-command) testing. + ;; + ;; I can't work out why this is a problem though. The (tramp-wait-for-output) + ;; call in (tramp-find-shell) *should* make this not happen, I thought. + ;; + ;; After much debugging I couldn't find any problem with the implementation + ;; of that function though. The workaround stays for me at least. :/ + ;; + ;; Daniel Pittman <daniel@danann.net> + (make-local-variable 'tramp-ls-command) + (setq tramp-ls-command (tramp-find-ls-command multi-method method user host)) + (unless tramp-ls-command + (tramp-message + 1 + "Danger! Couldn't find ls which groks -n. Muddling through anyway") + (setq tramp-ls-command + (tramp-find-executable multi-method method user host + "ls" tramp-remote-path nil))) + (unless tramp-ls-command + (error "Fatal error: Couldn't find remote executable `ls'")) + (tramp-message 5 "Using remote command `%s' for getting directory listings" + tramp-ls-command) + (tramp-send-command multi-method method user host + (concat "tramp_set_exit_status () {" tramp-rsh-end-of-line + "return $1" tramp-rsh-end-of-line + "}")) + (tramp-wait-for-output) + ;; Set remote PATH variable. + (tramp-set-remote-path multi-method method user host "PATH" tramp-remote-path) + ;; Tell remote shell to use standard time format, needed for + ;; parsing `ls -l' output. + (tramp-send-command multi-method method user host + "LC_TIME=C; export LC_TIME; echo huhu") + (tramp-wait-for-output) + (tramp-send-command multi-method method user host + "mesg n; echo huhu") + (tramp-wait-for-output) + (tramp-send-command multi-method method user host + "biff n ; echo huhu") + (tramp-wait-for-output) + ;; Unalias ls(1) to work around issues with those silly people who make it + ;; spit out ANSI escapes or whatever. + (tramp-send-command multi-method method user host + "unalias ls; echo huhu") + (tramp-wait-for-output) + ;; Does `test A -nt B' work? Use abominable `find' construct if it + ;; doesn't. BSD/OS 4.0 wants the parentheses around the command, + ;; for otherwise the shell crashes. + (erase-buffer) + (make-local-variable 'tramp-test-groks-nt) + (tramp-send-command multi-method method user host + "( test / -nt / )") + (tramp-wait-for-output) + (goto-char (point-min)) + (setq tramp-test-groks-nt + (looking-at (format "\n%s\n" (regexp-quote tramp-end-of-output)))) + (unless tramp-test-groks-nt + (tramp-send-command + multi-method method user host + (concat "tramp_test_nt () {" tramp-rsh-end-of-line + "test -n \"`find $1 -prune -newer $2 -print`\"" tramp-rsh-end-of-line + "}"))) + (tramp-wait-for-output) + ;; Find a `perl'. + (erase-buffer) + (let ((tramp-remote-perl + (or (tramp-find-executable multi-method method user host + "perl5" tramp-remote-path nil) + (tramp-find-executable multi-method method user host + "perl" tramp-remote-path nil)))) + (when tramp-remote-perl + (tramp-set-connection-property "perl" tramp-remote-perl multi-method method user host) + ;; Set up stat in Perl if we can. + (when tramp-remote-perl + (tramp-message 5 "Sending the Perl `file-attributes' implementation.") + (tramp-send-linewise + multi-method method user host + (concat "tramp_file_attributes () {\n" + tramp-remote-perl + " -e '" tramp-perl-file-attributes "' $1 2>/dev/null\n" + "}")) + (tramp-wait-for-output) + (when (string= (tramp-get-encoding-command multi-method method) + "tramp_mimencode") + (tramp-message 5 "Sending the Perl `mime-encode' implementation.") + (tramp-send-linewise + multi-method method user host + (concat "tramp_mimencode () {\n" + (if (tramp-find-executable multi-method method user host + "mimencode" tramp-remote-path t) + "mimencode -b $1" + (concat tramp-remote-perl + " -e '" tramp-perl-mime-encode "' $1 2>/dev/null")) + "\n}")) + (tramp-wait-for-output)) + (when (string= (tramp-get-decoding-command multi-method method) + "tramp_mimedecode") + (tramp-message 5 "Sending the Perl `mime-decode' implementation.") + (tramp-send-linewise + multi-method method user host + (concat "tramp_mimedecode () {\n" + (if (tramp-find-executable multi-method method user host + "mimencode" tramp-remote-path t) + "mimencode -u -b $1" + (concat tramp-remote-perl + " -e '" tramp-perl-mime-decode "' $1 2>/dev/null")) + "\n}")) + (tramp-wait-for-output))))) + ;; Find ln(1) + (erase-buffer) + (let ((ln (tramp-find-executable multi-method method user host + "ln" tramp-remote-path nil))) + (when ln + (tramp-set-connection-property "ln" ln multi-method method user host))) + (erase-buffer) + ;; If encoding/decoding command are given, test to see if they work. + ;; CCC: Maybe it would be useful to run the encoder both locally and + ;; remotely to see if they produce the same result. + (let ((decoding (tramp-get-decoding-command multi-method method)) + (encoding (tramp-get-encoding-command multi-method method)) + (magic-string "xyzzy")) + (when (and (or decoding encoding) (not (and decoding encoding))) + (tramp-kill-process multi-method method user host) + (error + "Must give both decoding and encoding command in method definition")) + (when (and decoding encoding) + (tramp-message + 5 + "Checking to see if encoding/decoding commands work on remote host...") + (tramp-send-command + multi-method method user host + (format "echo %s | %s | %s" + (tramp-shell-quote-argument magic-string) encoding decoding)) + (tramp-wait-for-output) + (unless (looking-at (regexp-quote magic-string)) + (tramp-kill-process multi-method method user host) + (error "Remote host cannot execute de/encoding commands. See buffer `%s' for details" + (buffer-name))) + (erase-buffer) + (tramp-message + 5 "Checking to see if encoding/decoding commands work on remote host...done")))) + + +(defun tramp-maybe-open-connection (multi-method method user host) + "Maybe open a connection to HOST, logging in as USER, using METHOD. +Does not do anything if a connection is already open, but re-opens the +connection if a previous connection has died for some reason." + (let ((p (get-buffer-process (tramp-get-buffer multi-method method user host)))) + (unless (and p + (processp p) + (memq (process-status p) '(run open))) + (when (and p (processp p)) + (delete-process p)) + (funcall (tramp-get-connection-function multi-method method) + multi-method method user host)))) + +(defun tramp-send-command + (multi-method method user host command &optional noerase) + "Send the COMMAND to USER at HOST (logged in using METHOD). +Erases temporary buffer before sending the command (unless NOERASE +is true)." + (tramp-maybe-open-connection multi-method method user host) + (when tramp-debug-buffer + (save-excursion + (set-buffer (tramp-get-debug-buffer multi-method method user host)) + (goto-char (point-max)) + (tramp-insert-with-face 'bold (format "$ %s\n" command)))) + (let ((proc nil)) + (set-buffer (tramp-get-buffer multi-method method user host)) + (unless noerase (erase-buffer)) + (setq proc (get-buffer-process (current-buffer))) + (process-send-string proc + (concat command tramp-rsh-end-of-line)))) + +;; It seems that Tru64 Unix does not like it if long strings are sent +;; to it in one go. (This happens when sending the Perl +;; `file-attributes' implementation, for instance.) Therefore, we +;; have this function which waits a bit at each line. +(defun tramp-send-linewise + (multi-method method user host string &optional noerase) + "Send the STRING to USER at HOST linewise. +Erases temporary buffer before sending the STRING (unless NOERASE +is true). + +The STRING is expected to use Unix line-endings, but the lines sent to +the remote host use line-endings as defined in the variable +`tramp-rsh-end-of-line'." + (tramp-maybe-open-connection multi-method method user host) + (when tramp-debug-buffer + (save-excursion + (set-buffer (tramp-get-debug-buffer multi-method method user host)) + (goto-char (point-max)) + (tramp-insert-with-face 'bold (format "$ %s\n" string)))) + (let ((proc nil) + (lines (split-string string "\n"))) + (set-buffer (tramp-get-buffer multi-method method user host)) + (unless noerase (erase-buffer)) + (setq proc (get-buffer-process (current-buffer))) + (mapcar (lambda (x) + (sleep-for 0.1) + (process-send-string proc + (concat x tramp-rsh-end-of-line))) + lines))) + +(defun tramp-wait-for-output (&optional timeout) + "Wait for output from remote rsh command." + (let ((proc (get-buffer-process (current-buffer))) + (found nil) + (start-time (current-time)) + (end-of-output (concat "^" + (regexp-quote tramp-end-of-output) + "$"))) + ;; Algorithm: get waiting output. See if last line contains + ;; end-of-output sentinel. If not, wait a bit and again get + ;; waiting output. Repeat until timeout expires or end-of-output + ;; sentinel is seen. Will hang if timeout is nil and + ;; end-of-output sentinel never appears. + (save-match-data + (cond (timeout + ;; Work around an XEmacs bug, where the timeout expires + ;; faster than it should. This degenerates into polling + ;; for buggy XEmacsen, but oh, well. + (while (and (not found) + (< (tramp-time-diff (current-time) start-time) + timeout)) + (with-timeout (timeout) + (while (not found) + (accept-process-output proc 1) + (goto-char (point-max)) + (forward-line -1) + (setq found (looking-at end-of-output)))))) + (t + (while (not found) + (accept-process-output proc 1) + (goto-char (point-max)) + (forward-line -1) + (setq found (looking-at end-of-output)))))) + ;; At this point, either the timeout has expired or we have found + ;; the end-of-output sentinel. + (when found + (goto-char (point-max)) + (forward-line -2) + (delete-region (point) (point-max))) + ;; Add output to debug buffer if appropriate. + (when tramp-debug-buffer + (append-to-buffer + (tramp-get-debug-buffer tramp-current-multi-method tramp-current-method + tramp-current-user tramp-current-host) + (point-min) (point-max)) + (when (not found) + (save-excursion + (set-buffer + (tramp-get-debug-buffer tramp-current-multi-method tramp-current-method + tramp-current-user tramp-current-host)) + (goto-char (point-max)) + (insert "[[Remote prompt `" end-of-output "' not found" + (if timeout (concat " in " timeout " secs") "") + "]]")))) + (goto-char (point-min)) + ;; Return value is whether end-of-output sentinel was found. + found)) + +(defun tramp-match-string-list (&optional string) + "Returns list of all match strings. +That is, (list (match-string 0) (match-string 1) ...), according to the +number of matches." + (let* ((nmatches (/ (length (match-data)) 2)) + (i (- nmatches 1)) + (res nil)) + (while (>= i 0) + (setq res (cons (match-string i string) res)) + (setq i (- i 1))) + res)) + +(defun tramp-send-command-and-check (multi-method method user host command + &optional subshell) + "Run COMMAND and check its exit status. +MULTI-METHOD and METHOD specify how to log in (as USER) to the remote HOST. +Sends `echo $?' along with the COMMAND for checking the exit status. If +COMMAND is nil, just sends `echo $?'. Returns the exit status found. + +If the optional argument SUBSHELL is non-nil, the command is executed in +a subshell, ie surrounded by parentheses." + (tramp-send-command multi-method method user host + (concat (if subshell "( " "") + command + (if command " 2>/dev/null; " "") + "echo tramp_exit_status $?" + (if subshell " )" " "))) + (tramp-wait-for-output) + (goto-char (point-max)) + (unless (search-backward "tramp_exit_status " nil t) + (error "Couldn't find exit status of `%s'" command)) + (skip-chars-forward "^ ") + (read (current-buffer))) + +(defun tramp-barf-unless-okay (multi-method method user host command subshell + signal fmt &rest args) + "Run COMMAND, check exit status, throw error if exit status not okay. +Similar to `tramp-send-command-and-check' but accepts two more arguments +FMT and ARGS which are passed to `error'." + (unless (zerop (tramp-send-command-and-check + multi-method method user host command subshell)) + ;; CCC: really pop-to-buffer? Maybe it's appropriate to be more + ;; silent. + (pop-to-buffer (current-buffer)) + (funcall 'signal signal (apply 'format fmt args)))) + +(defun tramp-send-region (multi-method method user host start end) + "Send the region from START to END to remote command +running as USER on HOST using METHOD." + (let ((proc (get-buffer-process + (tramp-get-buffer multi-method method user host)))) + (unless proc + (error "Can't send region to remote host -- not logged in")) + (process-send-region proc start end) + (when tramp-debug-buffer + (append-to-buffer + (tramp-get-debug-buffer multi-method method user host) + start end)))) + +(defun tramp-send-eof (multi-method method user host) + "Send EOF to the remote end. +METHOD, HOST and USER specify the the connection." + (let ((proc (get-buffer-process + (tramp-get-buffer multi-method method user host)))) + (unless proc + (error "Can't send EOF to remote host -- not logged in")) + (process-send-eof proc))) +; (process-send-string proc "\^D"))) + +(defun tramp-kill-process (multi-method method user host) + "Kill the connection process used by Tramp. +MULTI-METHOD, METHOD, USER, and HOST, specify the connection." + (let ((proc (get-buffer-process + (tramp-get-buffer multi-method method user host)))) + (kill-process proc))) + +(defun tramp-discard-garbage-erase-buffer (p multi-method method user host) + "Erase buffer, then discard subsequent garbage. +If `tramp-discard-garbage' is nil, just erase buffer." + (if (not tramp-discard-garbage) + (erase-buffer) + (while (prog1 (erase-buffer) (accept-process-output p 0.25)) + (when tramp-debug-buffer + (save-excursion + (set-buffer (tramp-get-debug-buffer multi-method method user host)) + (goto-char (point-max)) + (tramp-insert-with-face + 'bold (format "Additional characters detected\n"))))))) + +(defun tramp-mode-string-to-int (mode-string) + "Converts a ten-letter `drwxrwxrwx'-style mode string into mode bits." + (let* ((mode-chars (string-to-vector mode-string)) + (owner-read (aref mode-chars 1)) + (owner-write (aref mode-chars 2)) + (owner-execute-or-setid (aref mode-chars 3)) + (group-read (aref mode-chars 4)) + (group-write (aref mode-chars 5)) + (group-execute-or-setid (aref mode-chars 6)) + (other-read (aref mode-chars 7)) + (other-write (aref mode-chars 8)) + (other-execute-or-sticky (aref mode-chars 9))) + (save-match-data + (logior + (case owner-read + (?r (tramp-octal-to-decimal "00400")) (?- 0) + (t (error "Second char `%c' must be one of `r-'" owner-read))) + (case owner-write + (?w (tramp-octal-to-decimal "00200")) (?- 0) + (t (error "Third char `%c' must be one of `w-'" owner-write))) + (case owner-execute-or-setid + (?x (tramp-octal-to-decimal "00100")) + (?S (tramp-octal-to-decimal "04000")) + (?s (tramp-octal-to-decimal "04100")) + (?- 0) + (t (error "Fourth char `%c' must be one of `xsS-'" + owner-execute-or-setid))) + (case group-read + (?r (tramp-octal-to-decimal "00040")) (?- 0) + (t (error "Fifth char `%c' must be one of `r-'" group-read))) + (case group-write + (?w (tramp-octal-to-decimal "00020")) (?- 0) + (t (error "Sixth char `%c' must be one of `w-'" group-write))) + (case group-execute-or-setid + (?x (tramp-octal-to-decimal "00010")) + (?S (tramp-octal-to-decimal "02000")) + (?s (tramp-octal-to-decimal "02010")) + (?- 0) + (t (error "Seventh char `%c' must be one of `xsS-'" + group-execute-or-setid))) + (case other-read + (?r (tramp-octal-to-decimal "00004")) (?- 0) + (t (error "Eighth char `%c' must be one of `r-'" other-read))) + (case other-write + (?w (tramp-octal-to-decimal "00002")) (?- 0) + (t (error "Nineth char `%c' must be one of `w-'" other-write))) + (case other-execute-or-sticky + (?x (tramp-octal-to-decimal "00001")) + (?T (tramp-octal-to-decimal "01000")) + (?t (tramp-octal-to-decimal "01001")) + (?- 0) + (t (error "Tenth char `%c' must be one of `xtT-'" + other-execute-or-sticky))))))) + + +(defun tramp-file-mode-from-int (mode) + "Turn an integer representing a file mode into an ls(1)-like string." + (let ((type (cdr (assoc (logand (lsh mode -12) 15) tramp-file-mode-type-map))) + (user (logand (lsh mode -6) 7)) + (group (logand (lsh mode -3) 7)) + (other (logand (lsh mode -0) 7)) + (suid (> (logand (lsh mode -9) 4) 0)) + (sgid (> (logand (lsh mode -9) 2) 0)) + (sticky (> (logand (lsh mode -9) 1) 0))) + (setq user (tramp-file-mode-permissions user suid "s")) + (setq group (tramp-file-mode-permissions group sgid "s")) + (setq other (tramp-file-mode-permissions other sticky "t")) + (concat type user group other))) + + +(defun tramp-file-mode-permissions (perm suid suid-text) + "Convert a permission bitset into a string. +This is used internally by `tramp-file-mode-from-int'." + (let ((r (> (logand perm 4) 0)) + (w (> (logand perm 2) 0)) + (x (> (logand perm 1) 0))) + (concat (or (and r "r") "-") + (or (and w "w") "-") + (or (and suid x suid-text) ; suid, execute + (and suid (upcase suid-text)) ; suid, !execute + (and x "x") "-")))) ; !suid + + +(defun tramp-decimal-to-octal (i) + "Return a string consisting of the octal digits of I. +Not actually used. Use `(format \"%o\" i)' instead?" + (cond ((< i 0) (error "Cannot convert negative number to octal")) + ((not (integerp i)) (error "Cannot convert non-integer to octal")) + ((zerop i) "0") + (t (concat (tramp-decimal-to-octal (/ i 8)) + (number-to-string (% i 8)))))) + + +;;(defun tramp-octal-to-decimal (ostr) +;; "Given a string of octal digits, return a decimal number." +;; (cond ((null ostr) 0) +;; ((string= "" ostr) 0) +;; (t (let ((last (aref ostr (1- (length ostr)))) +;; (rest (substring ostr 0 (1- (length ostr))))) +;; (unless (and (>= last ?0) +;; (<= last ?7)) +;; (error "Not an octal digit: %c" last)) +;; (+ (- last ?0) (* 8 (tramp-octal-to-decimal rest))))))) +;; Kudos to Gerd Moellmann for this suggestion. +(defun tramp-octal-to-decimal (ostr) + "Given a string of octal digits, return a decimal number." + (let ((x (or ostr ""))) + ;; `save-match' is in `tramp-mode-string-to-int' which calls this. + (unless (string-match "\\`[0-7]*\\'" x) + (error "Non-octal junk in string `%s'" x)) + (string-to-number ostr 8))) + +(defun tramp-shell-case-fold (string) + "Converts STRING to shell glob pattern which ignores case." + (mapconcat + (lambda (c) + (if (equal (downcase c) (upcase c)) + (vector c) + (format "[%c%c]" (downcase c) (upcase c)))) + string + "")) + + +;; ------------------------------------------------------------ +;; -- TRAMP file names -- +;; ------------------------------------------------------------ +;; Conversion functions between external representation and +;; internal data structure. Convenience functions for internal +;; data structure. + +(defstruct tramp-file-name multi-method method user host path) + +(defun tramp-tramp-file-p (name) + "Return t iff NAME is a tramp file." + (save-match-data + (string-match tramp-file-name-regexp name))) + +;; HHH: Changed. Used to assign the return value of (user-login-name) +;; to the `user' part of the structure if a user name was not +;; provided, now it assigns nil. +(defun tramp-dissect-file-name (name) + "Return an `tramp-file-name' structure. +The structure consists of remote method, remote user, remote host and +remote path name." + (let (method) + (save-match-data + (unless (string-match (nth 0 tramp-file-name-structure) name) + (error "Not a tramp file name: %s" name)) + (setq method (or (match-string (nth 1 tramp-file-name-structure) name) + tramp-default-method)) + (if (member method tramp-multi-methods) + ;; If it's a multi method, the file name structure contains + ;; arrays of method, user and host. + (tramp-dissect-multi-file-name name) + ;; Normal method. + (make-tramp-file-name + :multi-method nil + :method method + :user (or (match-string (nth 2 tramp-file-name-structure) name) + nil) + :host (match-string (nth 3 tramp-file-name-structure) name) + :path (match-string (nth 4 tramp-file-name-structure) name)))))) + +;; HHH: Not Changed. Multi method. Will probably not handle the case where +;; a user name is not provided in the "file name" very well. +(defun tramp-dissect-multi-file-name (name) + "Not implemented yet." + (let ((regexp (nth 0 tramp-multi-file-name-structure)) + (method-index (nth 1 tramp-multi-file-name-structure)) + (hops-index (nth 2 tramp-multi-file-name-structure)) + (path-index (nth 3 tramp-multi-file-name-structure)) + (hop-regexp (nth 0 tramp-multi-file-name-hop-structure)) + (hop-method-index (nth 1 tramp-multi-file-name-hop-structure)) + (hop-user-index (nth 2 tramp-multi-file-name-hop-structure)) + (hop-host-index (nth 3 tramp-multi-file-name-hop-structure)) + method hops len hop-methods hop-users hop-hosts path) + (unless (string-match (format regexp hop-regexp) name) + (error "Not a multi tramp file name: %s" name)) + (setq method (match-string method-index name)) + (setq hops (match-string hops-index name)) + (setq len (/ (length (match-data t)) 2)) + (when (< path-index 0) (incf path-index len)) + (setq path (match-string path-index name)) + (let ((index 0)) + (while (string-match hop-regexp hops index) + (setq index (match-end 0)) + (setq hop-methods + (cons (match-string hop-method-index hops) hop-methods)) + (setq hop-users + (cons (match-string hop-user-index hops) hop-users)) + (setq hop-hosts + (cons (match-string hop-host-index hops) hop-hosts)))) + (make-tramp-file-name + :multi-method method + :method (apply 'vector (reverse hop-methods)) + :user (apply 'vector (reverse hop-users)) + :host (apply 'vector (reverse hop-hosts)) + :path path))) + +(defun tramp-make-tramp-file-name (multi-method method user host path) + "Constructs a tramp file name from METHOD, USER, HOST and PATH." + (unless tramp-make-tramp-file-format + (error "`tramp-make-tramp-file-format' is nil")) + (if multi-method + (tramp-make-tramp-multi-file-name multi-method method user host path) + (if user + (format-spec tramp-make-tramp-file-format + (list (cons ?m method) + (cons ?u user) + (cons ?h host) + (cons ?p path))) + (format-spec tramp-make-tramp-file-user-nil-format + (list (cons ?m method) + (cons ?h host) + (cons ?p path)))))) + +;; CCC: Henrik Holm: Not Changed. Multi Method. What should be done +;; with this when USER is nil? +(defun tramp-make-tramp-multi-file-name (multi-method method user host path) + "Constructs a tramp file name for a multi-hop method." + (unless tramp-make-multi-tramp-file-format + (error "`tramp-make-multi-tramp-file-format' is nil")) + (let* ((prefix-format (nth 0 tramp-make-multi-tramp-file-format)) + (hop-format (nth 1 tramp-make-multi-tramp-file-format)) + (path-format (nth 2 tramp-make-multi-tramp-file-format)) + (prefix (format-spec prefix-format (list (cons ?m multi-method)))) + (hops "") + (path (format-spec path-format (list (cons ?p path)))) + (i 0) + (len (length method))) + (while (< i len) + (let ((m (aref method i)) + (u (aref user i)) + (h (aref host i))) + (setq hops (concat hops + (format-spec + hop-format + (list (cons ?m m) + (cons ?u u) + (cons ?h h))))) + (incf i))) + (concat prefix hops path))) + +;; HHH: Changed. Handles the case where no user name is given in the +;; file name. +(defun tramp-make-rcp-program-file-name (user host path) + "Create a file name suitable to be passed to `rcp'." + (if user + (format "%s@%s:%s" user host path) + (format "%s:%s" host path))) + +(defun tramp-method-out-of-band-p (multi-method method) + "Return t if this is an out-of-band method, nil otherwise. +It is important to check for this condition, since it is not possible +to enter a password for the `tramp-rcp-program'." + (tramp-get-rcp-program multi-method method)) + +;; Variables local to connection. + +(defun tramp-get-ls-command (multi-method method user host) + (save-excursion + (tramp-maybe-open-connection multi-method method user host) + (set-buffer (tramp-get-buffer multi-method method user host)) + tramp-ls-command)) + +(defun tramp-get-test-groks-nt (multi-method method user host) + (save-excursion + (tramp-maybe-open-connection multi-method method user host) + (set-buffer (tramp-get-buffer multi-method method user host)) + tramp-test-groks-nt)) + +(defun tramp-get-file-exists-command (multi-method method user host) + (save-excursion + (tramp-maybe-open-connection multi-method method user host) + (set-buffer (tramp-get-buffer multi-method method user host)) + tramp-file-exists-command)) + +(defun tramp-get-remote-perl (multi-method method user host) + (tramp-get-connection-property "perl" nil multi-method method user host)) + +(defun tramp-get-remote-ln (multi-method method user host) + (tramp-get-connection-property "ln" nil multi-method method user host)) + +;; Get a property of a TRAMP connection. +(defun tramp-get-connection-property (property default multi-method method user host) + "Get the named property for the connection. +If the value is not set for the connection, return `default'" + (tramp-maybe-open-connection multi-method method user host) + (with-current-buffer (tramp-get-buffer multi-method method user host) + (let (error) + (condition-case nil + (symbol-value (intern (concat "tramp-connection-property-" property))) + (error default))))) + +;; Set a property of a TRAMP connection. +(defun tramp-set-connection-property (property value multi-method method user host) + "Set the named property of a TRAMP connection." + (tramp-maybe-open-connection multi-method method user host) + (with-current-buffer (tramp-get-buffer multi-method method user host) + (set (make-local-variable + (intern (concat "tramp-connection-property-" property))) + value))) + + + +(defun tramp-get-connection-function (multi-method method) + (second (or (assoc 'tramp-connection-function + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify a connection function" + (or multi-method method))))) + +(defun tramp-get-remote-sh (multi-method method) + (second (or (assoc 'tramp-remote-sh + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify a remote shell" + (or multi-method method))))) + +(defun tramp-get-rsh-program (multi-method method) + (second (or (assoc 'tramp-rsh-program + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify an rsh program" + (or multi-method method))))) + +(defun tramp-get-rsh-args (multi-method method) + (second (or (assoc 'tramp-rsh-args + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify rsh args" + (or multi-method method))))) + +(defun tramp-get-rcp-program (multi-method method) + (second (or (assoc 'tramp-rcp-program + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify an rcp program" + (or multi-method method))))) + +(defun tramp-get-rcp-args (multi-method method) + (second (or (assoc 'tramp-rcp-args + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify rcp args" + (or multi-method method))))) + +(defun tramp-get-rcp-keep-date-arg (multi-method method) + (second (or (assoc 'tramp-rcp-keep-date-arg + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify `keep-date' arg for tramp" + (or multi-method method))))) + +(defun tramp-get-su-program (multi-method method) + (second (or (assoc 'tramp-su-program + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify a su program" + (or multi-method method))))) + +(defun tramp-get-su-args (multi-method method) + (second (or (assoc 'tramp-su-args + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify su args" + (or multi-method method))))) + +(defun tramp-get-encoding-command (multi-method method) + (second (or (assoc 'tramp-encoding-command + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify an encoding command" + (or multi-method method))))) + +(defun tramp-get-decoding-command (multi-method method) + (second (or (assoc 'tramp-decoding-command + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify a decoding command" + (or multi-method method))))) + +(defun tramp-get-encoding-function (multi-method method) + (second (or (assoc 'tramp-encoding-function + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify an encoding function" + (or multi-method method))))) + +(defun tramp-get-decoding-function (multi-method method) + (second (or (assoc 'tramp-decoding-function + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify a decoding function" + (or multi-method method))))) + +(defun tramp-get-telnet-program (multi-method method) + (second (or (assoc 'tramp-telnet-program + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify a telnet program" + (or multi-method method))))) + +(defun tramp-get-telnet-args (multi-method method) + (second (or (assoc 'tramp-telnet-args + (assoc (or multi-method method tramp-default-method) + tramp-methods)) + (error "Method `%s' didn't specify telnet args" + (or multi-method method))))) + +;; Auto saving to a special directory. + +(defun tramp-make-auto-save-file-name (fn) + "Returns a file name in `tramp-auto-save-directory' for autosaving this file." + (when tramp-auto-save-directory + (unless (file-exists-p tramp-auto-save-directory) + (make-directory tramp-auto-save-directory t))) + ;; jka-compr doesn't like auto-saving, so by appending "~" to the + ;; file name we make sure that jka-compr isn't used for the + ;; auto-save file. + (let ((buffer-file-name (expand-file-name + (tramp-subst-strs-in-string '(("_" . "|") + ("/" . "_a") + (":" . "_b") + ("|" . "__") + ("[" . "_l") + ("]" . "_r")) + fn) + tramp-auto-save-directory))) + (make-auto-save-file-name))) + +(defadvice make-auto-save-file-name + (around tramp-advice-make-auto-save-file-name () activate) + "Invoke `tramp-make-auto-save-file-name' for tramp files." + (if (and (buffer-file-name) (tramp-tramp-file-p (buffer-file-name)) + tramp-auto-save-directory) + (setq ad-return-value + (tramp-make-auto-save-file-name (buffer-file-name))) + ad-do-it)) + +(defun tramp-subst-strs-in-string (alist string) + "Replace all occurrences of the string FROM with TO in STRING. +ALIST is of the form ((FROM . TO) ...)." + (save-match-data + (while alist + (let* ((pr (car alist)) + (from (car pr)) + (to (cdr pr))) + (while (string-match (regexp-quote from) string) + (setq string (replace-match to t t string))) + (setq alist (cdr alist)))) + string)) + +(defun tramp-insert-with-face (face string) + "Insert text with a specific face." + (let ((start (point))) + (insert string) + (add-text-properties start (point) (list 'face face)))) + +;; ------------------------------------------------------------ +;; -- Compatibility functions section -- +;; ------------------------------------------------------------ + +(defun tramp-temporary-file-directory () + "Return name of directory for temporary files (compat function). +For Emacs, this is the variable `temporary-file-directory', for XEmacs +this is the function `temp-directory'." + (cond ((boundp 'temporary-file-directory) + (symbol-value 'temporary-file-directory)) + ((fboundp 'temp-directory) + (funcall (symbol-function 'temp-directory))) ;pacify byte-compiler + ((let ((d (getenv "TEMP"))) (and d (file-directory-p d))) + (file-name-as-directory (getenv "TEMP"))) + ((let ((d (getenv "TMP"))) (and d (file-directory-p d))) + (file-name-as-directory (getenv "TMP"))) + ((let ((d (getenv "TMPDIR"))) (and d (file-directory-p d))) + (file-name-as-directory (getenv "TMPDIR"))) + ((file-exists-p "c:/temp") (file-name-as-directory "c:/temp")) + (t (message (concat "Neither `temporary-file-directory' nor " + "`temp-directory' is defined -- using /tmp.")) + (file-name-as-directory "/tmp")))) + +(defun tramp-read-passwd (prompt) + "Read a password from user (compat function). +Invokes `read-passwd' if that is defined, else `ange-ftp-read-passwd'." + (apply + (if (fboundp 'read-passwd) #'read-passwd #'ange-ftp-read-passwd) + (list prompt))) + +(defun tramp-time-diff (t1 t2) + "Return the difference between the two times, in seconds. +T1 and T2 are time values (as returned by `current-time' for example). + +NOTE: This function will fail if the time difference is too large to +fit in an integer." + ;; Pacify byte-compiler with `symbol-function'. + (cond ((fboundp 'subtract-time) + (cadr (funcall (symbol-function 'subtract-time) t1 t2))) + ((fboundp 'itimer-time-difference) + (floor (funcall + (symbol-function 'itimer-time-difference) + (if (< (length t1) 3) (append t1 '(0)) t1) + (if (< (length t2) 3) (append t2 '(0)) t2)))) + (t + ;; snarfed from Emacs 21 time-date.el + (cadr (let ((borrow (< (cadr t1) (cadr t2)))) + (list (- (car t1) (car t2) (if borrow 1 0)) + (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2)))))))) + +(defun tramp-coding-system-change-eol-conversion (coding-system eol-type) + "Return a coding system like CODING-SYSTEM but with given EOL-TYPE. +EOL-TYPE can be one of `dos', `unix', or `mac'." + (cond ((fboundp 'coding-system-change-eol-conversion) + (apply #'coding-system-change-eol-conversion + (list coding-system eol-type))) + ((fboundp 'subsidiary-coding-system) + (apply + #'subsidiary-coding-system + (list coding-system + (cond ((eq eol-type 'dos) 'crlf) + ((eq eol-type 'unix) 'lf) + ((eq eol-type 'mac) 'cr) + (t + (error "Unknown EOL-TYPE `%s', must be %s" + eol-type + "`dos', `unix', or `mac'")))))) + (t (error "Can't change EOL conversion -- is MULE missing?")))) + +(defun tramp-split-string (string pattern) + "Like `split-string' but omit empty strings. +In Emacs, (split-string \"/foo/bar\" \"/\") returns (\"foo\" \"bar\"). +This is, the first, empty, element is omitted. In XEmacs, the first +element is not omitted. + +Note: this function has been written for `tramp-handle-file-truename'. +If you want to use it for something else, you'll have to check whether +it does the right thing." + (delete "" (split-string string pattern))) + +;; ------------------------------------------------------------ +;; -- Kludges section -- +;; ------------------------------------------------------------ + +;; Currently (as of Emacs 20.5), the function `shell-quote-argument' +;; does not deal well with newline characters. Newline is replaced by +;; backslash newline. But if, say, the string `a backslash newline b' +;; is passed to a shell, the shell will expand this into "ab", +;; completely omitting the newline. This is not what was intended. +;; It does not appear to be possible to make the function +;; `shell-quote-argument' work with newlines without making it +;; dependent on the shell used. But within this package, we know that +;; we will always use a Bourne-like shell, so we use an approach which +;; groks newlines. +;; +;; The approach is simple: we call `shell-quote-argument', then +;; massage the newline part of the result. +;; +;; This function should produce a string which is grokked by a Unix +;; shell, even if the Emacs is running on Windows. Since this is the +;; kludges section, we bind `system-type' in such a way that +;; `shell-quote-arguments' behaves as if on Unix. +;; +;; Thanks to Mario DeWeerd for the hint that it is sufficient for this +;; function to work with Bourne-like shells. +;; +;; CCC: This function should be rewritten so that +;; `shell-quote-argument' is not used. This way, we are safe from +;; changes in `shell-quote-argument'. +(defun tramp-shell-quote-argument (s) + "Similar to `shell-quote-argument', but groks newlines. +Only works for Bourne-like shells." + (let ((system-type 'not-windows)) + (save-match-data + (let ((result (shell-quote-argument s)) + (nl (regexp-quote (format "\\%s" tramp-rsh-end-of-line)))) + (when (and (>= (length result) 2) + (string= (substring result 0 2) "\\~")) + (setq result (substring result 1))) + (while (string-match nl result) + (setq result (replace-match (format "'%s'" tramp-rsh-end-of-line) + t t result))) + result)))) + +;; ;; EFS hooks itself into the file name handling stuff in more places +;; ;; than just `file-name-handler-alist'. The following tells EFS to stay +;; ;; away from tramp.el paths. +;; ;; +;; ;; This is needed because EFS installs (efs-dired-before-readin) into +;; ;; 'dired-before-readin-hook'. This prevents EFS from opening an FTP +;; ;; connection to help it's dired process. Not that I have any real +;; ;; idea *why* this is helpful to dired. +;; ;; +;; ;; Anyway, this advice fixes the problem (with a sledgehammer :) +;; ;; +;; ;; Daniel Pittman <daniel@danann.net> +;; ;; +;; ;; CCC: when the other defadvice calls have disappeared, make sure +;; ;; not to call defadvice unless it's necessary. How do we find out whether +;; ;; it is necessary? (featurep 'efs) is surely the wrong way -- +;; ;; EFS might nicht be loaded yet. +;; (defadvice efs-ftp-path (around dont-match-tramp-path activate protect) +;; "Cause efs-ftp-path to fail when the path is a TRAMP path." +;; (if (tramp-tramp-file-p (ad-get-arg 0)) +;; nil +;; ad-do-it)) + +;; We currently use "[" and "]" in the filename format. In Emacs +;; 20.x, this means that Emacs wants to expand wildcards if +;; `find-file-wildcards' is non-nil, and then barfs because no +;; expansion could be found. We detect this situation and do +;; something really awful: we have `file-expand-wildcards' return the +;; original filename if it can't expand anything. Let's just hope +;; that this doesn't break anything else. +;; +;; Another problem is that the check is done by Emacs version, which +;; is really not what we want to do. Oh, well. + +;;(when (and (not (featurep 'xemacs)) +;; (= emacs-major-version 20)) +;; It seems that this advice is needed in Emacs 21, too. +(defadvice file-expand-wildcards (around tramp-fix activate) + (let ((name (ad-get-arg 0))) + (if (tramp-tramp-file-p name) + ;; If it's a Tramp file, dissect it and look if wildcards + ;; need to be expanded at all. + (let ((v (tramp-dissect-file-name name))) + (if (string-match "[[*?]" (tramp-file-name-path v)) + (let ((res ad-do-it)) + (setq ad-return-value (or res (list name)))) + (setq ad-return-value (list name)))) + ;; If it is not a Tramp file, just run the original function. + (let ((res ad-do-it)) + (setq ad-return-value (or res (list name))))))) +;; ) + +;; Make the `reporter` functionality available for making bug reports about +;; the package. A most useful piece of code. + +(unless (fboundp 'reporter-submit-bug-report) + (autoload 'reporter-submit-bug-report "reporter")) + +(defun tramp-bug () + "Submit a bug report to the TRAMP developers." + (interactive) + (require 'reporter) + (let ((reporter-prompt-for-summary-p t)) + (reporter-submit-bug-report + tramp-bug-report-address ; to-address + (format "tramp (%s)" tramp-version) ; package name and version + `(;; Current state + tramp-ls-command + tramp-test-groks-nt + tramp-file-exists-command + tramp-current-multi-method + tramp-current-method + tramp-current-user + tramp-current-host + + ;; System defaults + tramp-auto-save-directory ; vars to dump + tramp-default-method + tramp-rsh-end-of-line + tramp-remote-path + tramp-login-prompt-regexp + tramp-password-prompt-regexp + tramp-wrong-passwd-regexp + tramp-temp-name-prefix + tramp-file-name-structure + tramp-file-name-regexp + tramp-multi-file-name-structure + tramp-multi-file-name-hop-structure + tramp-multi-methods + tramp-multi-connection-function-alist + tramp-make-tramp-file-format + tramp-end-of-output + + ;; Non-tramp variables of interest + shell-prompt-pattern + backup-by-copying + backup-by-copying-when-linked + backup-by-copying-when-mismatch + ,(when (boundp 'backup-by-copying-when-privileged-mismatch) + 'backup-by-copying-when-privileged-mismatch) + file-name-handler-alist) + nil ; pre-hook + nil ; post-hook + "\ +Enter your bug report in this message, including as much detail as you +possibly can about the problem, what you did to cause it and what the +local and remote machines are. + +If you can give a simple set of instructions to make this bug happen +reliably, please include those. Thank you for helping kill bugs in +TRAMP. +--bug report follows this line--"))) + +(defalias 'tramp-submit-bug 'tramp-bug) + +(provide 'tramp) + +;; Make sure that we get integration with the VC package. +;; When it is loaded, we need to pull in the integration module. +;; This must come after (provide 'tramp) because tramp-vc.el +;; requires tramp. +(eval-after-load "vc" + '(require 'tramp-vc)) + +;;; TODO: + +;; * Cooperate with PCL-CVS. It uses start-process, which doesn't +;; work for remote files. +;; * Allow /[method/user@host:port] syntax for the ssh "-p" argument. +;; * Rewrite `tramp-shell-quote-argument' to abstain from using +;; `shell-quote-argument'. +;; * Completion gets confused when you leave out the method name. +;; * Support `dired-compress-file' filename handler. +;; * In Emacs 21, `insert-directory' shows total number of bytes used +;; by the files in that directory. Add this here. +;; * Avoid screen blanking when hitting `g' in dired. (Eli Tziperman) +;; * Make ffap.el grok Tramp filenames. (Eli Tziperman) +;; * When logging in, keep looking for questions according to an alist +;; and then invoke the right function. +;; * Case-insensitive filename completion. (Norbert Goevert.) +;; * Running CVS remotely doesn't appear to work right. It thinks +;; files are locked by somebody else even if I'm the locking user. +;; Sometimes, one gets `No CVSROOT specified' errors from CVS. +;; (Skip Montanaro) +;; * Don't use globbing for directories with many files, as this is +;; likely to produce long command lines, and some shells choke on +;; long command lines. +;; * Implement `load' operation. +;; * Find out about the new auto-save mechanism in Emacs 21 and +;; do the right thing. +;; * `vc-directory' does not work. It never displays any files, even +;; if it does show files when run locally. +;; * Allow correction of passwords, if the remote end allows this. +;; (Mark Hershberger) +;; * Make sure permissions of tmp file are good. +;; (Nelson Minar <nelson@media.mit.edu>) +;; * Grok passwd prompts with scp? (David Winter +;; <winter@nevis1.nevis.columbia.edu>). Maybe just do `ssh -l user +;; host', then wait a while for the passwd or passphrase prompt. If +;; there is one, remember the passwd/phrase. +;; * How to deal with MULE in `insert-file-contents' and `write-region'? +;; * Do asynchronous `shell-command's. +;; * Grok `append' parameter for `write-region'. +;; * Test remote ksh or bash for tilde expansion in `tramp-find-shell'? +;; * abbreviate-file-name +;; * grok ~ in tramp-remote-path (Henrik Holm <henrikh@tele.ntnu.no>) +;; * `C' in dired gives error `not tramp file name'. +;; * Also allow to omit user names when doing multi-hop. Not sure yet +;; what the user names should default to, though. +;; * better error checking. At least whenever we see something +;; strange when doing zerop, we should kill the process and start +;; again. (Greg Stark) +;; * Add caching for filename completion. (Greg Stark) +;; Of course, this has issues with usability (stale cache bites) +;; -- <daniel@danann.net> +;; * Provide a local cache of old versions of remote files for the rsync +;; transfer method to use. (Greg Stark) +;; * Remove unneeded parameters from methods. +;; * Invoke rsync once for copying a whole directory hierarchy. +;; (Francesco Potortì) +;; * Should we set PATH ourselves or should we rely on the remote end +;; to do it? +;; * Do the autoconf thing. +;; * Make it work for XEmacs 20, which is missing `with-timeout'. +;; * Allow non-Unix remote systems. (More a long-term thing.) +;; * Make it work for different encodings, and for different file name +;; encodings, too. (Daniel Pittman) +;; * Change applicable functions to pass a struct tramp-file-name rather +;; than the individual items MULTI-METHOD, METHOD, USER, HOST, PATH. +;; * Implement asynchronous shell commands. +;; * Clean up unused *tramp/foo* buffers after a while. (Pete Forman) +;; * Progress reports while copying files. (Michael Kifer) +;; * `Smart' connection method that uses inline for small and out of +;; band for large files. (Michael Kifer) +;; * Don't search for perl5 and perl. Instead, only search for perl and +;; then look if it's the right version (with `perl -v'). +;; * When editing a remote CVS controlled file as a different user, VC +;; gets confused about the file locking status. Try to find out why +;; the workaround doesn't work. +;; * When user is running ssh-agent, it would be useful to add the +;; passwords typed by the user to that agent. This way, the next time +;; round, the users don't have to type all this in again. +;; This would be especially useful for start-process, I think. +;; An easy way to implement start-process is to open a second shell +;; connection which is inconvenient if the user has to reenter +;; passwords. +;; * Change `copy-file' to grok the case where the filename handler +;; for the source and the target file are different. Right now, +;; it looks at the source file and then calls that handler, if +;; there is one. But since ange-ftp, for instance, does not know +;; about Tramp, it does not do the right thing if the target file +;; name is a Tramp name. + +;; Functions for file-name-handler-alist: +;; diff-latest-backup-file -- in diff.el +;; dired-compress-file +;; dired-uncache -- this will be needed when we do insert-directory caching +;; file-name-as-directory -- use primitive? +;; file-name-directory -- use primitive? +;; file-name-nondirectory -- use primitive? +;; file-name-sans-versions -- use primitive? +;; file-newer-than-file-p +;; find-backup-file-name +;; get-file-buffer -- use primitive +;; load +;; unhandled-file-name-directory +;; vc-registered + +;;; tramp.el ends here
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/man/tramp.texi Mon Jun 17 11:47:23 2002 +0000 @@ -0,0 +1,1626 @@ +\input texinfo @c -*-texinfo-*- +@c %**start of header +@setfilename tramp.info +@settitle TRAMP User Manual +@setchapternewpage odd +@c %**end of header + +@c This is *so* much nicer :) +@footnotestyle end + +@c Version values, for easy modification +@c NOTE: The 'UPDATED' value is updated by the 'time-stamp' function. +@c If you change it by hand, the modifications will not stay. +@set VERSION $Revision: 2.20 $ +@set UPDATED Friday, 14 June, 2002 + + +@c Entries for @command{install-info} to use +@direntry +* TRAMP: (tramp). Transparent Remote Access, Multiple Protocol + Emacs remote file access via rsh and rcp. +@end direntry + +@c Macro to make formatting of the tramp program name consistent. +@macro tramp +@sc{tramp} +@end macro + +@c Copying permissions, et al +@ifinfo +This file documents @tramp{}, a remote file editing package for Emacs and +XEmacs. + +Copyright @copyright{} 1999, 2000 Free Software Foundation, Inc. + +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + +@ignore +Permission is granted to process this file through TeX and print the +results, provided the printed document carries a copying permission +notice identical to this one except for the removal of this paragraph +(this paragraph not being relevant to the printed manual). + +@end ignore +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided also that the +sections entitled ``Copying'' and ``GNU General Public License'' are +included exactly as in the original, and provided that the entire +resulting derived work is distributed under the terms of a permission +notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that this permission notice may be stated in a translation +approved by the Free Software Foundation. +@end ifinfo + +@tex + +@titlepage +@title @tramp{} User Manual +@subtitle Last updated @value{UPDATED} + +@author by Daniel Pittman +@author based on documentation by Kai Gro@ss{}johann +@page + +@vskip 0pt plus 1filll +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided also that the +sections entitled ``Copying'' and ``GNU General Public License'' are +included exactly as in the original, and provided that the entire +resulting derived work is distributed under the terms of a permission +notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that this permission notice may be stated in a translation +approved by the Free Software Foundation. + +@end titlepage +@page + +@end tex + +@ifnottex +@node Top, Copying, (dir), (dir) +@top @tramp{} User Manual + +@tramp{} stands for `Transparent Remote (file) Access, Multiple +Protocol'. This package provides remote file editing, similar to +@cite{ange-ftp} and @cite{EFS}. + +The difference is that ange-ftp uses FTP to transfer files between the +local and the remote host, whereas @tramp{} uses a combination of +@command{rsh} and @command{rcp} or other work-alike programs, such as +@command{ssh}/@command{scp}. + +This is version @value{VERSION} of the @tramp{} manual, last updated on +@value{UPDATED}. + +You can find the latest version of this document on the web at +@uref{http://www.freesoftware.fsf.org/tramp/}. + +@ifhtml +This manual is also available as a @uref{tramp_ja.html, Japanese +translation}. + +The latest release of @tramp{} is available for +@uref{http://savannah.gnu.org/download/tramp/, +download}, or you may see @ref{Obtaining @tramp{}} for more details, +including the CVS server details. + +@tramp{} also has a @uref{https://savannah.gnu.org/projects/tramp/, +Savannah Project Page}. +@end ifhtml + +There is a mailing list for @tramp{}, available at +@email{tramp-devel@@mail.freesoftware.fsf.org}, and archived at +@uref{http://www.mail-archive.com/emacs-rcp@@ls6.cs.uni-dortmund.de/} as +well as the usual Savannah archives. + +@end ifnottex + +@menu +* Copying:: @tramp{} Copying conditions. +* Overview:: What @tramp{} can and cannot do. + +For the end user: +* Obtaining @tramp{}:: How to obtain @tramp{}. +* History:: History of @tramp{} +* Installation:: Installing @tramp{} with your (X)Emacs. +* Configuration:: Configuring @tramp{} for use. +* Usage:: An overview of the operation of @tramp{}. +* Bug Reports:: Reporting Bugs and Problems +* Frequently Asked Questions:: Questions and answers from the mailing list. + +For the developer: +* Version Control:: The inner workings of remote version control. +* Files directories and paths:: How file names, directories and paths are mangled and managed. +* Issues:: + +@detailmenu + --- The Detailed Node Listing --- + +Configuring @tramp{} for use + +* Connection types:: Types of connections made to remote machines. +* Inline methods:: Inline methods. +* External transfer methods:: External transfer methods. +* Multi-hop Methods:: Connecting to a remote host using multiple hops. +* Default Method:: Selecting a default method. +* Customizing Methods:: Using Non-Standard Methods. +* Remote Programs:: How @tramp{} finds and uses programs on the remote machine. +* Remote shell setup:: + +Using @tramp + +* Filename Syntax:: @tramp{} filename conventions. +* Multi-hop filename syntax:: Multi-hop filename conventions +* Dired:: Dired and filename completion. + +The inner workings of remote version control + +* Version Controlled Files:: Determining if a file is under version control. +* Remote Commands:: Executing the version control commands on the remote machine. +* Changed workfiles:: Detecting if the working file has changed. +* Checking out files:: Bringing the workfile out of the repository. +* Miscellaneous Version Control:: Things related to Version Control that don't fit elsewhere + +Things related to Version Control that don't fit elsewhere + +* Remote File Ownership:: How VC determines who owns a workfile. +* Back-end Versions:: How VC determines what release your RCS is. + +How file names, directories and paths are mangled and managed. + +* Path deconstruction:: Breaking a path into its components. + +@end detailmenu +@end menu + +@node Copying +@chapter @tramp{} Copying conditions + +Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + +tramp.el is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2, or (at your option) any later +version. + +tramp.el is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along +with GNU Emacs; see the file COPYING. If not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +USA. + + +@node Overview +@chapter An overview of @tramp + +After the installation of @tramp{} into your Emacs, you will be able to +access files on remote machines as though they were local. Access to the +remote file system for editing files, version control, and +@command{dired} are transparently enabled. + +Your access to the remote machine can be with the @command{rsh}, +@command{rlogin}, @command{telnet} programs or with any similar +connection method. This connection must pass ASCII successfully to be +usable but need not be 8-bit clean. + +The package provides support for @command{ssh} connections out of the +box, one of the more common uses of the package. This allows relatively +secure access to machines, especially if @command{ftp} access is +disabled. + +The majority of activity carried out by @tramp{} requires only that the +remote login is possible and is carried out at the terminal. In order to +access remote files @tramp{} needs to transfer their content to the local +machine temporarily. + +@tramp{} can transfer files between the machines in a variety of ways. The +details are easy to select, depending on your needs and the machines in +question. + +The fastest transfer methods rely on a remote file transfer package such +as @command{rcp}, @command{scp} or @command{rsync}. The use of these +methods is only possible if the file copy command does not ask for a +password for the remote machine. + +If the remote copy methods are not suitable for you, @tramp{} also +supports the use of encoded transfers directly through the shell. This +requires that the @command{mimencode} or @command{uuencode} tools are +available on the remote machine. + +Within these limitations, @tramp{} is quite powerful. It is worth noting +that, as of the time of writing, it is far from a polished end-user +product. For a while yet you should expect to run into rough edges and +problems with the code now and then. + +It is finished enough that the developers use it for day to day work but +the installation and setup can be a little difficult to master, as can +the terminology. + +@tramp{} is still under active development and any problems you encounter, +trivial or major, should be reported to the @tramp{} developers. +@xref{Bug Reports}. + + +@subsubheading Behind the scenes + +This section tries to explain what goes on behind the scenes when you +access a remote file through @tramp{}. + +Suppose you type @kbd{C-x C-f} and enter part of an @tramp{} file name, +then hit @kbd{@key{TAB}} for completion. Suppose further that this is +the first time that @tramp{} is invoked for the host in question. Here's +what happens: + +@itemize +@item +@tramp{} discovers that it needs a connection to the host. So it invokes +@command{telnet HOST} or @command{rsh HOST -l USER} or a similar tool to +connect to the remote host. Communication with this process happens +through an Emacs buffer, that is, the output from the remote end goes +into a buffer. + +@item +The remote host may prompt for a login name (for @command{telnet}). The +login name is given in the file name, so @tramp{} sends the login name and +a newline. + +@item +The remote host may prompt for a password or pass phrase (for +@command{rsh} or for @command{telnet} after sending the login name). +@tramp{} displays the prompt in the minibuffer, asking you for the +password or pass phrase. + +You enter the password or pass phrase. @tramp{} sends it to the remote +host, followed by a newline. + +@item +@tramp{} now waits for the shell prompt or for a message that the login +failed. + +If @tramp{} sees neither of them after a certain period of time (a minute, +say), then it issues an error message saying that it couldn't find the +remote shell prompt and shows you what the remote host has sent. + +If @tramp{} sees a `login failed' message, it tells you so, aborts the +login attempt and allows you to try again. + +@item +Suppose that the login was successful and @tramp{} sees the shell prompt +from the remote host. Now @tramp{} invokes @command{/bin/sh} because +Bourne shells and C shells have different command +syntaxes.@footnote{Invoking @command{/bin/sh} will fail if your login +shell doesn't recognize @command{exec /bin/sh} as a valid command. +Maybe you use the Scheme shell @command{scsh}@dots{}} + +After the Bourne shell has come up, @tramp{} sends a few commands to +ensure a good working environment. It turns off echoing, it sets the +shell prompt, and a few other things. + +@item +Now the remote shell is up and it good working order. Remember, what +was supposed to happen is that @tramp{} tries to find out what files exist +on the remote host so that it can do filename completion. + +So, @tramp{} basically issues @command{cd} and @command{ls} commands and +also sometimes @command{echo} with globbing. Another command that is +often used is @command{test} to find out whether a file is writable or a +directory or the like. The output of each command is parsed for the +necessary operation. + +@item +Suppose you are finished with filename completion, have entered @kbd{C-x +C-f}, a full file name and hit @kbd{@key{RET}}. Now comes the time to +transfer the file contents from the remote host to the local host so +that you can edit them. + +See above for an explanation of how @tramp{} transfers the file contents. + +For inline transfers, @tramp{} issues a command like @command{mimencode -b +/path/to/remote/file}, waits until the output has accumulated in the +buffer that's used for communication, then decodes that output to +produce the file contents. + +For out-of-band transfers, @tramp{} issues a command like @command{rcp +user@@host:/path/to/remote/file /tmp/tramp.4711} and then reads the local +temporary file @file{/tmp/tramp.4711} into a buffer and deletes the +temporary file. + +@item +You now edit the buffer contents, blithely unaware of what has happened +behind the scenes. (Unless you have read this section, that is.) When +you are finished, you type @kbd{C-x C-s} to save the buffer. + +@item +Again, @tramp{} transfers the file contents to the remote host either +inline or out-of-band. This is the reverse of what happens when reading +the file. + +@end itemize + +I hope this has provided you with a basic overview of what happens +behind the scenes when you open a file with @tramp{}. + + +@c For the end user +@node Obtaining @tramp{} +@chapter Obtaining @tramp{}. + +@tramp{} is freely available on the Internet and the latest release may be +downloaded from +@uref{ftp://ls6-ftp.cs.uni-dortmund.de/pub/src/emacs/tramp.tar.gz}. This +release includes the full documentation and code for @tramp{}, suitable +for installation. + +For the especially brave, @tramp{} is available from CVS. The CVS version +is the latest version of the code and may contain incomplete features or +new issues. Use these versions at your own risk. + +Instructions for obtaining the latest development version of @tramp{} +from CVS can be found by going to the Savannah project page at +@uref{http://savannah.gnu.org/projects/tramp/} and then clicking on the +CVS link in the navigation bar at the top. Or follow the example +session below: + +@example +] @strong{cd ~/lisp} +] @strong{cvs -d:pserver:anoncvs@@subversions.gnu.org:/cvsroot/tramp login} + +(Logging in to anoncvs@@subversions.gnu.org) +CVS password: @strong{(just hit RET here)} +@dots{} + +] @strong{cvs -z3 -d:pserver:anoncvs@@subversions.gnu.org:/cvsroot/tramp co tramp} +@end example + +You should now have a directory @file{~/lisp/tramp} containing the latest +version of @tramp{}. You can fetch the latest updates from the repository +by issuing the command: + +@example +] @strong{cd ~/lisp/tramp} +] @strong{cvs update -d} +@end example + + +@node History +@chapter History of @tramp{} + +Development was started end of November 1998. The package was called +`rssh.el', back then. It only provided one method to access a file, +using @command{ssh} to log in to a remote host and using @command{scp} +to transfer the file contents. After a while, the name was changed to +`rcp.el', and now it's @tramp{}. Along the way, many more methods for +getting a remote shell and for transferring the file contents were +added. Support for VC was added. + +The most recent addition of a major feature was the multi-hop methods +added in April 2000. + + +@node Installation +@chapter Installing @tramp{} into Emacs or XEmacs + +Installing @tramp{} into your Emacs or XEmacs is a relatively easy +process, at least compared to rebuilding your machine from scratch. ;) + +Seriously though, the installation should be a fairly simple matter. + +The easiest way to proceed is as follows: + +@itemize +@item +Choose a directory, say @file{~/emacs/}. Change into that directory and +unpack the tarball. This will give you a directory +@file{~/emacs/tramp/} which contains subdirectories @file{lisp} for the +Lisp code and @file{texi} for the documentation. + +@item +Optionally byte-compile all files in the Lisp directory, +@file{~/emacs/tramp/lisp/}, by issuing a command like the following from +the top level directory @file{~/emacs/tramp/}: +@example +make EMACS=emacs all # for Emacs users +make EMACS=xemacs all # for XEmacs users +@end example + +@item +NOTE: +@example +If you run into problems running the example @command{make} +commands, don't dispare. You can still byte compile the +@file{*.el} files by opening emacs in @command{dired} +(@command{C-x d}) mode, at @file{~/tramp/lisp}. Mark the lisp +files with @command{m}, then press @command{B} to byte compile +your selections. + +Something similar can be done to create the info manual. +Just cd to @file{~/emacs/tramp/texi} and load the @file{tramp.texi} +file in emacs. Then press @command{M-x makeinfo-buffer <RET>} +to generate @file{tramp.info}. +@end example + +@item +Tell Emacs about the new Lisp directory and the @tramp{} package +with the following lines in @file{~/.emacs}: +@lisp +(add-to-list 'load-path "~/emacs/tramp/lisp/") +(require 'tramp) +@end lisp + +@item +To be able to read the Info documentation, create a file +@file{~/emacs/tramp/texi/dir} using for example the +@command{install-info} command, and add the directory to the search +path for Info. + +@item +NOTE: +@example +On systems using `gnu' @command{install-info}, the +@command{install-info} syntax is very direct and simple. One can +cd to @file{~/emacs/tramp/texi} and type: + @command{install-info tramp.info dir} +and a @file{dir} file will be created with the @tramp{} +entry. The info reader will know how to interpret it, but must +be told where to find it (see below). If you want anything fancier +you'll need to look through @command{man install-info}. + +Debian gnu/linux doesn't default to `gnu' @command{install-info} and +uses its own version. This version does not create a @file{dir} file +for you from scratch. You must provide a skeleton dir file it +recognizes. One can be found in a default install at +@file{/usr/info/dir}. Copy the top of this file down to the first +occurrence of `* Menu' including that line plus one more blank line, +to your working directory @file{texi/dir}, or use the sample provided +in the @file{texi} directroy of this distribution. See +@file{texi/dir_sample} + +Once a @file{dir} file is in place, this command will make the entry. + install-info --infodir=. tramp.info +If you want it in a specific category + (see @command{man install-info} for further details) +@end example + +If the environment variable @env{INFOPATH} is set, add the directory +@file{~/emacs/tramp/texi/} to it. Else, add the directory to +@code{Info-default-directory-list}, as follows: +@lisp +(add-to-list 'Info-default-directory-list "~/emacs/tramp/texi/") +@end lisp +XEmacs 21 users should use @code{Info-directory-list} rather than +@code{Info-default-directory-list}. + +@end itemize + + +For XEmacs users, the package @command{fsf-compat} must be installed. +For details on package installation, see @ref{Packages, , ,xemacs}. +@ifhtml +(If the previous link doesn't work, try the XEmacs documentation at +@uref{http://www.xemacs.org/Documentation/packageGuide.html,the XEmacs +site}.) +@end ifhtml + +@node Configuration +@chapter Configuring @tramp{} for use + +@tramp{} is (normally) fully functional when it is initially +installed. It is initially configured to use the @command{rsh} and +@command{rcp} programs to connect to the remote host. + +On some hosts, there are problems with opening a connection. These are +related to the behavior of the remote shell. See @xref{Remote shell +setup}, for details on this. + +If you do not wish to use these commands to connect to the remote host, +you should change the default connection and transfer method that @tramp +uses. There are several different methods that @tramp{} can use to +connect to remote machines and transfer files (@pxref{Connection types}). + + +@menu +* Connection types:: Types of connections made to remote machines. +* Inline methods:: Inline methods. +* External transfer methods:: External transfer methods. +* Multi-hop Methods:: Connecting to a remote host using multiple hops. +* Default Method:: Selecting a default method. +* Customizing Methods:: Using Non-Standard Methods. +* Remote Programs:: How @tramp{} finds and uses programs on the remote machine. +* Remote shell setup:: Remote shell setup hints. +* Windows setup hints:: Issues with Cygwin ssh. +@end menu + + +@node Connection types +@section Types of connections made to remote machines. + +There are two basic types of transfer methods, each with its own +advantages and limitations. Both types of connection make use of a +remote shell access program such as @command{rsh}, @command{ssh} or +@command{telnet} to connect to the remote machine. + +This connection is used to perform many of the operations that @tramp +requires to make the remote file system transparently accessible from +the local machine. It is only when visiting files that the methods +differ. + +Loading or saving a remote file requires that the content of the file be +transfered between the two machines. The content of the file can be +transfered over the same connection used to log in to the remote machine +or the file can be transfered through another connection using a remote +copy program such as @command{rcp}, @command{scp} or @command{rsync}. +The former are called @dfn{inline methods}, the latter are called +@dfn{external transfer methods}. + +The performance of the external transfer methods is generally better +than that of the inline methods. This is caused by the need to encode +and decode the data when transferring inline. + +The one exception to this rule are the @command{scp} based transfer +methods. While these methods do see better performance when actually +transferring files, the overhead of the cryptographic negotiation at +startup may drown out the improvement in file transfer times. + +External transfer methods do require that the remote copy command is not +interactive --- that is, the command does not prompt you for a password. +If you cannot perform remote copies without a password, you will need to +use an inline transfer method to work with @tramp{}. + +A variant of the inline methods are the @dfn{multi-hop methods}. +These methods allow you to connect a remote host using a number `hops', +each of which connects to a different host. This is useful if you are +in a secured network where you need to go through a bastion host to +connect to the outside world. + + +@node Inline methods +@section Inline methods + +The inline methods in @tramp{} are quite powerful and can work in +situations where you cannot use an external transfer program to connect. +Inline methods are the only methods that work when connecting to the +remote machine via telnet. (There are also strange inline methods which +allow you to transfer files between @emph{user identities} rather than +hosts, see below.) + +These methods depend on the existence of a suitable encoding and +decoding command on remote machine. Locally, @tramp{} may be able to use +features of Emacs to decode and encode the files or it may require +access to external commands to perform that task. + +@tramp{} supports the use of @command{uuencode} to transfer files. This is +@emph{not} recommended. The @command{uuencode} and @command{uudecode} +commands are not well standardized and may not function correctly or at +all on some machines, notably AIX and IRIX. These systems do not work +with @command{uuencode} at all. (But do see the note about AIX in the +documentation for @var{tramp-methods}.) + +In summary, if possible use the @command{mimencode} methods to transfer +the data base64 encoded. This has the advantage of using a built-in +command in every modern Emacs, improving performance. + +@itemize +@item @option{rm} --- @command{rsh} with @command{mimencode} + +Connect to the remote host with @command{rsh} and use base64 encoding to +transfer files between the machines. + +This requires the @command{mimencode} command that is part of the +@command{metamail} packages. This may not be installed on all remote +machines. + + +@item @option{sm} --- @command{ssh} with @command{mimencode} + +Connect to the remote host with @command{ssh} and use base64 encoding to +transfer files between the machines. + +This is identical to the previous option except that the @command{ssh} +package is used, making the connection more secure. + +There are also two variants, @option{sm1} and @option{sm2} that use the +@command{ssh1} and @command{ssh2} commands explicitly. If you don't know +what these are, you do not need these options. + + +@item @option{tm} --- @command{telnet} with @command{mimencode} + +Connect to the remote host with @command{telnet} and use base64 encoding +to transfer files between the machines. + +This requires the @command{mimencode} command that is part of the +@command{metamail} packages. + + +@item @option{ru} --- @command{rsh} with @command{uuencode} + +Connect to the remote host with @command{rsh} and use the +@command{uuencode} and @command{uudecode} commands to transfer files +between the machines. + + +@item @option{su} --- @command{ssh} with @command{uuencode} + +Connect to the remote host with @command{ssh} and use the +@command{uuencode} and @command{uudecode} commands to transfer files +between the machines. + +As with the @command{ssh} and base64 option above, this provides the +@option{su1} and @option{su2} methods to explicitly select an ssh +version. + +Note that this method does not invoke the @command{su} program, see +below for methods which use that. + + +@item @option{tu} --- @command{telnet} with @command{uuencode} + +Connect to the remote host with @command{telnet} and use the +@command{uuencode} and @command{uudecode} commands to transfer files +between the machines. + + +@item @option{sum} --- @command{su} with @command{mimencode} + +This method does not connect to a remote host at all, rather it uses the +@command{su} program to allow you to edit files as another user. Uses +base64 encoding to transfer the file contents. + + +@item @option{suu} --- @command{su} with @command{uuencode} + +Like @option{sum}, this uses the @command{su} program to allow you to +edit files on the local host as another user. Uses @command{uuencode} +and @command{uudecode} to transfer the file contents. + + +@item @option{sudm} --- @command{sudo} with @command{mimencode} + +This is similar to the @option{sum} method, but it uses @command{sudo} +rather than @command{su} to become a different user. + +Note that @command{sudo} must be configured to allow you to start a +shell as the user. It would be nice if it was sufficient if +@command{ls} and @command{mimencode} were allowed, but that is not easy +to implement, so I haven't got around to it, yet. + + +@item @option{sudu} --- @command{sudo} with @command{uuencode} + +This is similar to the @option{suu} method, but it uses @command{sudo} +rather than @command{su} to become a different user. + + +@item @option{smx} --- @command{ssh} with @command{mimencode} + +As you expect, this is similar to @option{sm}, only a little +different. Whereas @option{sm} opens a normal interactive shell on +the remote host, this option uses @command{ssh -t -t HOST -l USER +/bin/sh} tp open a connection. This is useful for users where the +normal login shell is set up to ask them a number of questions when +logging in. This procedure avoids these questions, and just gives +@tramp{} a more-or-less `standard' login shell to work with. + +This is also useful for Windows users where @command{ssh}, when +invoked from an Emacs buffer, tells them that it is not allocating a +pseudo tty. When this happens, the login shell is wont to not print +any shell prompt, which confuses @tramp{} mightily. + + +@item @option{km} --- @command{krlogin} with @command{mimencode} + +This method is also similar to @option{sm}. It only uses the +@command{krlogin -x} command to log in to the remote host. + + +@item @option{plinku} --- @command{plink} with @command{uuencode} + +This method is mostly interesting for Windows users using the PuTTY +implementation of SSH. It uses @command{plink -ssh} to log in to the +remote host. + +CCC: Do we have to connect to the remote host once from the command +line to accept the SSH key? Maybe this can be made automatic? + +@item @option{plinkm} --- @command{plink} with @command{mimencode} + +Like @option{plinku}, but uses base64 encoding instead of uu encoding. + +@end itemize + + + +@node External transfer methods +@section External transfer methods + +The external transfer methods operate through multiple channels, using +the remote shell connection for many actions while delegating file +transfers to an external transfer utility. + +This saves the overhead of encoding and decoding that multiplexing the +transfer through the one connection has with the inline methods. + +If you want to use an external transfer method you @emph{must} be able +to execute the transfer utility to copy files to and from the remote +machine without any interaction. + +This means that you will need to use @command{ssh-agent} if you use the +@command{scp} program for transfers, or maybe your version of +@command{scp} accepts a password on the command line.@footnote{PuTTY's +@command{pscp} allows you to specify the password on the command line.} +If you use @command{rsync} via @command{ssh} then the same rule must +apply to that connection. + +If you cannot get @command{scp} to run without asking for a password but +would still like to use @command{ssh} to secure your connection, have a +look at the @command{ssh} based inline methods. + + +@itemize +@item @option{rcp} --- @command{rsh} and @command{rcp} + +This method uses the @command{rsh} and @command{rcp} commands to connect +to the remote machine and transfer files. This is probably the fastest +connection method available. + + +@item @option{scp} --- @command{ssh} and @command{scp} + +Using @command{ssh} to connect to the remote host and @command{scp} to +transfer files between the machines is the best method for securely +connecting to a remote machine and accessing files. + +The performance of this option is also quite good. It may be slower than +the inline methods when you often open and close small files however. +The cost of the cryptographic handshake at the start of an @command{scp} +session can begin to absorb the advantage that the lack of encoding and +decoding presents. + + +@item @option{rsync} --- @command{ssh} and @command{rsync} + +Using the @command{ssh} command to connect securely to the remote +machine and the @command{rsync} command to transfer files is almost +identical to the @option{scp} method. + +While @command{rsync} performs much better than @command{scp} when +transferring files that exist on both hosts, this advantage is lost if +the file exists only on one side of the connection. + +The @command{rsync} based method may be considerably faster than the +@command{rcp} based methods when writing to the remote system. Reading +files to the local machine is no faster than with a direct copy. + + +@item @option{scpx} --- @command{ssh} and @command{scp} + +As you expect, this is similar to @option{scp}, only a little +different. Whereas @option{scp} opens a normal interactive shell on the +remote host, this option uses @command{ssh -t -t HOST -l USER /bin/sh} to +open a connection. This is useful for users where the normal login +shell is set up to ask them a number of questions when logging in. This +procedure avoids these questions, and just gives @tramp{} a more-or-less +`standard' login shell to work with. + +This is also useful for Windows users where @command{ssh}, when +invoked from an Emacs buffer, tells them that it is not allocating a +pseudo tty. When this happens, the login shell is wont to not print +any shell prompt, which confuses @tramp{} mightily. + + +@item @option{pscp} --- @command{plink} and @command{pscp} + +This method is similar to @option{scp}, but it uses the +@command{plink} command to connect to the remote host, and it uses +@command{pscp} for transferring the files. These programs are part +of PuTTY, an SSH implementation for Windows. + + +@item @option{fcp} --- @command{fsh} and @command{fcp} + +This method is similar to @option{scp}, but it uses the @command{fsh} +command to connect to the remote host, and it uses @command{fcp} for +transferring the files. @command{fsh/fcp} are a front-end for +@command{ssh} which allow for reusing the same @command{ssh} session +for submitting several commands. This avoids the startup overhead of +@command{scp} (which has to establish a secure connection whenever it +is called). Note, however, that you can also use one of the inline +methods to achieve a similar effect. + +This method uses the command @command{fsh HOST -l USER /bin/sh -i} to +establish the connection, it does not work to just say @command{fsh +HOST -l USER}. + +@end itemize + +@node Multi-hop Methods +@section Connecting to a remote host using multiple hops + +Sometimes, the methods described before are not sufficient. Sometimes, +it is not possible to connect to a remote host using a simple command. +For example, if you are in a secured network, you might have to log in +to a `bastion host' first before you can connect to the outside world. +Of course, the target host may also require a bastion host. The format +of multi-hop filenames is slightly different than the format of normal +@tramp{} methods. + +A multi-hop file name specifies a method, a number of hops, and a path +name on the remote system. The method specifies how the file is +transferred through the inline connection. The following two multi-hop +methods are available: + +@itemize +@item @option{multi} --- base64 encoding with @command{mimencode} + +The file is transferred through the connection in base64 encoding. Uses +the @command{mimencode} program for doing encoding and decoding, but +uses an Emacs internal implementation on the local host if available. + +@item @option{multiu} --- use commands @command{uuencode} and @command{uudecode} + +The file is transferred through the connection in `uu' encoding. Uses +the @command{uuencode} and @command{uudecode} programs for encoding and +decoding, but uses a Lisp implementation for decoding on the local host +if available. + +@end itemize + +Each hop consists of a @dfn{hop method} specification, a user name and a +host name. The following hop methods are (currently) available: + +@itemize +@item @option{telnet} + +Uses the well-known @command{telnet} program to connect to the host. +Whereas user name and host name are supplied in the file name, the +user is queried for the password. + +@item @option{rsh} + +This uses @command{rsh} to connect to the host. You do not need to +enter a password unless @command{rsh} explicitly asks for it. + +@item @option{ssh} + +This uses @command{ssh} to connect to the host. You might have to enter +a password or a pass phrase. + +@item @option{su} + +This method does not actually contact a different host, but it allows +you to become a different user on the host you're currently on. This +might be useful if you want to edit files as root, but the remote host +does not allow remote root logins. In this case you can use +@option{telnet}, @option{rsh} or @option{ssh} to connect to the +remote host as a non-root user, then use an @option{su} hop to become +root. But @option{su} need not be the last hop in a sequence, you could +also use it somewhere in the middle, if the need arises. + +Even though you @emph{must} specify both user and host with a +@option{su} hop, the host name is ignored and only the user name is +used. + +@item @option{sudo} + +This is similar to the @option{su} hop, except that it uses +@command{sudo} rather than @command{su} to become a different user. + +@end itemize + +Some people might wish to use port forwarding with @code{ssh} or maybe +they have to use a nonstandard port. This can be accomplished by +putting a stanza in @file{~/.ssh/config} for the account which specifies +a different port number for a certain host name. But it can also be +accomplished within Tramp, by adding a multi-hop method. For example: + +@lisp +(add-to-list 'tramp-multi-connection-function-alist + '("sshf" tramp-multi-connect-rlogin "ssh %h -l %u -p 4400%n")) +@end lisp + +Now you can use a @code{sshf} hop which connects to port 4400 instead of +the standard port. + + +@node Default Method +@section Selecting a default method + +When you select an appropriate transfer method for your typical usage +you should set the variable @var{tramp-default-method} to reflect that +choice. This variable controls which method will be used when a method +is not specified in the @tramp{} file path. For example: + +@lisp +(setq tramp-default-method "scp") +@end lisp + +External transfer methods are normally preferable to inline transfer +methods, giving better performance. They may not be useful if you use +many remote machines where you cannot log in without a password. + +@xref{Inline methods}. +@xref{External transfer methods}. +@xref{Multi-hop Methods}. + +Another consideration with the selection of transfer methods is the +environment you will use them in and, especially when used over the +Internet, the security implications of your preferred method. + +The @command{rsh} and @command{telnet} methods send your password as +plain text as you log in to the remote machine, as well as transferring +the files in such a way that the content can easily be read from other +machines. + +If you need to connect to remote systems that are accessible from the +Internet, you should give serious thought to using @command{ssh} based +methods to connect. These provide a much higher level of security, +making it a non-trivial exercise for someone to obtain your password or +read the content of the files you are editing. + +@node Customizing Methods +@section Using Non-Standard Methods + +There is a variable @code{tramp-methods} which you can change if the +predefined methods don't seem right. + +For the time being, I'll refer you to the Lisp documentation of that +variable, accessible with @kbd{C-h v tramp-methods @key{RET}}. + + +@node Remote Programs +@section How @tramp{} finds and uses programs on the remote machine. + +@tramp{} depends on a number of programs on the remote host in order to +function, including @command{ls}, @command{test}, @command{find} and +@command{cat}. + +In addition to these required tools, there are various tools that may be +required based on the connection method. See @ref{Inline methods} and +@ref{External transfer methods} for details on these. + +Certain other tools, such as @command{perl} (or @command{perl5}) and +@command{grep} will be used if they can be found. When they are +available, they are used to improve the performance and accuracy of +remote file access. + +When @tramp{} connects to the remote machine, it searches for the +programs that it can use. The variable @var{tramp-remote-path} controls +the directories searched on the remote machine. + +By default, this is set to a reasonable set of defaults for most +machines. It is possible, however, that your local (or remote ;) system +administrator has put the tools you want in some obscure local +directory. + +In this case, you can still use them with @tramp{}. You simply need to +add code to your @file{.emacs} to add the directory to the remote path. +This will then be searched by @tramp{} when you connect and the software +found. + +To add a directory to the remote search path, you could use code such +as: + +@example +(require 'tramp) @i{; @tramp{} must be loaded before this} + @i{; happens.} + +@i{; We have @command{perl} in "/usr/local/perl"} +(add-to-list 'tramp-remote-path "/usr/local/perl") +@end example + +@node Remote shell setup +@comment node-name, next, previous, up +@section Remote shell setup hints + +As explained in the @ref{Overview} section, @tramp{} connects to the +remote host and talks to the shell it finds there. Of course, when you +log in, the shell executes its init files. Suppose your init file +requires you to enter the birthdate of your mother; clearly @tramp{} +does not know this and hence fails to log you in to that host. + +There are different possible strategies for pursuing this problem. One +strategy is to enable @tramp{} to deal with all possible situations. +This is a losing battle, since it is not possible to deal with +@emph{all} situations. The other strategy is to require you to set up +the remote host such that it behaves like @tramp{} expect. This might +be inconvenient because you have to invest a lot of effort into shell +setup before you can begin to use @tramp{}. + +The package, therefore, pursues a combined approach. It tries to figure +out some of the more common setups, and only requires you to avoid +really exotic stuff. For example, it looks through a list of +directories to find some programs on the remote host. And also, it +knows that it is not obvious how to check whether a file exist, and +therefore it tries different possibilities. (On some hosts and shells, +the command @code{test -e} does the trick, on some hosts the shell +builtin doesn't work but the program @code{/usr/bin/test -e} or +@code{/bin/test -e} works. And on still other hosts, @code{ls -d} is +the right way to do this.) + +Below you find a discussion of a few things that @tramp{} does not deal +with, and that you therefore have to set up correctly. + +@itemize +@item @code{shell-prompt-pattern} + +@vindex shell-prompt-pattern +After logging in to the remote host, @tramp{} has to wait for the remote +shell startup to finish before it can send commands to the remote +shell. The strategy here is to wait for the shell prompt. In order to +recognize the shell prompt, the variable @code{shell-prompt-pattern} has +to be set correctly to recognize the shell prompt on the remote host. + +@item @code{tset} and other questions + +Some people invoke the @code{tset} program from their shell startup +scripts which asks the user about the terminal type of the shell. Maybe +some shells ask other questions when they are started. @tramp{} does +not know how to answer these questions. (A facility for enabling +@tramp{} to answer these questions is planned for some future version, +but don't hold your breath.) + +Therefore, you should take care that the shell does not ask any +questions when invoked from @tramp{}. You can do this by checking the +@code{TERM} environment variable, it will be set to @code{dumb} when +connecting. + +@vindex tramp-terminal-type +The variable @code{tramp-terminal-type} can be used to change this value +@code{dumb}. + +@end itemize + + +@node Windows setup hints +@section Issues with Cygwin ssh + +This section needs a lot of work! Please help. + +If you use the Cygwin installation of ssh (you have to explicitly select +it in the installer), then it should work out of the box to just select +@code{smx} as the connection method. You can find information about +setting up Cygwin in their FAQ at @uref{http://cygwin.com/faq/}. + + +@node Usage +@chapter Using @tramp + +Once you have installed @tramp{} it will operate fairly transparently. You +will be able to access files on any remote machine that you can log in +to as though they were local. + +Files are specified to @tramp{} using a formalized syntax specifying the +details of the system to connect to. This is similar to the syntax used +by the @command{EFS} and @command{ange-ftp} packages. + + +@menu +* Filename Syntax:: @tramp{} filename conventions. +* Multi-hop filename syntax:: Multi-hop filename conventions +* Dired:: Dired and filename completion. +@end menu + + +@node Filename Syntax +@section @tramp{} filename conventions + +To access the file <path> on the remote machine <machine> you would +specify the filename @file{/[<machine>]<path>}. (The square brackets +are part of the file name.) This will connect to <machine> and transfer +the file using the default method. @xref{Default Method}. + +Some examples of @tramp{} filenames are: + +@table @file +@item /[melancholia].emacs +Edit the file @file{.emacs} in your home directory on the machine +@code{melancholia}. + +@item /[melancholia.danann.net].emacs +This edits the same file, using the fully qualified domain name of +the machine. + +@item /[melancholia]~/.emacs +This also edits the same file --- the @file{~} is expanded to your +home directory on the remote machine, just like it is locally. + +@item /[melancholia]~daniel/.emacs +This edits the file @file{.emacs} in the home directory of the user +@code{daniel} on the machine @code{melancholia}. The @file{~<user>} +construct is expanded to the home directory of that user on the remote +machine. + +@item /[melancholia]/etc/squid.conf +This edits the file @file{/etc/squid.conf} on the machine +@code{melancholia}. + +@end table + + +Unless you specify a different name to use, @tramp{} will use the current +local user name as the remote user name to log in with. If you need to +log in as a different user, you can specify the user name as part of the +filename. + +To log in to the remote machine as a specific user, you use the syntax +@file{/[<user>@@<machine>]/path/to.file}. That means that connecting to +@code{melancholia} as @code{daniel} and editing @file{.emacs} in your +home directory you would specify @file{/[daniel@@melancholia].emacs}. + + +It is also possible to specify other file transfer methods +(@pxref{Default Method}) as part of the filename. This is done by +replacing the initial @file{/[} with @file{/[<method>/}. (Note the +trailing slash!) The user, machine and file specification remain the +same. + +So, to connect to the machine @code{melancholia} as @code{daniel}, using +the @option{su} method to transfer files, and edit @file{.emacs} in my +home directory I would specify the filename +@file{/[su/daniel@@melancholia].emacs}. + + +@node Multi-hop filename syntax +@section Multi-hop filename conventions + +The syntax of multi-hop file names is necessarily slightly different +than the syntax of other @tramp{} file names. Here's an example multi-hop +file name: + +@file{/[multi/rsh:out@@gate/telnet:kai@@real.host]/path/to.file} + +This is quite a mouthful. So let's go through it step by step. The +file name consists of three parts, separated by slashes and square +brackets. The first part is @file{/[multi}, the method specification. +The second part is @file{rsh:out@@gate/telnet:kai@@real.host} and +specifies the hops. (Yes, the second part may contain even more +slashes, so that's why this file name has more than two colons in it.) +The final part is @file{/path/to.file} and specifies the file name on +the remote host. + +The first part and the final part should be clear. @ref{Multi-hop +Methods}, for a list of alternatives for the method specification. + +The second part can be subdivided again into components, so-called hops. +In the above file name, there are two hops, @file{rsh:out@@gate} and +@file{telnet:kai@@real.host}. + +Each hop can @emph{again} be subdivided into (three) components, the +@dfn{hop method}, the @dfn{user name} and the @dfn{host name}. The +meaning of the second and third component should be clear, and the hop +method says what program to use to perform that hop. + +The first hop, @file{rsh:out@@gate}, says to use @command{rsh} to log in +as user @code{out} to the host @code{gate}. Starting at that host, the +second hop, @file{telnet:kai@@real.host}, says to use @command{telnet} +to log in as user @code{kai} to host @code{real.host}. + +@xref{Multi-hop Methods}, for a list of possible hop method values. The +variable @var{tramp-multi-connection-function-alist} contains the list of +possible hop methods and information on how to execute them, should you +want to add your own. + + +@node Dired +@section Dired and filename completion + +@tramp{} works transparently with dired, enabling you to use this powerful +file management tool to manage files on any machine you have access to +over the Internet. + +Filename completion also works with @tramp{} for files on remote machines +although there is no completion for user names or machine names at this +stage. + +As filename completion needs to fetch the listing of files from the +remote machine, this feature is sometimes fairly slow. As @tramp{} does not +yet cache the results of directory listing, there is no gain in +performance the second time you complete filenames. + +If you need to browse a directory tree, Dired is a better choice, at +present, than filename completion. Dired has its own cache mechanism +and will only fetch the directory listing once. + + +@node Bug Reports +@chapter Reporting Bugs and Problems + +Bugs and problems with @tramp{} are actively worked on by the development +team. Feature requests and suggestions are also more than welcome. + +The @tramp{} mailing list is a great place to get information on working +with @tramp{}, solving problems and general discussion and advice on topics +relating to the package. + +The mailing list is at @email{tramp-devel@@mail.freesoftware.fsf.org}. +Messages sent to this address go to all the subscribers. This is +@emph{not} the address to send subscription requests to. + +For help on subscribing to the list, send mail to the administrative +address, @email{tramp-devel-request@@mail.freesoftware.fsf.org}, with the +subject @samp{help}. + +To report a bug in @tramp{}, you should execute @kbd{M-x tramp-bug}. This +will automatically generate a buffer with the details of your system and +@tramp{} version. + +When submitting a bug report, please try to describe in excruciating +detail the steps required to reproduce the problem, the setup of the +remote machine and any special conditions that exist. + +If you can identify a minimal test case that reproduces the problem, +include that with your bug report. This will make it much easier for the +development team to analyze and correct the problem. + +@node Frequently Asked Questions +@chapter Frequently Asked Questions + +@itemize @bullet +@item Where can I get the latest @tramp{}? + +@tramp{} is available at +@uref{ftp://ls6-ftp.cs.uni-dortmund.de/pub/src/emacs/tramp.tar.gz}. +There is also a Savannah project page, at +@uref{https://savannah.gnu.org/projects/tramp/}. + + +@item Which systems does it work on? + +The package has been used successfully on Emacs 20 and Emacs 21, as well +as XEmacs 21. XEmacs 20 is more problematic, see the notes in +@file{tramp.el}. I don't think anybody has really tried it on Emacs 19. + +The package was intended to work on Unix, and it really expects a +Unix-like system on the remote end, but some people seemed to have some +success getting it to work on NT Emacs. + +There are some informations on Tramp on NT at the following URL; many +thanks to Joe Stoy for providing the information: +@uref{ftp://ftp.comlab.ox.ac.uk/tmp/Joe.Stoy/} + +The above mostly contains patches to old ssh versions; Tom Roche has a +Web page with instructions: +@uref{http://www4.ncsu.edu/~tlroche/plinkTramp.html} + +??? Is the XEmacs info correct? + +??? Can somebody provide some information for getting it to work on NT +Emacs? I think there was some issue with @command{ssh}? + + +@item I can't stop EFS starting with XEmacs + +Not all the older versions of @tramp{} supported XEmacs correctly. The +first thing to do is to make sure that you have the latest version of +@tramp{} installed. + +If you do, please try and find out exactly the conditions required for +the @code{EFS} handlers to fire. If you can, putting a breakpoint on +@code{efs-ftp-path} and sending in the stack trace along with your bug +report would make it easier for the developers to work out what is going +wrong. + + +@item File name completion does not work with @tramp{} + +When you log in to the remote machine, do you see the output of +@command{ls} in color? If so, this may be the cause of your problems. + +@command{ls} outputs @acronym{ANSI} escape sequences that your terminal +emulator interprets to set the colors. These escape sequences will +confuse @tramp{} however. + +In your @file{.bashrc}, @file{.profile} or equivalent on the remote +machine you probably have an alias configured that adds the option +@option{--color=yes} or @option{--color=auto}. + +You should remove that alias and ensure that a new login @emph{does not} +display the output of @command{ls} in color. If you still cannot use +filename completion, report a bug to the @tramp{} developers. + + +@item File name completion does not work in large directories + +@tramp{} uses globbing for some operations. (Globbing means to use the +shell to expand wildcards such as `*.c'.) This might create long +command lines, especially in directories with many files. Some shell +choke on long command lines, or don't cope well with the globbing +itself. + +If you have a large directory on the remote end, you may wish to execute +a command like @command{ls -d * ..?* > /dev/null} and see if it hangs. +Note that you must first start the right shell, which might be +@command{/bin/sh}, @command{ksh} or @command{bash}, depending on which +of those supports tilde expansion. + + +@item What kinds of systems does @tramp{} work on + +@tramp{} really expects the remote system to be a Unix-like system. The +local system should preferably be Unix-like, as well, but @tramp{} might +work on NT with some tweaking. + + +@item How can I get notified when @tramp{} file transfers are complete? + +The following snippet can be put in your @file{~/.emacs} file. It makes +Emacs beep after reading from or writing to the remote host. + +@lisp +(defadvice tramp-handle-write-region + (after tramp-write-beep-advice activate) + " make tramp beep after writing a file." + (interactive) + (beep)) +(defadvice tramp-handle-do-copy-or-rename-file + (after tramp-copy-beep-advice activate) + " make tramp beep after copying a file." + (interactive) + (beep)) +(defadvice tramp-handle-insert-file-contents + (after tramp-copy-beep-advice activate) + " make tramp beep after copying a file." + (interactive) + (beep)) +@end lisp + + +@item There's this @file{~/.sh_history} file on the remote host which + keeps growing and growing. What's that? + +Sometimes, @tramp{} starts @code{ksh} on the remote host for tilde +expansion. Maybe @code{ksh} saves the history by default. @tramp{} +tries to turn off saving the history, but maybe you have to help. For +example, you could put this in your @file{.kshrc}: + +@example +if [ -f $HOME/.sh_history ] ; then + /bin/rm $HOME/.sh_history +fi +if [ "$@{HISTFILE-unset@}" != "unset" ] ; then + unset HISTFILE +fi +if [ "$@{HISTSIZE-unset@}" != "unset" ] ; then + unset HISTSIZE +fi +@end example + +@end itemize + + +@c For the developer +@node Version Control +@chapter The inner workings of remote version control + +Unlike EFS and ange-ftp, @tramp{} has full shell access to the remote +machine. This makes it possible to provide version control for files +accessed under @tramp{}. + +The actual version control binaries must be installed on the remote +machine, accessible in the directories specified in +@var{tramp-remote-path}. + +This transparent integration with the version control systems is one of +the most valuable features provided by @tramp{}, but it is far from perfect. +Work is ongoing to improve the transparency of the system. + +@menu +* Version Controlled Files:: Determining if a file is under version control. +* Remote Commands:: Executing the version control commands on the remote machine. +* Changed workfiles:: Detecting if the working file has changed. +* Checking out files:: Bringing the workfile out of the repository. +* Miscellaneous Version Control:: Things related to Version Control that don't fit elsewhere +@end menu + + +@node Version Controlled Files +@section Determining if a file is under version control + +The VC package uses the existence of on-disk revision control master +files to determine if a given file is under revision control. These file +tests happen on the remote machine through the standard @tramp{} mechanisms. + + +@node Remote Commands +@section Executing the version control commands on the remote machine + +There are no hooks provided by VC to allow intercepting of the version +control command execution. The calls occur through the +@code{call-process} mechanism, a function that is somewhat more +efficient than the @code{shell-command} function but that does not +provide hooks for remote execution of commands. + +To work around this, the functions @code{vc-do-command} and +@code{vc-simple-command} have been advised to intercept requests for +operations on files accessed via @tramp{}. + +In the case of a remote file, the @code{shell-command} interface is +used, with some wrapper code, to provide the same functionality on the +remote machine as would be seen on the local machine. + + +@node Changed workfiles +@section Detecting if the working file has changed + +As there is currently no way to get access to the mtime of a file on a +remote machine in a portable way, the @code{vc-workfile-unchanged-p} +function is advised to call an @tramp{} specific function for remote files. + +The @code{tramp-vc-workfile-unchanged-p} function uses the functioning VC +diff functionality to determine if any changes have occurred between the +workfile and the version control master. + +This requires that a shell command be executed remotely, a process that +is notably heavier-weight than the mtime comparison used for local +files. Unfortunately, unless a portable solution to the issue is found, +this will remain the cost of remote version control. + + +@node Checking out files +@section Bringing the workfile out of the repository + +VC will, by default, check for remote files and refuse to act on them +when checking out files from the repository. To work around this +problem, the function @code{vc-checkout} knows about @tramp{} files and +allows version control to occur. + + +@node Miscellaneous Version Control +@section Things related to Version Control that don't fit elsewhere + +Minor implementation details, &c. + +@menu +* Remote File Ownership:: How VC determines who owns a workfile. +* Back-end Versions:: How VC determines what release your RCS is. +@end menu + + +@node Remote File Ownership +@subsection How VC determines who owns a workfile + +Emacs provides the @code{user-full-name} function to return the login name +of the current user as well as mapping from arbitrary user id values +back to login names. The VC code uses this functionality to map from the +uid of the owner of a workfile to the login name in some circumstances. + +This will not, for obvious reasons, work if the remote system has a +different set of logins. As such, it is necessary to delegate to the +remote machine the job of determining the login name associated with a +uid. + +Unfortunately, with the profusion of distributed management systems such +as @code{NIS}, @code{NIS+} and @code{NetInfo}, there is no simple, +reliable and portable method for performing this mapping. + +Thankfully, the only place in the VC code that depends on the mapping of +a uid to a login name is the @code{vc-file-owner} function. This returns +the login of the owner of the file as a string. + +This function has been advised to use the output of @command{ls} on the +remote machine to determine the login name, delegating the problem of +mapping the uid to the login to the remote system which should know more +about it than I do. + + +@node Back-end Versions +@subsection How VC determines what release your RCS is + +VC needs to know what release your revision control binaries you are +running as not all features VC supports are available with older +versions of @command{rcs(1)}, @command{cvs(1)} or @command{sccs(1)}. + +The default implementation of VC determines this value the first time it +is needed and then stores the value globally to avoid the overhead of +executing a process and parsing its output each time the information is +needed. + +Unfortunately, life is not quite so easy when remote version control +comes into the picture. Each remote machine may have a different version +of the version control tools and, while this is painful, we need to +ensure that unavailable features are not used remotely. + +To resolve this issue, @tramp{} currently takes the sledgehammer +approach of making the release values of the revision control tools +local to each @tramp{} buffer, forcing VC to determine these values +again each time a new file is visited. + +This has, quite obviously, some performance implications. Thankfully, +most of the common operations performed by VC do not actually require +that the remote version be known. This makes the problem far less +apparent. + +Eventually these values will be captured by @tramp{} on a system by +system basis and the results cached to improve performance. + + +@node Files directories and paths +@chapter How file names, directories and paths are mangled and managed. + +@menu +* Path deconstruction:: Breaking a path into its components. +@end menu + + +@node Path deconstruction +@section Breaking a path into its components. + +@tramp{} filenames are somewhat different, obviously, to ordinary path +names. As such, the lisp functions @code{file-name-directory} and +@code{file-name-nondirectory} are overridden within the @tramp{} package. + +Their replacements are reasonably simplistic in their approach. They +dissect the filename, call the original handler on the remote path and +then rebuild the @tramp{} path with the result. + +This allows the platform specific hacks in the original handlers to take +effect while preserving the @tramp{} path information. + + +@node Issues +@chapter Debatable Issues and What Was Decided + +@itemize @bullet +@item The uuencode method does not always work. + +Due to the design of @tramp{}, the encoding and decoding programs need to +read from stdin and write to stdout. On some systems, @code{uudecode -o +-} will read stdin and write the decoded file to stdout, on other +systems @code{uudecode -p} does the same thing. But some systems have +uudecode implementations which cannot do this at all---it is not +possible to call these uudecode implementations with suitable parameters +so that they write to stdout. + +Of course, this could be circumvented: the @code{begin foo 644} line +could be rewritten to put in some temporary file name, then +@code{uudecode} could be called, then the temp file could be printed and +deleted. + +But I have decided that this is too fragile to reliably work, so on some +systems you'll have to do without the uuencode methods. + +@item @tramp{} does not work on XEmacs 20. + +This is because it requires the macro @code{with-timeout} which does not +appear to exist in XEmacs 20. I'm somewhat reluctant to add an +emulation macro to @tramp{}, but if somebody who uses XEmacs 20 steps +forward and wishes to implement and test it, please contact me or the +mailing list. + +@end itemize + + +@c End of tramp.texi - the TRAMP User Manual +@bye + +@c TODO +@c +@c * Say something about the .login and .profile files of the remote +@c shells. +@c * Explain how tramp.el works in principle: open a shell on a remote +@c host and then send commands to it. + +@c Local Variables: +@c eval: (add-hook 'write-file-hooks 'time-stamp) +@c time-stamp-start: "@set UPDATED " +@c time-stamp-format: "%:a, %:d %:b, %:y" +@c time-stamp-end: "$" +@c time-stamp-line-limit: 50 +@c End: