From: Glenn Morris Date: Sat, 15 Jan 2011 20:03:38 +0000 (-0800) Subject: Merge from emacs-23 branch. X-Git-Tag: emacs-pretest-24.0.90~104^2~618^2~1322^2~230^2~17 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=362b9d483c714a8fd87966ddbd8686850f870e34;p=emacs.git Merge from emacs-23 branch. --- 362b9d483c714a8fd87966ddbd8686850f870e34 diff --cc doc/emacs/ChangeLog index c2d058119a4,a374272135f..fa7b38e8bcb --- a/doc/emacs/ChangeLog +++ b/doc/emacs/ChangeLog @@@ -1,4 -1,4 +1,15 @@@ -2010-12-30 Chong Yidong ++2011-01-15 Chong Yidong ++ ++ * files.texi (File Aliases): Move directory-abbrev-alist doc from Lisp ++ manual. Explain why directory-abbrev-alist elements should be anchored ++ (Bug#7777). ++ ++2011-01-15 Eli Zaretskii ++ ++ * msdog.texi (Windows Startup): Correct inaccurate description of ++ differences between emacsclient.exe and emacsclientw.exe. ++ +2011-01-02 Chong Yidong * rmail.texi (Rmail Display): Edit for grammar and conciseness. diff --cc doc/emacs/files.texi index 0df3769cd8b,4df81caa8f2..c869e19afe7 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@@ -1160,7 -1158,7 +1160,6 @@@ links point to directories @vindex find-file-existing-other-name @vindex find-file-suppress-same-file-warnings -- Normally, if you visit a file which Emacs is already visiting under a different name, Emacs displays a message in the echo area and uses the existing buffer visiting that file. This can happen on systems @@@ -1182,6 -1180,6 +1181,31 @@@ then the file name recorded for a buffe than the name you specify. Setting @code{find-file-visit-truename} also implies the effect of @code{find-file-existing-other-name}. ++@cindex directory name abbreviation ++@vindex directory-abbrev-alist ++ Sometimes, a directory is ordinarily accessed through a symbolic ++link, and you may want Emacs to preferentially display its ``linked'' ++name instead of its truename. To do this, customize the variable ++@code{directory-abbrev-alist}. Each element in this list should have ++the form @code{(@var{from} . @var{to})}, which says to replace ++@var{from} with @var{to} when it appears in a directory name. For ++this feature to work properly, @var{from} and @var{to} should point to ++the same file. The @var{from} string is actually a regular expression ++(@pxref{Regexps}); it should always start with @samp{\`}, to avoid ++matching to an incorrect part of the original directory name. The ++@var{to} string should be an ordinary absolute directory name. Do not ++use @samp{~} to stand for a home directory in the @var{to} string; ++Emacs performs these substitutions separately. ++ ++ Here's an example, from a system on which file system ++@file{/home/fsf} and so on are normally accessed through symbolic ++links named @file{/fsf} and so on. ++ ++@example ++(("\\`/home/fsf" . "/fsf") ++ ("\\`/home/gd" . "/gd")) ++@end example ++ @node Directories @section File Directories diff --cc doc/emacs/msdog.texi index 0c0dcb3f4af,514ef956dc3..306cb213374 --- a/doc/emacs/msdog.texi +++ b/doc/emacs/msdog.texi @@@ -90,20 -90,20 +90,24 @@@ Via the Emacs client program, @file{ema programs, and to reuse a running Emacs process for serving editing jobs required by other programs. @xref{Emacs Server}. The difference between @file{emacsclient.exe} and @file{emacsclientw.exe} is that the --former waits for Emacs to signal that the editing job is finished, --while the latter does not wait. Which one of them to use in each case --depends on the expectations of the program that needs editing --services. If the program will use the edited files, it needs to wait --for Emacs, so you should use @file{emacsclient.exe}. By contrast, if --the results of editing are not needed by the invoking program, you --will be better off using @file{emacsclientw.exe}. A notable situation --where you would want @file{emacsclientw.exe} is when you right-click --on a file in the Windows Explorer and select ``Open With'' from the --pop-up menu. Use the @samp{--alternate-editor=} or @samp{-a} options --if Emacs might not be running (or not running as a server) when --@command{emacsclient} is invoked---that will always give you an --editor. When invoked via @command{emacsclient}, Emacs will start in --the current directory of the program that invoked ++former is a console program, while the latter is a Windows GUI ++program. Both programs wait for Emacs to signal that the editing job ++is finished, before they exit and return control to the program that ++invoked them. Which one of them to use in each case depends on the ++expectations of the program that needs editing services. If that ++program is itself a console (text-mode) program, you should use ++@file{emacsclient.exe}, so that any of its messages and prompts appear ++in the same command window as those of the invoking program. By ++contrast, if the invoking program is a GUI program, you will be better ++off using @file{emacsclientw.exe}, because @file{emacsclient.exe} will ++pop up a command window if it is invoked from a GUI program. A ++notable situation where you would want @file{emacsclientw.exe} is when ++you right-click on a file in the Windows Explorer and select ``Open ++With'' from the pop-up menu. Use the @samp{--alternate-editor=} or ++@samp{-a} options if Emacs might not be running (or not running as a ++server) when @command{emacsclient} is invoked---that will always give ++you an editor. When invoked via @command{emacsclient}, Emacs will ++start in the current directory of the program that invoked @command{emacsclient}. @end enumerate diff --cc doc/lispref/ChangeLog index 6a51b3f506b,1cff2853a27..40481acec12 --- a/doc/lispref/ChangeLog +++ b/doc/lispref/ChangeLog @@@ -1,8 -1,4 +1,18 @@@ -2010-12-25 Eli Zaretskii ++2011-01-15 Chong Yidong ++ ++ * files.texi (Directory Names): Move directory-abbrev-alist doc to ++ Emacs manual. ++ ++2011-01-15 Eli Zaretskii ++ ++ * files.texi (Directory Names): Explain why FROM in ++ directory-abbrev-alist should begin with \`. (Bug#7777) ++ +2011-01-11 Stefan Monnier + + * loading.texi (Hooks for Loading): Adjust doc of eval-after-load. + +2011-01-02 Eli Zaretskii * modes.texi (Emulating Mode Line): Fix last change. diff --cc doc/lispref/files.texi index e2d22033a0e,c2e057783c5..ee820b9ec88 --- a/doc/lispref/files.texi +++ b/doc/lispref/files.texi @@@ -1906,51 -1920,51 +1906,22 @@@ Don't try concatenating a slash by hand because this is not portable. Always use @code{file-name-as-directory}. --@cindex directory name abbreviation -- Directory name abbreviations are useful for directories that are --normally accessed through symbolic links. Sometimes the users recognize --primarily the link's name as ``the name'' of the directory, and find it --annoying to see the directory's ``real'' name. If you define the link --name as an abbreviation for the ``real'' name, Emacs shows users the --abbreviation instead. -- --@defopt directory-abbrev-alist --The variable @code{directory-abbrev-alist} contains an alist of --abbreviations to use for file directories. Each element has the form --@code{(@var{from} . @var{to})}, and says to replace @var{from} with --@var{to} when it appears in a directory name. The @var{from} string is - actually a regular expression; it ought to always start with @samp{\`}. -actually a regular expression; it should always start with @samp{\`}. --The @var{to} string should be an ordinary absolute directory name. Do --not use @samp{~} to stand for a home directory in that string. The --function @code{abbreviate-file-name} performs these substitutions. -- --You can set this variable in @file{site-init.el} to describe the --abbreviations appropriate for your site. -- --Here's an example, from a system on which file system @file{/home/fsf} --and so on are normally accessed through symbolic links named @file{/fsf} --and so on. -- --@example --(("\\`/home/fsf" . "/fsf") -- ("\\`/home/gp" . "/gp") -- ("\\`/home/gd" . "/gd")) --@end example --@end defopt -- To convert a directory name to its abbreviation, use this function: @defun abbreviate-file-name filename @anchor{Definition of abbreviate-file-name} --This function applies abbreviations from @code{directory-abbrev-alist} --to its argument, and also substitutes @samp{~} for the user's home --directory if the argument names a file in the home directory or one of --its subdirectories. (If the home directory is a root directory, it is --not replaced with @samp{~}, because this does not make the result --shorter on many systems.) You can use it for directory names and for --file names, because it recognizes abbreviations even as part of the --name. ++This function returns an abbreviated form of @var{filename}. It ++applies the abbreviations specified in @code{directory-abbrev-alist} ++(@pxref{File Aliases,,File Aliases, emacs, The GNU Emacs Manual}), ++then substitutes @samp{~} for the user's home directory if the ++argument names a file in the home directory or one of its ++subdirectories. If the home directory is a root directory, it is not ++replaced with @samp{~}, because this does not make the result shorter ++on many systems. ++ ++You can use this function for directory names and for file names, ++because it recognizes abbreviations even as part of the name. @end defun @node File Name Expansion diff --cc lisp/ChangeLog index 3c88bb33646,504ebf59708..8c0bfe3aaeb --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@@ -1,22 -1,4 +1,73 @@@ -2011-01-03 Brent Goodrick (tiny change) ++2011-01-15 Stefan Monnier ++ ++ * tmm.el (tmm-get-keymap): Skip bindings without labels (bug#7721). ++ (tmm-prompt): Simplify. ++ (tmm-add-prompt): Remove unused var `win'. ++ ++ * whitespace.el (global-whitespace-newline-mode): Fix call (bug#7810) ++ to minor mode which used nil accidentally to mean "turn off". ++ ++2011-01-15 Michael Albinus ++ ++ * net/tramp-sh.el (tramp-find-inline-compress) ++ (tramp-get-inline-coding): Quote command after pipe symbol for ++ local calls under W32. (Bug#6784) ++ ++2011-01-15 Michael Albinus ++ ++ * net/tramp.el (tramp-default-method): Initialize with pscp/plink ++ only when running under W32. ++ ++2011-01-15 Eli Zaretskii ++ ++ * progmodes/grep.el (grep-compute-defaults): Quote the program ++ file name after the pipe symbol in Grep templates. (Bug#6784) ++ * jka-compr.el (jka-compr-partial-uncompress): Likewise. ++ ++2011-01-15 Lennart Borgman ++ ++ * buff-menu.el (Buffer-menu-buffer-list): New var. ++ (Buffer-menu-revert-function, list-buffers-noselect): Use it, so a ++ restricted buffer list is not lost on revert (Bug#7749). ++ ++2011-01-15 Eric Hanchrow ++ ++ * net/ldap.el (ldap-search-internal): Discard stderr output. ++ ++2011-01-15 Eli Zaretskii ++ ++ * files.el (directory-abbrev-alist): Doc fix. (Bug#7777) ++ ++2011-01-15 Stefan Monnier ++ ++ * vc-bzr.el (vc-bzr-annotate-time): Tweak previous change. ++ ++2011-01-15 Kenichi Handa ++ ++ * mail/rmailmm.el (rmail-mime-insert-bulk): Display an unknown ++ part as a plain text. ++ (rmail-mime-process-multipart): Set the default content-type to ++ nil for unknown multipart subtypes (bug#7651). ++ +2011-01-14 Stefan Monnier + + * hexl.el (hexl-mode-old-*): Remove. + (hexl-mode--old-var-vals): New var to replace them. + (hexl-mode--minor-mode-p, hexl-mode--setq-local): New funs. + (hexl-mode, hexl-follow-line, hexl-activate-ruler): + Use them to set local vars (bug#7846). + (hexl-mode-exit): Use hexl-mode--old-var-vals to restore state. + (hexl-backward-short, hexl-backward-word, hexl-scroll-down) + (hexl-scroll-up, hexl-end-of-1k-page, hexl-end-of-512b-page): Simplify. + + * vc/smerge-mode.el: Resolve comment conflicts more aggressively. + (smerge-resolve--normalize-re): New var. + (smerge-resolve--extract-comment, smerge-resolve--normalize): New funs. + (smerge-resolve): Use them. + * newcomment.el (comment-only-p): New function. + (comment-or-uncomment-region): Use it. + +2011-01-14 Brent Goodrick (tiny change) * abbrev.el (prepare-abbrev-list-buffer): If listing local abbrev table, get the value before switching to the output buffer. (Bug#7733) diff --cc lisp/buff-menu.el index 4eac52ac4a5,44e494e98a6..70f48a8a58e --- a/lisp/buff-menu.el +++ b/lisp/buff-menu.el @@@ -112,9 -111,9 +112,15 @@@ A nil value means sort by visited orde This variable determines whether reverting the buffer lists only file buffers. It affects both manual reverting and reverting by Auto Revert Mode.") -- (make-variable-buffer-local 'Buffer-menu-files-only) ++(defvar Buffer-menu--buffers nil ++ "If non-nil, list of buffers shown in the current buffer-menu. ++This variable determines whether reverting the buffer lists only ++this buffers. It affects both manual reverting and reverting by ++Auto Revert Mode.") ++(make-variable-buffer-local 'Buffer-menu--buffers) ++ (defvar Info-current-file) ;; from info.el (defvar Info-current-node) ;; from info.el @@@ -282,7 -281,7 +288,7 @@@ Letters do not insert themselves; inste ;; interactively current buffer is correctly identified with a `.' ;; by `list-buffers-noselect'. (with-current-buffer (window-buffer) -- (list-buffers-noselect Buffer-menu-files-only)) ++ (list-buffers-noselect Buffer-menu-files-only Buffer-menu--buffers)) (if oline (while (setq prop (next-single-property-change prop 'buffer)) (when (eq (get-text-property prop 'buffer) oline) @@@ -919,6 -920,6 +925,7 @@@ For more information, see the function (and desired-point (goto-char desired-point)) (setq Buffer-menu-files-only files-only) ++ (setq Buffer-menu--buffers buffer-list) (set-buffer-modified-p nil) (current-buffer)))) diff --cc lisp/files.el index 1e03ba1920f,690caf13960..2223c1ae6b8 --- a/lisp/files.el +++ b/lisp/files.el @@@ -57,7 -56,7 +57,10 @@@ when it has unsaved changes. A list of elements of the form (FROM . TO), each meaning to replace FROM with TO when it appears in a directory name. This replacement is done when setting up the default directory of a newly visited file. - *Every* FROM string ought to start with \"\\\\`\". -*Every* FROM string should start with \"\\\\`\". ++ ++FROM is matched against directory names anchored at the first ++character, so it should start with a \"\\\\`\", or, if directory ++names cannot have embedded newlines, with a \"^\". FROM and TO should be equivalent names, which refer to the same directory. Do not use `~' in the TO strings; diff --cc lisp/jka-compr.el index 8c9866380b3,635f3ce8468..739fd3f1dd0 --- a/lisp/jka-compr.el +++ b/lisp/jka-compr.el @@@ -166,8 -166,8 +166,11 @@@ to keep: LEN chars starting BEG chars f (unwind-protect (or (memq (call-process jka-compr-shell infile t nil "-c" ++ ;; Windows shells need the program file name ++ ;; after the pipe symbol be quoted if they use ++ ;; forward slashes as directory separators. (format -- "%s %s 2> %s | %s bs=%d skip=%d %s 2> %s" ++ "%s %s 2> %s | \"%s\" bs=%d skip=%d %s 2> %s" prog (mapconcat 'identity args " ") err-file diff --cc lisp/mail/rmailmm.el index ba1f39798e3,f6741023e64..2221568e55f --- a/lisp/mail/rmailmm.el +++ b/lisp/mail/rmailmm.el @@@ -742,7 -741,7 +742,11 @@@ directly. (cond ((eq (cdr bulk-data) 'text) (rmail-mime-insert-decoded-text entity)) ((cdr bulk-data) -- (rmail-mime-insert-image entity))))) ++ (rmail-mime-insert-image entity)) ++ (t ++ ;; As we don't know how to display the body, just ++ ;; insert it as a text. ++ (rmail-mime-insert-decoded-text entity))))) (put-text-property beg (point) 'rmail-mime-entity entity))) (defun test-rmail-mime-bulk-handler () @@@ -820,7 -819,7 +824,9 @@@ The other arguments are the same as `rm (cond ((string-match "mixed" subtype) (setq content-type '("text/plain"))) ((string-match "digest" subtype) -- (setq content-type '("message/rfc822")))) ++ (setq content-type '("message/rfc822"))) ++ (t ++ (setq content-type nil))) ;; Loop over all body parts, where beg points at the beginning of ;; the part and end points at the end of the part. next points at diff --cc lisp/net/ldap.el index 88284af06f0,afdb0fe3e13..0de4c7f32d8 --- a/lisp/net/ldap.el +++ b/lisp/net/ldap.el @@@ -556,9 -556,13 +556,10 @@@ an alist of attribute/value pairs. (if (and sizelimit (not (equal "" sizelimit))) (setq arglist (nconc arglist (list (format "-z%s" sizelimit))))) - (eval `(call-process ldap-ldapsearch-prog - nil - buf - nil - ,@arglist - ,@ldap-ldapsearch-args - ,@filter)) + (apply #'call-process ldap-ldapsearch-prog - nil buf nil ++ ;; Ignore stderr, which can corrupt results ++ nil (list buf nil) nil + (append arglist ldap-ldapsearch-args filter)) (insert "\n") (goto-char (point-min)) diff --cc lisp/net/tramp-sh.el index ae7deeeb953,00000000000..c50f4649e8e mode 100644,000000..100644 --- a/lisp/net/tramp-sh.el +++ b/lisp/net/tramp-sh.el @@@ -1,5061 -1,0 +1,5085 @@@ +;;; tramp-sh.el --- Tramp access functions for (s)sh-like connections + +;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, +;; 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. + +;; (copyright statements below in code to be updated with the above notice) + +;; Author: Kai Großjohann +;; Michael Albinus +;; Keywords: comm, processes +;; Package: tramp + +;; 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 3 of the License, 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. If not, see . + +;;; Code: + +(eval-when-compile (require 'cl)) ; ignore-errors +(require 'tramp) +(require 'shell) + +;; 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'. + +(defcustom tramp-inline-compress-start-size 4096 + "*The minimum size of compressing where inline transfer. +When inline transfer, compress transfered data of file +whose size is this value or above (up to `tramp-copy-size-limit'). +If it is nil, no compression at all will be applied." + :group 'tramp + :type '(choice (const nil) integer)) + +(defcustom tramp-copy-size-limit 10240 + "*The maximum file size where inline copying is preferred over an out-of-the-band copy. +If it is nil, inline out-of-the-band copy will be used without a check." + :group 'tramp + :type '(choice (const nil) integer)) + +;;;###tramp-autoload +(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) + +;; ksh on OpenBSD 4.5 requires, that $PS1 contains a `#' character for +;; root users. It uses the `$' character for other users. In order +;; to guarantee a proper prompt, we use "#$" for the prompt. + +(defvar tramp-end-of-output + (format + "///%s#$" + (md5 (concat (prin1-to-string process-environment) (current-time-string)))) + "String used to recognize end of output. +The '$' character at the end is quoted; the string cannot be +detected as prompt when being sent on echoing hosts, therefore.") + +;;;###tramp-autoload +(defconst tramp-initial-end-of-output "#$ " + "Prompt when establishing a connection.") + +;; Initialize `tramp-methods' with the supported methods. +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("rcp" + (tramp-login-program "rsh") + (tramp-login-args (("%h") ("-l" "%u"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "rcp") + (tramp-copy-args (("%k" "-p") ("-r"))) + (tramp-copy-keep-date t) + (tramp-copy-recursive t))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("remcp" + (tramp-login-program "remsh") + (tramp-login-args (("%h") ("-l" "%u"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "rcp") + (tramp-copy-args (("%k" "-p"))) + (tramp-copy-keep-date t))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("scp" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-P" "%p") ("%k" "-p") ("-q") ("-r"))) + (tramp-copy-keep-date t) + (tramp-copy-recursive t) + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("scp1" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") + ("-1") ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-1") ("-P" "%p") ("%k" "-p") ("-q") ("-r"))) + (tramp-copy-keep-date t) + (tramp-copy-recursive t) + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("scp2" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") + ("-2") ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-2") ("-P" "%p") ("%k" "-p") ("-q") ("-r"))) + (tramp-copy-keep-date t) + (tramp-copy-recursive t) + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("scpc" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") + ("-o" "ControlPath=%t.%%r@%%h:%%p") + ("-o" "ControlMaster=yes") + ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-P" "%p") ("%k" "-p") ("-q") ("-r") + ("-o" "ControlPath=%t.%%r@%%h:%%p") + ("-o" "ControlMaster=auto"))) + (tramp-copy-keep-date t) + (tramp-copy-recursive t) + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("scpx" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") + ("-e" "none") ("-t" "-t") + ("%h") ("/bin/sh"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-P" "%p") ("%k" "-p") ("-q") ("-r"))) + (tramp-copy-keep-date t) + (tramp-copy-recursive t) + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("sftp" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "sftp"))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("rsync" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "rsync") + (tramp-copy-args (("-e" "ssh") ("%k" "-t") ("-r"))) + (tramp-copy-keep-date t) + (tramp-copy-keep-tmpfile t) + (tramp-copy-recursive t))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + `("rsyncc" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") + ("-o" "ControlPath=%t.%%r@%%h:%%p") + ("-o" "ControlMaster=yes") + ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "rsync") + (tramp-copy-args (("%k" "-t") ("-r"))) + (tramp-copy-env (("RSYNC_RSH") + (,(concat + "ssh" + " -o ControlPath=%t.%%r@%%h:%%p" + " -o ControlMaster=auto")))) + (tramp-copy-keep-date t) + (tramp-copy-keep-tmpfile t) + (tramp-copy-recursive t))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("rsh" + (tramp-login-program "rsh") + (tramp-login-args (("%h") ("-l" "%u"))) + (tramp-remote-sh "/bin/sh"))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("remsh" + (tramp-login-program "remsh") + (tramp-login-args (("%h") ("-l" "%u"))) + (tramp-remote-sh "/bin/sh"))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("ssh" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("ssh1" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") + ("-1") ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("ssh2" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") + ("-2") ("-e" "none") ("%h"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("sshx" + (tramp-login-program "ssh") + (tramp-login-args (("-l" "%u") ("-p" "%p") + ("-e" "none") ("-t" "-t") + ("%h") ("/bin/sh"))) + (tramp-async-args (("-q"))) + (tramp-remote-sh "/bin/sh") + (tramp-gw-args (("-o" "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("telnet" + (tramp-login-program "telnet") + (tramp-login-args (("%h") ("%p"))) + (tramp-remote-sh "/bin/sh") + (tramp-default-port 23))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("su" + (tramp-login-program "su") + (tramp-login-args (("-") ("%u"))) + (tramp-remote-sh "/bin/sh"))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("sudo" + (tramp-login-program "sudo") + (tramp-login-args (("-u" "%u") ("-s") ("-H") ("-p" "Password:"))) + (tramp-remote-sh "/bin/sh"))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("ksu" + (tramp-login-program "ksu") + (tramp-login-args (("%u") ("-q"))) + (tramp-remote-sh "/bin/sh"))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("krlogin" + (tramp-login-program "krlogin") + (tramp-login-args (("%h") ("-l" "%u") ("-x"))) + (tramp-remote-sh "/bin/sh"))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("plink" + (tramp-login-program "plink") + (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") ("%h"))) + (tramp-remote-sh "/bin/sh") + (tramp-password-end-of-line "xy") ;see docstring for "xy" + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("plink1" + (tramp-login-program "plink") + (tramp-login-args (("-l" "%u") ("-P" "%p") ("-1" "-ssh") ("%h"))) + (tramp-remote-sh "/bin/sh") + (tramp-password-end-of-line "xy") ;see docstring for "xy" + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + `("plinkx" + (tramp-login-program "plink") + ;; ("%h") must be a single element, see + ;; `tramp-compute-multi-hops'. + (tramp-login-args (("-load") ("%h") ("-t") + (,(format + "env 'TERM=%s' 'PROMPT_COMMAND=' 'PS1=%s'" + tramp-terminal-type + tramp-initial-end-of-output)) + ("/bin/sh"))) + (tramp-remote-sh "/bin/sh"))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("pscp" + (tramp-login-program "plink") + (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") ("%h"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "pscp") + (tramp-copy-args (("-P" "%p") ("-scp") ("%k" "-p") + ("-q") ("-r"))) + (tramp-copy-keep-date t) + (tramp-copy-recursive t) + (tramp-password-end-of-line "xy") ;see docstring for "xy" + (tramp-default-port 22))) +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("psftp" + (tramp-login-program "plink") + (tramp-login-args (("-l" "%u") ("-P" "%p") ("-ssh") ("%h"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "pscp") + (tramp-copy-args (("-P" "%p") ("-sftp") ("%k" "-p") + ("-q") ("-r"))) + (tramp-copy-keep-date t) + (tramp-copy-recursive t) + (tramp-password-end-of-line "xy"))) ;see docstring for "xy" +;;;###tramp-autoload +(add-to-list 'tramp-methods + '("fcp" + (tramp-login-program "fsh") + (tramp-login-args (("%h") ("-l" "%u") ("sh" "-i"))) + (tramp-remote-sh "/bin/sh -i") + (tramp-copy-program "fcp") + (tramp-copy-args (("%k" "-p"))) + (tramp-copy-keep-date t))) + +;;;###tramp-autoload +(add-to-list 'tramp-default-method-alist + `(,tramp-local-host-regexp "\\`root\\'" "su")) + +;;;###tramp-autoload +(add-to-list 'tramp-default-user-alist + `(,(concat "\\`" (regexp-opt '("su" "sudo" "ksu")) "\\'") + nil "root")) +;; Do not add "ssh" based methods, otherwise ~/.ssh/config would be ignored. +;;;###tramp-autoload +(add-to-list 'tramp-default-user-alist + `(,(concat + "\\`" + (regexp-opt + '("rcp" "remcp" "rsh" "telnet" "krlogin" + "plink" "plink1" "pscp" "psftp" "fcp")) + "\\'") + nil ,(user-login-name))) + +(defconst tramp-completion-function-alist-rsh + '((tramp-parse-rhosts "/etc/hosts.equiv") + (tramp-parse-rhosts "~/.rhosts")) + "Default list of (FUNCTION FILE) pairs to be examined for rsh methods.") + +(defconst tramp-completion-function-alist-ssh + '((tramp-parse-rhosts "/etc/hosts.equiv") + (tramp-parse-rhosts "/etc/shosts.equiv") + (tramp-parse-shosts "/etc/ssh_known_hosts") + (tramp-parse-sconfig "/etc/ssh_config") + (tramp-parse-shostkeys "/etc/ssh2/hostkeys") + (tramp-parse-sknownhosts "/etc/ssh2/knownhosts") + (tramp-parse-rhosts "~/.rhosts") + (tramp-parse-rhosts "~/.shosts") + (tramp-parse-shosts "~/.ssh/known_hosts") + (tramp-parse-sconfig "~/.ssh/config") + (tramp-parse-shostkeys "~/.ssh2/hostkeys") + (tramp-parse-sknownhosts "~/.ssh2/knownhosts")) + "Default list of (FUNCTION FILE) pairs to be examined for ssh methods.") + +(defconst tramp-completion-function-alist-telnet + '((tramp-parse-hosts "/etc/hosts")) + "Default list of (FUNCTION FILE) pairs to be examined for telnet methods.") + +(defconst tramp-completion-function-alist-su + '((tramp-parse-passwd "/etc/passwd")) + "Default list of (FUNCTION FILE) pairs to be examined for su methods.") + +(defconst tramp-completion-function-alist-putty + '((tramp-parse-putty + "HKEY_CURRENT_USER\\Software\\SimonTatham\\PuTTY\\Sessions")) + "Default list of (FUNCTION REGISTRY) pairs to be examined for putty methods.") + +(tramp-set-completion-function "rcp" tramp-completion-function-alist-rsh) +(tramp-set-completion-function "remcp" tramp-completion-function-alist-rsh) +(tramp-set-completion-function "scp" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "scp1" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "scp2" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "scpc" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "scpx" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "sftp" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "rsync" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "rsyncc" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "rsh" tramp-completion-function-alist-rsh) +(tramp-set-completion-function "remsh" tramp-completion-function-alist-rsh) +(tramp-set-completion-function "ssh" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "ssh1" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "ssh2" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "ssh1_old" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "ssh2_old" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "sshx" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "telnet" tramp-completion-function-alist-telnet) +(tramp-set-completion-function "su" tramp-completion-function-alist-su) +(tramp-set-completion-function "sudo" tramp-completion-function-alist-su) +(tramp-set-completion-function "ksu" tramp-completion-function-alist-su) +(tramp-set-completion-function "krlogin" tramp-completion-function-alist-rsh) +(tramp-set-completion-function "plink" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "plink1" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "plinkx" tramp-completion-function-alist-putty) +(tramp-set-completion-function "pscp" tramp-completion-function-alist-ssh) +(tramp-set-completion-function "fcp" tramp-completion-function-alist-ssh) + +;; "getconf PATH" yields: +;; HP-UX: /usr/bin:/usr/ccs/bin:/opt/ansic/bin:/opt/langtools/bin:/opt/fortran/bin +;; Solaris: /usr/xpg4/bin:/usr/ccs/bin:/usr/bin:/opt/SUNWspro/bin +;; GNU/Linux (Debian, Suse): /bin:/usr/bin +;; FreeBSD: /usr/bin:/bin:/usr/sbin:/sbin: - beware trailing ":"! +;; IRIX64: /usr/bin +(defcustom tramp-remote-path + '(tramp-default-remote-path "/usr/sbin" "/usr/local/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. +For every remote host, this variable will be set buffer local, +keeping the list of existing directories on that host. + +You can use `~' in this list, but when searching for a shell which groks +tilde expansion, all directory names starting with `~' will be ignored. + +`Default Directories' represent the list of directories given by +the command \"getconf PATH\". It is recommended to use this +entry on top of this list, because these are the default +directories for POSIX compatible commands. + +`Private Directories' are the settings of the $PATH environment, +as given in your `~/.profile'." + :group 'tramp + :type '(repeat (choice + (const :tag "Default Directories" tramp-default-remote-path) + (const :tag "Private Directories" tramp-own-remote-path) + (string :tag "Directory")))) + +(defcustom tramp-remote-process-environment + `("HISTFILE=$HOME/.tramp_history" "HISTSIZE=1" "LC_ALL=C" + ,(format "TERM=%s" tramp-terminal-type) + "EMACS=t" ;; Deprecated. + ,(format "INSIDE_EMACS='%s,tramp:%s'" emacs-version tramp-version) + "CDPATH=" "HISTORY=" "MAIL=" "MAILCHECK=" "MAILPATH=" + "autocorrect=" "correct=") + + "*List of environment variables to be set on the remote host. + +Each element should be a string of the form ENVVARNAME=VALUE. An +entry ENVVARNAME= diables the corresponding environment variable, +which might have been set in the init files like ~/.profile. + +Special handling is applied to the PATH environment, which should +not be set here. Instead of, it should be set via `tramp-remote-path'." + :group 'tramp + :type '(repeat string)) + +(defcustom tramp-sh-extra-args '(("/bash\\'" . "-norc -noprofile")) + "*Alist specifying extra arguments to pass to the remote shell. +Entries are (REGEXP . ARGS) where REGEXP is a regular expression +matching the shell file name and ARGS is a string specifying the +arguments. + +This variable is only used when Tramp needs to start up another shell +for tilde expansion. The extra arguments should typically prevent the +shell from reading its init file." + :group 'tramp + ;; This might be the wrong way to test whether the widget type + ;; `alist' is available. Who knows the right way to test it? + :type (if (get 'alist 'widget-type) + '(alist :key-type string :value-type string) + '(repeat (cons string string)))) + +(defconst tramp-actions-before-shell + '((tramp-login-prompt-regexp tramp-action-login) + (tramp-password-prompt-regexp tramp-action-password) + (tramp-wrong-passwd-regexp tramp-action-permission-denied) + (shell-prompt-pattern tramp-action-succeed) + (tramp-shell-prompt-pattern tramp-action-succeed) + (tramp-yesno-prompt-regexp tramp-action-yesno) + (tramp-yn-prompt-regexp tramp-action-yn) + (tramp-terminal-prompt-regexp tramp-action-terminal) + (tramp-process-alive-regexp tramp-action-process-alive)) + "List of pattern/action pairs. +Whenever a pattern matches, the corresponding action is performed. +Each item looks like (PATTERN ACTION). + +The PATTERN should be a symbol, a variable. The value of this +variable gives the regular expression to search for. Note that the +regexp must match at the end of the buffer, \"\\'\" is implicitly +appended to it. + +The ACTION should also be a symbol, but a function. When the +corresponding PATTERN matches, the ACTION function is called.") + +(defconst tramp-actions-copy-out-of-band + '((tramp-password-prompt-regexp tramp-action-password) + (tramp-wrong-passwd-regexp tramp-action-permission-denied) + (tramp-copy-failed-regexp tramp-action-permission-denied) + (tramp-process-alive-regexp tramp-action-out-of-band)) + "List of pattern/action pairs. +This list is used for copying/renaming with out-of-band methods. + +See `tramp-actions-before-shell' for more info.") + +(defconst tramp-uudecode + "(echo begin 600 /tmp/tramp.$$; tail +2) | uudecode +cat /tmp/tramp.$$ +rm -f /tmp/tramp.$$" + "Shell function to implement `uudecode' to standard output. +Many systems support `uudecode -o /dev/stdout' or `uudecode -o -' +for this or `uudecode -p', but some systems don't, and for them +we have this shell function.") + +(defconst tramp-perl-file-truename + "%s -e ' +use File::Spec; +use Cwd \"realpath\"; + +sub recursive { + my ($volume, @dirs) = @_; + my $real = realpath(File::Spec->catpath( + $volume, File::Spec->catdir(@dirs), \"\")); + if ($real) { + my ($vol, $dir) = File::Spec->splitpath($real, 1); + return ($vol, File::Spec->splitdir($dir)); + } + else { + my $last = pop(@dirs); + ($volume, @dirs) = recursive($volume, @dirs); + push(@dirs, $last); + return ($volume, @dirs); + } +} + +$result = realpath($ARGV[0]); +if (!$result) { + my ($vol, $dir) = File::Spec->splitpath($ARGV[0], 1); + ($vol, @dirs) = recursive($vol, File::Spec->splitdir($dir)); + + $result = File::Spec->catpath($vol, File::Spec->catdir(@dirs), \"\"); +} + +if ($ARGV[0] =~ /\\/$/) { + $result = $result . \"/\"; +} + +print \"\\\"$result\\\"\\n\"; +' \"$1\" 2>/dev/null" + "Perl script to produce output suitable for use with `file-truename' +on the remote file system. +Escape sequence %s is replaced with name of Perl binary. +This string is passed to `format', so percent characters need to be doubled.") + +(defconst tramp-perl-file-name-all-completions + "%s -e 'sub case { + my $str = shift; + if ($ARGV[2]) { + return lc($str); + } + else { + return $str; + } +} +opendir(d, $ARGV[0]) || die(\"$ARGV[0]: $!\\nfail\\n\"); +@files = readdir(d); closedir(d); +foreach $f (@files) { + if (case(substr($f, 0, length($ARGV[1]))) eq case($ARGV[1])) { + if (-d \"$ARGV[0]/$f\") { + print \"$f/\\n\"; + } + else { + print \"$f\\n\"; + } + } +} +print \"ok\\n\" +' \"$1\" \"$2\" \"$3\" 2>/dev/null" + "Perl script to produce output suitable for use with +`file-name-all-completions' on the remote file system. Escape +sequence %s is replaced with name of Perl binary. This string is +passed to `format', so percent characters need to be doubled.") + +;; 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. +;; The device number is returned as "-1", because there will be a virtual +;; device number set in `tramp-sh-handle-file-attributes'. +(defconst tramp-perl-file-attributes + "%s -e ' +@stat = lstat($ARGV[0]); +if (!@stat) { + print \"nil\\n\"; + exit 0; +} +if (($stat[2] & 0170000) == 0120000) +{ + $type = readlink($ARGV[0]); + $type = \"\\\"$type\\\"\"; +} +elsif (($stat[2] & 0170000) == 040000) +{ + $type = \"t\"; +} +else +{ + $type = \"nil\" +}; +$uid = ($ARGV[1] eq \"integer\") ? $stat[4] : \"\\\"\" . getpwuid($stat[4]) . \"\\\"\"; +$gid = ($ARGV[1] eq \"integer\") ? $stat[5] : \"\\\"\" . getgrgid($stat[5]) . \"\\\"\"; +printf( + \"(%%s %%u %%s %%s (%%u %%u) (%%u %%u) (%%u %%u) %%u.0 %%u t (%%u . %%u) -1)\\n\", + $type, + $stat[3], + $uid, + $gid, + $stat[8] >> 16 & 0xffff, + $stat[8] & 0xffff, + $stat[9] >> 16 & 0xffff, + $stat[9] & 0xffff, + $stat[10] >> 16 & 0xffff, + $stat[10] & 0xffff, + $stat[7], + $stat[2], + $stat[1] >> 16 & 0xffff, + $stat[1] & 0xffff +);' \"$1\" \"$2\" 2>/dev/null" + "Perl script to produce output suitable for use with `file-attributes' +on the remote file system. +Escape sequence %s is replaced with name of Perl binary. +This string is passed to `format', so percent characters need to be doubled.") + +(defconst tramp-perl-directory-files-and-attributes + "%s -e ' +chdir($ARGV[0]) or printf(\"\\\"Cannot change to $ARGV[0]: $''!''\\\"\\n\"), exit(); +opendir(DIR,\".\") or printf(\"\\\"Cannot open directory $ARGV[0]: $''!''\\\"\\n\"), exit(); +@list = readdir(DIR); +closedir(DIR); +$n = scalar(@list); +printf(\"(\\n\"); +for($i = 0; $i < $n; $i++) +{ + $filename = $list[$i]; + @stat = lstat($filename); + if (($stat[2] & 0170000) == 0120000) + { + $type = readlink($filename); + $type = \"\\\"$type\\\"\"; + } + elsif (($stat[2] & 0170000) == 040000) + { + $type = \"t\"; + } + else + { + $type = \"nil\" + }; + $uid = ($ARGV[1] eq \"integer\") ? $stat[4] : \"\\\"\" . getpwuid($stat[4]) . \"\\\"\"; + $gid = ($ARGV[1] eq \"integer\") ? $stat[5] : \"\\\"\" . getgrgid($stat[5]) . \"\\\"\"; + printf( + \"(\\\"%%s\\\" %%s %%u %%s %%s (%%u %%u) (%%u %%u) (%%u %%u) %%u.0 %%u t (%%u . %%u) (%%u . %%u))\\n\", + $filename, + $type, + $stat[3], + $uid, + $gid, + $stat[8] >> 16 & 0xffff, + $stat[8] & 0xffff, + $stat[9] >> 16 & 0xffff, + $stat[9] & 0xffff, + $stat[10] >> 16 & 0xffff, + $stat[10] & 0xffff, + $stat[7], + $stat[2], + $stat[1] >> 16 & 0xffff, + $stat[1] & 0xffff, + $stat[0] >> 16 & 0xffff, + $stat[0] & 0xffff); +} +printf(\")\\n\");' \"$1\" \"$2\" 2>/dev/null" + "Perl script implementing `directory-files-attributes' as Lisp `read'able +output. +Escape sequence %s is replaced with name of Perl binary. +This string is passed to `format', so percent characters need to be doubled.") + +;; These two use base64 encoding. +(defconst tramp-perl-encode-with-module + "%s -MMIME::Base64 -0777 -ne 'print encode_base64($_)' 2>/dev/null" + "Perl program to use for encoding a file. +Escape sequence %s is replaced with name of Perl binary. +This string is passed to `format', so percent characters need to be doubled. +This implementation requires the MIME::Base64 Perl module to be installed +on the remote host.") + +(defconst tramp-perl-decode-with-module + "%s -MMIME::Base64 -0777 -ne 'print decode_base64($_)' 2>/dev/null" + "Perl program to use for decoding a file. +Escape sequence %s is replaced with name of Perl binary. +This string is passed to `format', so percent characters need to be doubled. +This implementation requires the MIME::Base64 Perl module to be installed +on the remote host.") + +(defconst tramp-perl-encode + "%s -e ' +# This script contributed by Juanma Barranquero . +# Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 +# Free Software Foundation, Inc. +use strict; + +my %%trans = do { + my $i = 0; + map {(substr(unpack(q(B8), chr $i++), 2, 6), $_)} + split //, q(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/); +}; + +binmode(\\*STDIN); + +# We read in chunks of 54 bytes, to generate output lines +# of 72 chars (plus end of line) +$/ = \\54; + +while (my $data = ) { + my $pad = q(); + + # Only for the last chunk, and only if did not fill the last three-byte packet + if (eof) { + my $mod = length($data) %% 3; + $pad = q(=) x (3 - $mod) if $mod; + } + + # Not the fastest method, but it is simple: unpack to binary string, split + # by groups of 6 bits and convert back from binary to byte; then map into + # the translation table + print + join q(), + map($trans{$_}, + (substr(unpack(q(B*), $data) . q(00000), 0, 432) =~ /....../g)), + $pad, + qq(\\n); +}' 2>/dev/null" + "Perl program to use for encoding a file. +Escape sequence %s is replaced with name of Perl binary. +This string is passed to `format', so percent characters need to be doubled.") + +(defconst tramp-perl-decode + "%s -e ' +# This script contributed by Juanma Barranquero . - # Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 ++# Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 +# Free Software Foundation, Inc. +use strict; + +my %%trans = do { + my $i = 0; + map {($_, substr(unpack(q(B8), chr $i++), 2, 6))} + split //, q(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/) +}; + +my %%bytes = map {(unpack(q(B8), chr $_), chr $_)} 0 .. 255; + +binmode(\\*STDOUT); + +# We are going to accumulate into $pending to accept any line length +# (we do not check they are <= 76 chars as the RFC says) +my $pending = q(); + +while (my $data = ) { + chomp $data; + + # If we find one or two =, we have reached the end and + # any following data is to be discarded + my $finished = $data =~ s/(==?).*/$1/; + $pending .= $data; + + my $len = length($pending); + my $chunk = substr($pending, 0, $len & ~3); + $pending = substr($pending, $len & ~3 + 1); + + # Easy method: translate from chars to (pregenerated) six-bit packets, join, + # split in 8-bit chunks and convert back to char. + print join q(), + map $bytes{$_}, + ((join q(), map {$trans{$_} || q()} split //, $chunk) =~ /......../g); + + last if $finished; +}' 2>/dev/null" + "Perl program to use for decoding a file. +Escape sequence %s is replaced with name of Perl binary. +This string is passed to `format', so percent characters need to be doubled.") + +(defconst tramp-vc-registered-read-file-names + "echo \"(\" +while read file; do + if %s \"$file\"; then + echo \"(\\\"$file\\\" \\\"file-exists-p\\\" t)\" + else + echo \"(\\\"$file\\\" \\\"file-exists-p\\\" nil)\" + fi + if %s \"$file\"; then + echo \"(\\\"$file\\\" \\\"file-readable-p\\\" t)\" + else + echo \"(\\\"$file\\\" \\\"file-readable-p\\\" nil)\" + fi +done +echo \")\"" + "Script to check existence of VC related files. +It must be send formatted with two strings; the tests for file +existence, and file readability. Input shall be read via +here-document, otherwise the command could exceed maximum length +of command line.") + +(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.") + +;; New handlers should be added here. The following operations can be +;; handled using the normal primitives: file-name-sans-versions, +;; get-file-buffer. +(defconst tramp-sh-file-name-handler-alist + '((load . tramp-handle-load) + (make-symbolic-link . tramp-sh-handle-make-symbolic-link) + (file-name-as-directory . tramp-handle-file-name-as-directory) + (file-name-directory . tramp-handle-file-name-directory) + (file-name-nondirectory . tramp-handle-file-name-nondirectory) + (file-truename . tramp-sh-handle-file-truename) + (file-exists-p . tramp-sh-handle-file-exists-p) + (file-directory-p . tramp-sh-handle-file-directory-p) + (file-executable-p . tramp-sh-handle-file-executable-p) + (file-readable-p . tramp-sh-handle-file-readable-p) + (file-regular-p . tramp-handle-file-regular-p) + (file-symlink-p . tramp-handle-file-symlink-p) + (file-writable-p . tramp-sh-handle-file-writable-p) + (file-ownership-preserved-p . tramp-sh-handle-file-ownership-preserved-p) + (file-newer-than-file-p . tramp-sh-handle-file-newer-than-file-p) + (file-attributes . tramp-sh-handle-file-attributes) + (file-modes . tramp-handle-file-modes) + (directory-files . tramp-handle-directory-files) + (directory-files-and-attributes + . tramp-sh-handle-directory-files-and-attributes) + (file-name-all-completions . tramp-sh-handle-file-name-all-completions) + (file-name-completion . tramp-handle-file-name-completion) + (add-name-to-file . tramp-sh-handle-add-name-to-file) + (copy-file . tramp-sh-handle-copy-file) + (copy-directory . tramp-sh-handle-copy-directory) + (rename-file . tramp-sh-handle-rename-file) + (set-file-modes . tramp-sh-handle-set-file-modes) + (set-file-times . tramp-sh-handle-set-file-times) + (make-directory . tramp-sh-handle-make-directory) + (delete-directory . tramp-sh-handle-delete-directory) + (delete-file . tramp-sh-handle-delete-file) + (directory-file-name . tramp-handle-directory-file-name) + ;; `executable-find' is not official yet. + (executable-find . tramp-sh-handle-executable-find) + (start-file-process . tramp-sh-handle-start-file-process) + (process-file . tramp-sh-handle-process-file) + (shell-command . tramp-sh-handle-shell-command) + (insert-directory . tramp-sh-handle-insert-directory) + (expand-file-name . tramp-sh-handle-expand-file-name) + (substitute-in-file-name . tramp-handle-substitute-in-file-name) + (file-local-copy . tramp-sh-handle-file-local-copy) + (file-remote-p . tramp-handle-file-remote-p) + (insert-file-contents . tramp-handle-insert-file-contents) + (insert-file-contents-literally + . tramp-sh-handle-insert-file-contents-literally) + (write-region . tramp-sh-handle-write-region) + (find-backup-file-name . tramp-handle-find-backup-file-name) + (make-auto-save-file-name . tramp-sh-handle-make-auto-save-file-name) + (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory) + (dired-compress-file . tramp-sh-handle-dired-compress-file) + (dired-recursive-delete-directory + . tramp-sh-handle-dired-recursive-delete-directory) + (dired-uncache . tramp-handle-dired-uncache) + (set-visited-file-modtime . tramp-sh-handle-set-visited-file-modtime) + (verify-visited-file-modtime . tramp-sh-handle-verify-visited-file-modtime) + (file-selinux-context . tramp-sh-handle-file-selinux-context) + (set-file-selinux-context . tramp-sh-handle-set-file-selinux-context) + (vc-registered . tramp-sh-handle-vc-registered)) + "Alist of handler functions. +Operations not mentioned here will be handled by the normal Emacs functions.") + +;; This must be the last entry, because `identity' always matches. +;;;###tramp-autoload +(add-to-list 'tramp-foreign-file-name-handler-alist + '(identity . tramp-sh-file-name-handler) 'append) + +;;; File Name Handler Functions: + +(defun tramp-sh-handle-make-symbolic-link + (filename linkname &optional ok-if-already-exists) + "Like `make-symbolic-link' for Tramp files. +If LINKNAME is a non-Tramp file, it is used verbatim as the target of +the symlink. If LINKNAME is a Tramp file, only the localname component is +used as the target of the symlink. + +If LINKNAME is a Tramp file and the localname component is relative, then +it is expanded first, before the localname component is taken. Note that +this can give surprising results if the user/host for the source and +target of the symlink differ." + (with-parsed-tramp-file-name linkname l + (let ((ln (tramp-get-remote-ln l)) + (cwd (tramp-run-real-handler + 'file-name-directory (list l-localname)))) + (unless ln + (tramp-error + l 'file-error + "Making a symbolic link. ln(1) does not exist on the remote host.")) + + ;; Do the 'confirm if exists' thing. + (when (file-exists-p linkname) + ;; What to do? + (if (or (null ok-if-already-exists) ; not allowed to exist + (and (numberp ok-if-already-exists) + (not (yes-or-no-p + (format + "File %s already exists; make it a link anyway? " + l-localname))))) + (tramp-error + l 'file-already-exists "File %s already exists" l-localname) + (delete-file linkname))) + + ;; If FILENAME is a Tramp name, use just the localname component. + (when (tramp-tramp-file-p filename) + (setq filename + (tramp-file-name-localname + (tramp-dissect-file-name (expand-file-name filename))))) + + (tramp-flush-file-property l (file-name-directory l-localname)) + (tramp-flush-file-property l l-localname) + + ;; Right, they are on the same host, regardless of user, method, etc. + ;; We now make the link on the remote machine. This will occur as the user + ;; that FILENAME belongs to. + (tramp-send-command-and-check + l + (format + "cd %s && %s -sf %s %s" + (tramp-shell-quote-argument cwd) + ln + (tramp-shell-quote-argument filename) + (tramp-shell-quote-argument l-localname)) + t)))) + +(defun tramp-sh-handle-file-truename (filename &optional counter prev-dirs) + "Like `file-truename' for Tramp files." + (with-parsed-tramp-file-name (expand-file-name filename) nil + (with-file-property v localname "file-truename" + (let ((result nil)) ; result steps in reverse order + (tramp-message v 4 "Finding true name for `%s'" filename) + (cond + ;; Use GNU readlink --canonicalize-missing where available. + ((tramp-get-remote-readlink v) + (setq result + (tramp-send-command-and-read + v + (format "echo \"\\\"`%s --canonicalize-missing %s`\\\"\"" + (tramp-get-remote-readlink v) + (tramp-shell-quote-argument localname))))) + + ;; Use Perl implementation. + ((and (tramp-get-remote-perl v) + (tramp-get-connection-property v "perl-file-spec" nil) + (tramp-get-connection-property v "perl-cwd-realpath" nil)) + (tramp-maybe-send-script + v tramp-perl-file-truename "tramp_perl_file_truename") + (setq result + (tramp-send-command-and-read + v + (format "tramp_perl_file_truename %s" + (tramp-shell-quote-argument localname))))) + + ;; Do it yourself. We bind `directory-sep-char' here for + ;; XEmacs on Windows, which would otherwise use backslash. + (t (let* ((directory-sep-char ?/) + (steps (tramp-compat-split-string localname "/")) + (localnamedir (tramp-run-real-handler + 'file-name-as-directory (list localname))) + (is-dir (string= localname localnamedir)) + (thisstep nil) + (numchase 0) + ;; Don't make the following value larger than + ;; necessary. People expect an error message in a + ;; timely fashion when something is wrong; + ;; otherwise they might think that Emacs is hung. + ;; Of course, correctness has to come first. + (numchase-limit 20) + symlink-target) + (while (and steps (< numchase numchase-limit)) + (setq thisstep (pop steps)) + (tramp-message + v 5 "Check %s" + (mapconcat 'identity + (append '("") (reverse result) (list thisstep)) + "/")) + (setq symlink-target + (nth 0 (file-attributes + (tramp-make-tramp-file-name + method user host + (mapconcat 'identity + (append '("") + (reverse result) + (list thisstep)) + "/"))))) + (cond ((string= "." thisstep) + (tramp-message v 5 "Ignoring step `.'")) + ((string= ".." thisstep) + (tramp-message v 5 "Processing step `..'") + (pop result)) + ((stringp symlink-target) + ;; It's a symlink, follow it. + (tramp-message v 5 "Follow symlink to %s" symlink-target) + (setq numchase (1+ numchase)) + (when (file-name-absolute-p symlink-target) + (setq result nil)) + ;; If the symlink was absolute, we'll get a string like + ;; "/user@host:/some/target"; extract the + ;; "/some/target" part from it. + (when (tramp-tramp-file-p symlink-target) + (unless (tramp-equal-remote filename symlink-target) + (tramp-error + v 'file-error + "Symlink target `%s' on wrong host" symlink-target)) + (setq symlink-target localname)) + (setq steps + (append (tramp-compat-split-string + symlink-target "/") + steps))) + (t + ;; It's a file. + (setq result (cons thisstep result))))) + (when (>= numchase numchase-limit) + (tramp-error + v 'file-error + "Maximum number (%d) of symlinks exceeded" numchase-limit)) + (setq result (reverse result)) + ;; Combine list to form string. + (setq result + (if result + (mapconcat 'identity (cons "" result) "/") + "/")) + (when (and is-dir (or (string= "" result) + (not (string= (substring result -1) "/")))) + (setq result (concat result "/")))))) + + (tramp-message v 4 "True name of `%s' is `%s'" filename result) + (tramp-make-tramp-file-name method user host result))))) + +;; Basic functions. + +(defun tramp-sh-handle-file-exists-p (filename) + "Like `file-exists-p' for Tramp files." + (with-parsed-tramp-file-name filename nil + (with-file-property v localname "file-exists-p" + (or (not (null (tramp-get-file-property + v localname "file-attributes-integer" nil))) + (not (null (tramp-get-file-property + v localname "file-attributes-string" nil))) + (tramp-send-command-and-check + v + (format + "%s %s" + (tramp-get-file-exists-command v) + (tramp-shell-quote-argument localname))))))) + +;; CCC: This should check for an error condition and signal failure +;; when something goes wrong. +;; Daniel Pittman +(defun tramp-sh-handle-file-attributes (filename &optional id-format) + "Like `file-attributes' for Tramp files." + (unless id-format (setq id-format 'integer)) + ;; Don't modify `last-coding-system-used' by accident. + (let ((last-coding-system-used last-coding-system-used)) + (with-parsed-tramp-file-name (expand-file-name filename) nil + (with-file-property v localname (format "file-attributes-%s" id-format) + (save-excursion + (tramp-convert-file-attributes + v + (cond + ((tramp-get-remote-stat v) + (tramp-do-file-attributes-with-stat v localname id-format)) + ((tramp-get-remote-perl v) + (tramp-do-file-attributes-with-perl v localname id-format)) + (t + (tramp-do-file-attributes-with-ls v localname id-format))))))))) + +(defun tramp-do-file-attributes-with-ls (vec localname &optional id-format) + "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-message vec 5 "file attributes with ls: %s" localname) + (tramp-send-command + vec + (format "(%s %s || %s -h %s) && %s %s %s" + (tramp-get-file-exists-command vec) + (tramp-shell-quote-argument localname) + (tramp-get-test-command vec) + (tramp-shell-quote-argument localname) + (tramp-get-ls-command vec) + (if (eq id-format 'integer) "-ildn" "-ild") + (tramp-shell-quote-argument localname))) + ;; parse `ls -l' output ... + (with-current-buffer (tramp-get-buffer vec) + (when (> (buffer-size) 0) + (goto-char (point-min)) + ;; ... 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]\\)\\'" + (car (cddr err)))) + (let* ((big (read (substring (car (cddr err)) 0 + (match-beginning 1)))) + (small (read (match-string 1 (car (cddr 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))) + (if (eq id-format 'integer) + (progn + (unless (numberp res-uid) (setq res-uid -1)) + (unless (numberp res-gid) (setq res-gid -1))) + (progn + (unless (stringp res-uid) (setq res-uid (symbol-name res-uid))) + (unless (stringp res-gid) (setq res-gid (symbol-name res-gid))))) + ;; ... 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) (point-at-eol)))) + ;; return data gathered + (list + ;; 0. t for directory, string (name linked to) for symbolic + ;; link, or nil. + (or dirp res-symlink-target) + ;; 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 if file's gid would change if file were deleted and + ;; recreated. Will be set in `tramp-convert-file-attributes' + t + ;; 10. inode number. + res-inode + ;; 11. Device number. Will be replaced by a virtual device number. + -1 + ))))) + +(defun tramp-do-file-attributes-with-perl + (vec localname &optional id-format) + "Implement `file-attributes' for Tramp files using a Perl script." + (tramp-message vec 5 "file attributes with perl: %s" localname) + (tramp-maybe-send-script + vec tramp-perl-file-attributes "tramp_perl_file_attributes") + (tramp-send-command-and-read + vec + (format "tramp_perl_file_attributes %s %s" + (tramp-shell-quote-argument localname) id-format))) + +(defun tramp-do-file-attributes-with-stat + (vec localname &optional id-format) + "Implement `file-attributes' for Tramp files using stat(1) command." + (tramp-message vec 5 "file attributes with stat: %s" localname) + (tramp-send-command-and-read + vec + (format + ;; On Opsware, pdksh (which is the true name of ksh there) doesn't + ;; parse correctly the sequence "((". Therefore, we add a space. + "( (%s %s || %s -h %s) && %s -c '((\"%%N\") %%h %s %s %%Xe0 %%Ye0 %%Ze0 %%se0 \"%%A\" t %%ie0 -1)' %s || echo nil)" + (tramp-get-file-exists-command vec) + (tramp-shell-quote-argument localname) + (tramp-get-test-command vec) + (tramp-shell-quote-argument localname) + (tramp-get-remote-stat vec) + (if (eq id-format 'integer) "%u" "\"%U\"") + (if (eq id-format 'integer) "%g" "\"%G\"") + (tramp-shell-quote-argument localname)))) + +(defun tramp-sh-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))) + (if time-list + (tramp-run-real-handler 'set-visited-file-modtime (list time-list)) + (let ((f (buffer-file-name)) + coding-system-used) + (with-parsed-tramp-file-name f nil + (let* ((attr (file-attributes f)) + ;; '(-1 65535) means file doesn't exists yet. + (modtime (or (nth 5 attr) '(-1 65535)))) + (when (boundp 'last-coding-system-used) + (setq coding-system-used (symbol-value 'last-coding-system-used))) + ;; We use '(0 0) as a don't-know value. See also + ;; `tramp-do-file-attributes-with-ls'. + (if (not (equal modtime '(0 0))) + (tramp-run-real-handler 'set-visited-file-modtime (list modtime)) + (progn + (tramp-send-command + v + (format "%s -ild %s" + (tramp-get-ls-command v) + (tramp-shell-quote-argument localname))) + (setq attr (buffer-substring (point) + (progn (end-of-line) (point))))) + (tramp-set-file-property + v localname "visited-file-modtime-ild" attr)) + (when (boundp 'last-coding-system-used) + (set 'last-coding-system-used coding-system-used)) + nil))))) + +;; This function makes the same assumption as +;; `tramp-sh-handle-set-visited-file-modtime'. +(defun tramp-sh-handle-verify-visited-file-modtime (buf) + "Like `verify-visited-file-modtime' for Tramp files. +At the time `verify-visited-file-modtime' calls this function, we +already know that the buffer is visiting a file and that +`visited-file-modtime' does not return 0. Do not call this +function directly, unless those two cases are already taken care +of." + (with-current-buffer buf + (let ((f (buffer-file-name))) + ;; There is no file visiting the buffer, or the buffer has no + ;; recorded last modification time, or there is no established + ;; connection. + (if (or (not f) + (eq (visited-file-modtime) 0) + (not (tramp-file-name-handler 'file-remote-p f nil 'connected))) + t + (with-parsed-tramp-file-name f nil + (let* ((remote-file-name-inhibit-cache t) + (attr (file-attributes f)) + (modtime (nth 5 attr)) + (mt (visited-file-modtime))) + + (cond + ;; File exists, and has a known modtime. + ((and attr (not (equal modtime '(0 0)))) + (< (abs (tramp-time-diff + modtime + ;; For compatibility, deal with both the old + ;; (HIGH . LOW) and the new (HIGH LOW) return + ;; values of `visited-file-modtime'. + (if (atom (cdr mt)) + (list (car mt) (cdr mt)) + mt))) + 2)) + ;; Modtime has the don't know value. + (attr + (tramp-send-command + v + (format "%s -ild %s" + (tramp-get-ls-command v) + (tramp-shell-quote-argument localname))) + (with-current-buffer (tramp-get-buffer v) + (setq attr (buffer-substring + (point) (progn (end-of-line) (point))))) + (equal + attr + (tramp-get-file-property + v localname "visited-file-modtime-ild" ""))) + ;; If file does not exist, say it is not modified if and + ;; only if that agrees with the buffer's record. + (t (equal mt '(-1 65535)))))))))) + +(defun tramp-sh-handle-set-file-modes (filename mode) + "Like `set-file-modes' for Tramp files." + (with-parsed-tramp-file-name filename nil + (tramp-flush-file-property v localname) + ;; FIXME: extract the proper text from chmod's stderr. + (tramp-barf-unless-okay + v + (format "chmod %s %s" + (tramp-compat-decimal-to-octal mode) + (tramp-shell-quote-argument localname)) + "Error while changing file's mode %s" filename))) + +(defun tramp-sh-handle-set-file-times (filename &optional time) + "Like `set-file-times' for Tramp files." + (if (file-remote-p filename) + (with-parsed-tramp-file-name filename nil + (tramp-flush-file-property v localname) + (let ((time (if (or (null time) (equal time '(0 0))) + (current-time) + time)) + ;; With GNU Emacs, `format-time-string' has an optional + ;; parameter UNIVERSAL. This is preferred, because we + ;; could handle the case when the remote host is located + ;; in a different time zone as the local host. + (utc (not (featurep 'xemacs)))) + (tramp-send-command-and-check + v (format "%s touch -t %s %s" + (if utc "TZ=UTC; export TZ;" "") + (if utc + (format-time-string "%Y%m%d%H%M.%S" time t) + (format-time-string "%Y%m%d%H%M.%S" time)) + (tramp-shell-quote-argument localname))))) + + ;; We handle also the local part, because in older Emacsen, + ;; without `set-file-times', this function is an alias for this. + ;; We are local, so we don't need the UTC settings. + (zerop + (tramp-compat-call-process + "touch" nil nil nil "-t" + (format-time-string "%Y%m%d%H%M.%S" time) + (tramp-shell-quote-argument filename))))) + +(defun tramp-set-file-uid-gid (filename &optional uid gid) + "Set the ownership for FILENAME. +If UID and GID are provided, these values are used; otherwise uid +and gid of the corresponding user is taken. Both parameters must be integers." + ;; Modern Unices allow chown only for root. So we might need + ;; another implementation, see `dired-do-chown'. OTOH, it is mostly + ;; working with su(do)? when it is needed, so it shall succeed in + ;; the majority of cases. + ;; Don't modify `last-coding-system-used' by accident. + (let ((last-coding-system-used last-coding-system-used)) + (if (file-remote-p filename) + (with-parsed-tramp-file-name filename nil + (if (and (zerop (user-uid)) (tramp-local-host-p v)) + ;; If we are root on the local host, we can do it directly. + (tramp-set-file-uid-gid localname uid gid) + (let ((uid (or (and (integerp uid) uid) + (tramp-get-remote-uid v 'integer))) + (gid (or (and (integerp gid) gid) + (tramp-get-remote-gid v 'integer)))) + (tramp-send-command + v (format + "chown %d:%d %s" uid gid + (tramp-shell-quote-argument localname)))))) + + ;; We handle also the local part, because there doesn't exist + ;; `set-file-uid-gid'. On W32 "chown" might not work. + (let ((uid (or (and (integerp uid) uid) (tramp-get-local-uid 'integer))) + (gid (or (and (integerp gid) gid) (tramp-get-local-gid 'integer)))) + (tramp-compat-call-process + "chown" nil nil nil + (format "%d:%d" uid gid) (tramp-shell-quote-argument filename)))))) + +(defun tramp-remote-selinux-p (vec) + "Check, whether SELINUX is enabled on the remote host." + (with-connection-property (tramp-get-connection-process vec) "selinux-p" + (let ((result (tramp-find-executable + vec "getenforce" (tramp-get-remote-path vec) t t))) + (and result + (string-equal + (tramp-send-command-and-read + vec (format "echo \\\"`%S`\\\"" result)) + "Enforcing"))))) + +(defun tramp-sh-handle-file-selinux-context (filename) + "Like `file-selinux-context' for Tramp files." + (with-parsed-tramp-file-name filename nil + (with-file-property v localname "file-selinux-context" + (let ((context '(nil nil nil nil)) + (regexp (concat "\\([a-z0-9_]+\\):" "\\([a-z0-9_]+\\):" + "\\([a-z0-9_]+\\):" "\\([a-z0-9_]+\\)"))) + (when (and (tramp-remote-selinux-p v) + (tramp-send-command-and-check + v (format + "%s -d -Z %s" + (tramp-get-ls-command v) + (tramp-shell-quote-argument localname)))) + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (when (re-search-forward regexp (point-at-eol) t) + (setq context (list (match-string 1) (match-string 2) + (match-string 3) (match-string 4)))))) + ;; Return the context. + context)))) + +(defun tramp-sh-handle-set-file-selinux-context (filename context) + "Like `set-file-selinux-context' for Tramp files." + (with-parsed-tramp-file-name filename nil + (if (and (consp context) + (tramp-remote-selinux-p v) + (tramp-send-command-and-check + v (format "chcon %s %s %s %s %s" + (if (stringp (nth 0 context)) + (format "--user=%s" (nth 0 context)) "") + (if (stringp (nth 1 context)) + (format "--role=%s" (nth 1 context)) "") + (if (stringp (nth 2 context)) + (format "--type=%s" (nth 2 context)) "") + (if (stringp (nth 3 context)) + (format "--range=%s" (nth 3 context)) "") + (tramp-shell-quote-argument localname)))) + (tramp-set-file-property v localname "file-selinux-context" context) + (tramp-set-file-property v localname "file-selinux-context" 'undef))) + ;; We always return nil. + nil) + +;; Simple functions using the `test' command. + +(defun tramp-sh-handle-file-executable-p (filename) + "Like `file-executable-p' for Tramp files." + (with-parsed-tramp-file-name filename nil + (with-file-property v localname "file-executable-p" + ;; Examine `file-attributes' cache to see if request can be + ;; satisfied without remote operation. + (or (tramp-check-cached-permissions v ?x) + (tramp-run-test "-x" filename))))) + +(defun tramp-sh-handle-file-readable-p (filename) + "Like `file-readable-p' for Tramp files." + (with-parsed-tramp-file-name filename nil + (with-file-property v localname "file-readable-p" + ;; Examine `file-attributes' cache to see if request can be + ;; satisfied without remote operation. + (or (tramp-check-cached-permissions v ?r) + (tramp-run-test "-r" 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-sh-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 + ;; We try to get the mtime of both files. If they are not + ;; equal to the "dont-know" value, then we subtract the times + ;; and obtain the result. + (let ((fa1 (file-attributes file1)) + (fa2 (file-attributes file2))) + (if (and (not (equal (nth 5 fa1) '(0 0))) + (not (equal (nth 5 fa2) '(0 0)))) + (> 0 (tramp-time-diff (nth 5 fa2) (nth 5 fa1))) + ;; If one of them is the dont-know value, then we can + ;; still try to run a shell command on the remote host. + ;; However, this only works if both files are Tramp + ;; files and both have the same method, same user, same + ;; host. + (unless (tramp-equal-remote file1 file2) + (with-parsed-tramp-file-name + (if (tramp-tramp-file-p file1) file1 file2) nil + (tramp-error + v 'file-error + "Files %s and %s must have same method, user, host" + file1 file2))) + (with-parsed-tramp-file-name file1 nil + (tramp-run-test2 + (tramp-get-test-nt-command v) file1 file2)))))))) + +;; Functions implemented using the basic functions above. + +(defun tramp-sh-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' + (with-parsed-tramp-file-name filename nil + (with-file-property v localname "file-directory-p" + (tramp-run-test "-d" filename)))) + +(defun tramp-sh-handle-file-writable-p (filename) + "Like `file-writable-p' for Tramp files." + (with-parsed-tramp-file-name filename nil + (with-file-property v localname "file-writable-p" + (if (file-exists-p filename) + ;; Examine `file-attributes' cache to see if request can be + ;; satisfied without remote operation. + (or (tramp-check-cached-permissions v ?w) + (tramp-run-test "-w" filename)) + ;; If file doesn't exist, check if directory is writable. + (and (tramp-run-test "-d" (file-name-directory filename)) + (tramp-run-test "-w" (file-name-directory filename))))))) + +(defun tramp-sh-handle-file-ownership-preserved-p (filename) + "Like `file-ownership-preserved-p' for Tramp files." + (with-parsed-tramp-file-name filename nil + (with-file-property v localname "file-ownership-preserved-p" + (let ((attributes (file-attributes filename))) + ;; Return t if the file doesn't exist, since it's true that no + ;; information would be lost by an (attempted) delete and create. + (or (null attributes) + (= (nth 2 attributes) (tramp-get-remote-uid v 'integer))))))) + +;; Directory listings. + +(defun tramp-sh-handle-directory-files-and-attributes + (directory &optional full match nosort id-format) + "Like `directory-files-and-attributes' for Tramp files." + (unless id-format (setq id-format 'integer)) + (when (file-directory-p directory) + (setq directory (expand-file-name directory)) + (let* ((temp + (copy-tree + (with-parsed-tramp-file-name directory nil + (with-file-property + v localname + (format "directory-files-and-attributes-%s" id-format) + (save-excursion + (mapcar + (lambda (x) + (cons (car x) + (tramp-convert-file-attributes v (cdr x)))) + (cond + ((tramp-get-remote-stat v) + (tramp-do-directory-files-and-attributes-with-stat + v localname id-format)) + ((tramp-get-remote-perl v) + (tramp-do-directory-files-and-attributes-with-perl + v localname id-format))))))))) + result item) + + (while temp + (setq item (pop temp)) + (when (or (null match) (string-match match (car item))) + (when full + (setcar item (expand-file-name (car item) directory))) + (push item result))) + + (if nosort + result + (sort result (lambda (x y) (string< (car x) (car y)))))))) + +(defun tramp-do-directory-files-and-attributes-with-perl + (vec localname &optional id-format) + "Implement `directory-files-and-attributes' for Tramp files using a Perl script." + (tramp-message vec 5 "directory-files-and-attributes with perl: %s" localname) + (tramp-maybe-send-script + vec tramp-perl-directory-files-and-attributes + "tramp_perl_directory_files_and_attributes") + (let ((object + (tramp-send-command-and-read + vec + (format "tramp_perl_directory_files_and_attributes %s %s" + (tramp-shell-quote-argument localname) id-format)))) + (when (stringp object) (tramp-error vec 'file-error object)) + object)) + +(defun tramp-do-directory-files-and-attributes-with-stat + (vec localname &optional id-format) + "Implement `directory-files-and-attributes' for Tramp files using stat(1) command." + (tramp-message vec 5 "directory-files-and-attributes with stat: %s" localname) + (tramp-send-command-and-read + vec + (format + (concat + ;; We must care about filenames with spaces, or starting with + ;; "-"; this would confuse xargs. "ls -aQ" might be a solution, + ;; but it does not work on all remote systems. Therefore, we + ;; quote the filenames via sed. + "cd %s; echo \"(\"; (%s -a | sed -e s/\\$/\\\"/g -e s/^/\\\"/g | xargs " + "%s -c '(\"%%n\" (\"%%N\") %%h %s %s %%Xe0 %%Ye0 %%Ze0 %%se0 \"%%A\" t %%ie0 -1)'); " + "echo \")\"") + (tramp-shell-quote-argument localname) + (tramp-get-ls-command vec) + (tramp-get-remote-stat vec) + (if (eq id-format 'integer) "%u" "\"%U\"") + (if (eq id-format 'integer) "%g" "\"%G\"")))) + +;; This function should return "foo/" for directories and "bar" for +;; files. +(defun tramp-sh-handle-file-name-all-completions (filename directory) + "Like `file-name-all-completions' for Tramp files." + (unless (save-match-data (string-match "/" filename)) + (with-parsed-tramp-file-name (expand-file-name directory) nil + + (all-completions + filename + (mapcar + 'list + (or + ;; Try cache entries for filename, filename with last + ;; character removed, filename with last two characters + ;; removed, ..., and finally the empty string - all + ;; concatenated to the local directory name. + (let ((remote-file-name-inhibit-cache + (or remote-file-name-inhibit-cache + tramp-completion-reread-directory-timeout))) + + ;; This is inefficient for very long filenames, pity + ;; `reduce' is not available... + (car + (apply + 'append + (mapcar + (lambda (x) + (let ((cache-hit + (tramp-get-file-property + v + (concat localname (substring filename 0 x)) + "file-name-all-completions" + nil))) + (when cache-hit (list cache-hit)))) + (tramp-compat-number-sequence (length filename) 0 -1))))) + + ;; Cache expired or no matching cache entry found so we need + ;; to perform a remote operation. + (let (result) + ;; Get a list of directories and files, including reliably + ;; tagging the directories with a trailing '/'. Because I + ;; rock. --daniel@danann.net + + ;; Changed to perform `cd' in the same remote op and only + ;; get entries starting with `filename'. Capture any `cd' + ;; error messages. Ensure any `cd' and `echo' aliases are + ;; ignored. + (tramp-send-command + v + (if (tramp-get-remote-perl v) + (progn + (tramp-maybe-send-script + v tramp-perl-file-name-all-completions + "tramp_perl_file_name_all_completions") + (format "tramp_perl_file_name_all_completions %s %s %d" + (tramp-shell-quote-argument localname) + (tramp-shell-quote-argument filename) + (if (symbol-value + ;; `read-file-name-completion-ignore-case' + ;; is introduced with Emacs 22.1. + (if (boundp + 'read-file-name-completion-ignore-case) + 'read-file-name-completion-ignore-case + 'completion-ignore-case)) + 1 0))) + + (format (concat + "(\\cd %s 2>&1 && (%s %s -a 2>/dev/null" + ;; `ls' with wildcard might fail with `Argument + ;; list too long' error in some corner cases; if + ;; `ls' fails after `cd' succeeded, chances are + ;; that's the case, so let's retry without + ;; wildcard. This will return "too many" entries + ;; but that isn't harmful. + " || %s -a 2>/dev/null)" + " | while read f; do" + " if %s -d \"$f\" 2>/dev/null;" + " then \\echo \"$f/\"; else \\echo \"$f\"; fi; done" + " && \\echo ok) || \\echo fail") + (tramp-shell-quote-argument localname) + (tramp-get-ls-command v) + ;; When `filename' is empty, just `ls' without + ;; filename argument is more efficient than `ls *' + ;; for very large directories and might avoid the + ;; `Argument list too long' error. + ;; + ;; With and only with wildcard, we need to add + ;; `-d' to prevent `ls' from descending into + ;; sub-directories. + (if (zerop (length filename)) + "." + (concat (tramp-shell-quote-argument filename) "* -d")) + (tramp-get-ls-command v) + (tramp-get-test-command v)))) + + ;; Now grab the output. + (with-current-buffer (tramp-get-buffer v) + (goto-char (point-max)) + + ;; Check result code, found in last line of output + (forward-line -1) + (if (looking-at "^fail$") + (progn + ;; Grab error message from line before last line + ;; (it was put there by `cd 2>&1') + (forward-line -1) + (tramp-error + v 'file-error + "tramp-sh-handle-file-name-all-completions: %s" + (buffer-substring (point) (point-at-eol)))) + ;; For peace of mind, if buffer doesn't end in `fail' + ;; then it should end in `ok'. If neither are in the + ;; buffer something went seriously wrong on the remote + ;; side. + (unless (looking-at "^ok$") + (tramp-error + v 'file-error + "\ +tramp-sh-handle-file-name-all-completions: internal error accessing `%s': `%s'" + (tramp-shell-quote-argument localname) (buffer-string)))) + + (while (zerop (forward-line -1)) + (push (buffer-substring (point) (point-at-eol)) result))) + + ;; Because the remote op went through OK we know the + ;; directory we `cd'-ed to exists + (tramp-set-file-property + v localname "file-exists-p" t) + + ;; Because the remote op went through OK we know every + ;; file listed by `ls' exists. + (mapc (lambda (entry) + (tramp-set-file-property + v (concat localname entry) "file-exists-p" t)) + result) + + ;; Store result in the cache + (tramp-set-file-property + v (concat localname filename) + "file-name-all-completions" + result)))))))) + +;; cp, mv and ln + +(defun tramp-sh-handle-add-name-to-file + (filename newname &optional ok-if-already-exists) + "Like `add-name-to-file' for Tramp files." + (unless (tramp-equal-remote filename newname) + (with-parsed-tramp-file-name + (if (tramp-tramp-file-p filename) filename newname) nil + (tramp-error + v 'file-error + "add-name-to-file: %s" + "only implemented for same method, same user, same host"))) + (with-parsed-tramp-file-name filename v1 + (with-parsed-tramp-file-name newname v2 + (let ((ln (when v1 (tramp-get-remote-ln v1)))) + (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))) + (tramp-error + v2 'file-error + "add-name-to-file: file %s already exists" newname)) + (tramp-flush-file-property v2 (file-name-directory v2-localname)) + (tramp-flush-file-property v2 v2-localname) + (tramp-barf-unless-okay + v1 + (format "%s %s %s" ln (tramp-shell-quote-argument v1-localname) + (tramp-shell-quote-argument v2-localname)) + "error with add-name-to-file, see buffer `%s' for details" + (buffer-name)))))) + +(defun tramp-sh-handle-copy-file + (filename newname &optional ok-if-already-exists keep-date + preserve-uid-gid preserve-selinux-context) + "Like `copy-file' for Tramp files." + (setq filename (expand-file-name filename)) + (setq newname (expand-file-name newname)) + (cond + ;; At least one file a Tramp file? + ((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 + preserve-uid-gid preserve-selinux-context)) + ;; Compat section. + (preserve-selinux-context + (tramp-run-real-handler + 'copy-file + (list filename newname ok-if-already-exists keep-date + preserve-uid-gid preserve-selinux-context))) + (preserve-uid-gid + (tramp-run-real-handler + 'copy-file + (list filename newname ok-if-already-exists keep-date preserve-uid-gid))) + (t + (tramp-run-real-handler + 'copy-file (list filename newname ok-if-already-exists keep-date))))) + +(defun tramp-sh-handle-copy-directory + (dirname newname &optional keep-date parents) + "Like `copy-directory' for Tramp files." + (let ((t1 (tramp-tramp-file-p dirname)) + (t2 (tramp-tramp-file-p newname))) + (with-parsed-tramp-file-name (if t1 dirname newname) nil + (if (and (tramp-get-method-parameter method 'tramp-copy-recursive) + ;; When DIRNAME and NEWNAME are remote, they must have + ;; the same method. + (or (null t1) (null t2) + (string-equal + (tramp-file-name-method (tramp-dissect-file-name dirname)) + (tramp-file-name-method (tramp-dissect-file-name newname))))) + ;; scp or rsync DTRT. + (progn + (setq dirname (directory-file-name (expand-file-name dirname)) + newname (directory-file-name (expand-file-name newname))) + (if (and (file-directory-p newname) + (not (string-equal (file-name-nondirectory dirname) + (file-name-nondirectory newname)))) + (setq newname + (expand-file-name + (file-name-nondirectory dirname) newname))) + (if (not (file-directory-p (file-name-directory newname))) + (make-directory (file-name-directory newname) parents)) + (tramp-do-copy-or-rename-file-out-of-band + 'copy dirname newname keep-date)) + ;; We must do it file-wise. + (tramp-run-real-handler + 'copy-directory (list dirname newname keep-date parents))) + + ;; When newname did exist, we have wrong cached values. + (when t2 + (with-parsed-tramp-file-name newname nil + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname)))))) + +(defun tramp-sh-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 t t) + (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 + preserve-uid-gid preserve-selinux-context) + "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. PRESERVE-UID-GID, when non-nil, instructs to keep +the uid and gid if both files are on the same host. +PRESERVE-SELINUX-CONTEXT activates selinux commands. + +This function is invoked by `tramp-sh-handle-copy-file' and +`tramp-sh-handle-rename-file'. It is an error if OP is neither +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)) + (let ((t1 (tramp-tramp-file-p filename)) + (t2 (tramp-tramp-file-p newname)) + (context (and preserve-selinux-context + (apply 'file-selinux-context (list filename)))) + pr tm) + + (with-parsed-tramp-file-name (if t1 filename newname) nil + (when (and (not ok-if-already-exists) (file-exists-p newname)) + (tramp-error + v 'file-already-exists "File %s already exists" newname)) + + (with-progress-reporter + v 0 (format "%s %s to %s" + (if (eq op 'copy) "Copying" "Renaming") + filename newname) + + (cond + ;; Both are Tramp files. + ((and t1 t2) + (with-parsed-tramp-file-name filename v1 + (with-parsed-tramp-file-name newname v2 + (cond + ;; Shortcut: if method, host, user are the same for + ;; both files, we invoke `cp' or `mv' on the remote + ;; host directly. + ((tramp-equal-remote filename newname) + (tramp-do-copy-or-rename-file-directly + op filename newname + ok-if-already-exists keep-date preserve-uid-gid)) + + ;; Try out-of-band operation. + ((tramp-method-out-of-band-p + v1 (nth 7 (file-attributes (file-truename filename)))) + (tramp-do-copy-or-rename-file-out-of-band + op filename newname keep-date)) + + ;; No shortcut was possible. So we copy the file + ;; first. If the operation was `rename', we go back + ;; and delete the original file (if the copy was + ;; successful). The approach is simple-minded: we + ;; create a new buffer, insert the contents of the + ;; source file into it, then write out the buffer to + ;; the target file. The advantage is that it doesn't + ;; matter which filename handlers are used for the + ;; source and target file. + (t + (tramp-do-copy-or-rename-file-via-buffer + op filename newname keep-date)))))) + + ;; One file is a Tramp file, the other one is local. + ((or t1 t2) + (cond + ;; Fast track on local machine. + ((tramp-local-host-p v) + (tramp-do-copy-or-rename-file-directly + op filename newname + ok-if-already-exists keep-date preserve-uid-gid)) + + ;; If the Tramp file has an out-of-band method, the + ;; corresponding copy-program can be invoked. + ((tramp-method-out-of-band-p + v (nth 7 (file-attributes (file-truename filename)))) + (tramp-do-copy-or-rename-file-out-of-band + op filename newname keep-date)) + + ;; Use the inline method via a Tramp buffer. + (t (tramp-do-copy-or-rename-file-via-buffer + op filename newname keep-date)))) + + (t + ;; One of them must be a Tramp file. + (error "Tramp implementation says this cannot happen"))) + + ;; Handle `preserve-selinux-context'. + (when context (apply 'set-file-selinux-context (list newname context))) + + ;; In case of `rename', we must flush the cache of the source file. + (when (and t1 (eq op 'rename)) + (with-parsed-tramp-file-name filename v1 + (tramp-flush-file-property v1 (file-name-directory localname)) + (tramp-flush-file-property v1 localname))) + + ;; When newname did exist, we have wrong cached values. + (when t2 + (with-parsed-tramp-file-name newname v2 + (tramp-flush-file-property v2 (file-name-directory localname)) + (tramp-flush-file-property v2 localname))))))) + +(defun tramp-do-copy-or-rename-file-via-buffer (op filename newname keep-date) + "Use an Emacs buffer to copy or rename a file. +First arg OP is either `copy' or `rename' and indicates the operation. +FILENAME is the source file, NEWNAME the target file. +KEEP-DATE is non-nil if NEWNAME should have the same timestamp as FILENAME." + (with-temp-buffer + ;; We must disable multibyte, because binary data shall not be + ;; converted. + (set-buffer-multibyte nil) + (let ((coding-system-for-read 'binary) + (jka-compr-inhibit t)) + (insert-file-contents-literally filename)) + ;; We don't want the target file to be compressed, so we let-bind + ;; `jka-compr-inhibit' to t. + (let ((coding-system-for-write 'binary) + (jka-compr-inhibit t)) + (write-region (point-min) (point-max) newname))) + ;; KEEP-DATE handling. + (when keep-date (set-file-times newname (nth 5 (file-attributes filename)))) + ;; Set the mode. + (set-file-modes newname (tramp-default-file-modes filename)) + ;; If the operation was `rename', delete the original file. + (unless (eq op 'copy) (delete-file filename))) + +(defun tramp-do-copy-or-rename-file-directly + (op filename newname ok-if-already-exists keep-date preserve-uid-gid) + "Invokes `cp' or `mv' on the remote system. +OP must be one of `copy' or `rename', indicating `cp' or `mv', +respectively. 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). Both files must reside on the same host. +KEEP-DATE means to make sure that NEWNAME has the same timestamp +as FILENAME. PRESERVE-UID-GID, when non-nil, instructs to keep +the uid and gid from FILENAME." + (let ((t1 (tramp-tramp-file-p filename)) + (t2 (tramp-tramp-file-p newname)) + (file-times (nth 5 (file-attributes filename))) + (file-modes (tramp-default-file-modes filename))) + (with-parsed-tramp-file-name (if t1 filename newname) nil + (let* ((cmd (cond ((and (eq op 'copy) preserve-uid-gid) "cp -f -p") + ((eq op 'copy) "cp -f") + ((eq op 'rename) "mv -f") + (t (tramp-error + v 'file-error + "Unknown operation `%s', must be `copy' or `rename'" + op)))) + (localname1 + (if t1 + (tramp-file-name-handler 'file-remote-p filename 'localname) + filename)) + (localname2 + (if t2 + (tramp-file-name-handler 'file-remote-p newname 'localname) + newname)) + (prefix (file-remote-p (if t1 filename newname))) + cmd-result) + + (cond + ;; Both files are on a remote host, with same user. + ((and t1 t2) + (setq cmd-result + (tramp-send-command-and-check + v (format "%s %s %s" cmd + (tramp-shell-quote-argument localname1) + (tramp-shell-quote-argument localname2)))) + (with-current-buffer (tramp-get-buffer v) + (goto-char (point-min)) + (unless + (or + (and keep-date + ;; Mask cp -f error. + (re-search-forward + tramp-operation-not-permitted-regexp nil t)) + cmd-result) + (tramp-error-with-buffer + nil v 'file-error + "Copying directly failed, see buffer `%s' for details." + (buffer-name))))) + + ;; We are on the local host. + ((or t1 t2) + (cond + ;; We can do it directly. + ((let (file-name-handler-alist) + (and (file-readable-p localname1) + (file-writable-p (file-name-directory localname2)) + (or (file-directory-p localname2) + (file-writable-p localname2)))) + (if (eq op 'copy) + (tramp-compat-copy-file + localname1 localname2 ok-if-already-exists + keep-date preserve-uid-gid) + (tramp-run-real-handler + 'rename-file (list localname1 localname2 ok-if-already-exists)))) + + ;; We can do it directly with `tramp-send-command' + ((and (file-readable-p (concat prefix localname1)) + (file-writable-p + (file-name-directory (concat prefix localname2))) + (or (file-directory-p (concat prefix localname2)) + (file-writable-p (concat prefix localname2)))) + (tramp-do-copy-or-rename-file-directly + op (concat prefix localname1) (concat prefix localname2) + ok-if-already-exists keep-date t) + ;; We must change the ownership to the local user. + (tramp-set-file-uid-gid + (concat prefix localname2) + (tramp-get-local-uid 'integer) + (tramp-get-local-gid 'integer))) + + ;; We need a temporary file in between. + (t + ;; Create the temporary file. + (let ((tmpfile (tramp-compat-make-temp-file localname1))) + (unwind-protect + (progn + (cond + (t1 + (tramp-barf-unless-okay + v (format + "%s %s %s" cmd + (tramp-shell-quote-argument localname1) + (tramp-shell-quote-argument tmpfile)) + "Copying directly failed, see buffer `%s' for details." + (tramp-get-buffer v)) + ;; We must change the ownership as remote user. + ;; Since this does not work reliable, we also + ;; give read permissions. + (set-file-modes + (concat prefix tmpfile) + (tramp-compat-octal-to-decimal "0777")) + (tramp-set-file-uid-gid + (concat prefix tmpfile) + (tramp-get-local-uid 'integer) + (tramp-get-local-gid 'integer))) + (t2 + (if (eq op 'copy) + (tramp-compat-copy-file + localname1 tmpfile t + keep-date preserve-uid-gid) + (tramp-run-real-handler + 'rename-file + (list localname1 tmpfile t))) + ;; We must change the ownership as local user. + ;; Since this does not work reliable, we also + ;; give read permissions. + (set-file-modes + tmpfile (tramp-compat-octal-to-decimal "0777")) + (tramp-set-file-uid-gid + tmpfile + (tramp-get-remote-uid v 'integer) + (tramp-get-remote-gid v 'integer)))) + + ;; Move the temporary file to its destination. + (cond + (t2 + (tramp-barf-unless-okay + v (format + "cp -f -p %s %s" + (tramp-shell-quote-argument tmpfile) + (tramp-shell-quote-argument localname2)) + "Copying directly failed, see buffer `%s' for details." + (tramp-get-buffer v))) + (t1 + (tramp-run-real-handler + 'rename-file + (list tmpfile localname2 ok-if-already-exists))))) + + ;; Save exit. + (ignore-errors (delete-file tmpfile))))))))) + + ;; Set the time and mode. Mask possible errors. + (ignore-errors + (when keep-date + (set-file-times newname file-times) + (set-file-modes newname file-modes)))))) + +(defun tramp-do-copy-or-rename-file-out-of-band (op filename newname keep-date) + "Invoke rcp program to copy. +The method used must be an out-of-band method." + (let* ((t1 (tramp-tramp-file-p filename)) + (t2 (tramp-tramp-file-p newname)) + (orig-vec (tramp-dissect-file-name (if t1 filename newname))) + copy-program copy-args copy-env copy-keep-date port spec + source target) + + (with-parsed-tramp-file-name (if t1 filename newname) nil + (if (and t1 t2) + + ;; Both are Tramp files. We shall optimize it, when the + ;; methods for filename and newname are the same. + (let* ((dir-flag (file-directory-p filename)) + (tmpfile (tramp-compat-make-temp-file localname dir-flag))) + (if dir-flag + (setq tmpfile + (expand-file-name + (file-name-nondirectory newname) tmpfile))) + (unwind-protect + (progn + (tramp-do-copy-or-rename-file-out-of-band + op filename tmpfile keep-date) + (tramp-do-copy-or-rename-file-out-of-band + 'rename tmpfile newname keep-date)) + ;; Save exit. + (ignore-errors + (if dir-flag + (tramp-compat-delete-directory + (expand-file-name ".." tmpfile) 'recursive) + (delete-file tmpfile))))) + + ;; Set variables for computing the prompt for reading + ;; password. + (setq tramp-current-method (tramp-file-name-method v) + tramp-current-user (tramp-file-name-user v) + tramp-current-host (tramp-file-name-host v)) + + ;; Expand hops. Might be necessary for gateway methods. + (setq v (car (tramp-compute-multi-hops v))) + (aset v 3 localname) + + ;; Check which ones of source and target are Tramp files. + (setq source (if t1 (tramp-make-copy-program-file-name v) filename) + target (funcall + (if (and (file-directory-p filename) + (string-equal + (file-name-nondirectory filename) + (file-name-nondirectory newname))) + 'file-name-directory + 'identity) + (if t2 (tramp-make-copy-program-file-name v) newname))) + + ;; Check for port number. Until now, there's no need for handling + ;; like method, user, host. + (setq host (tramp-file-name-real-host v) + port (tramp-file-name-port v) + port (or (and port (number-to-string port)) "")) + + ;; Compose copy command. + (setq spec (format-spec-make + ?h host ?u user ?p port + ?t (tramp-get-connection-property + (tramp-get-connection-process v) "temp-file" "") + ?k (if keep-date " " "")) + copy-program (tramp-get-method-parameter + method 'tramp-copy-program) + copy-keep-date (tramp-get-method-parameter + method 'tramp-copy-keep-date) + copy-args + (delete + ;; " " has either been a replacement of "%k" (when + ;; keep-date argument is non-nil), or a replacemtent + ;; for the whole keep-date sublist. + " " + (dolist + (x + (tramp-get-method-parameter method 'tramp-copy-args) + copy-args) + (setq copy-args + (append + copy-args + (let ((y (mapcar (lambda (z) (format-spec z spec)) x))) + (if (zerop (length (car y))) '(" ") y)))))) + copy-env + (delq + nil + (mapcar + (lambda (x) + (setq x (mapcar (lambda (y) (format-spec y spec)) x)) + (unless (member "" x) (mapconcat 'identity x " "))) + (tramp-get-method-parameter method 'tramp-copy-env)))) + + ;; Check for program. + (when (and (fboundp 'executable-find) + (not (let ((default-directory + (tramp-compat-temporary-file-directory))) + (executable-find copy-program)))) + (tramp-error + v 'file-error "Cannot find copy program: %s" copy-program)) + + (with-temp-buffer + (unwind-protect + ;; The default directory must be remote. + (let ((default-directory + (file-name-directory (if t1 filename newname))) + (process-environment (copy-sequence process-environment))) + ;; Set the transfer process properties. + (tramp-set-connection-property + v "process-name" (buffer-name (current-buffer))) + (tramp-set-connection-property + v "process-buffer" (current-buffer)) + (while copy-env + (tramp-message + orig-vec 5 "%s=\"%s\"" (car copy-env) (cadr copy-env)) + (setenv (pop copy-env) (pop copy-env))) + + ;; Use an asynchronous process. By this, password can + ;; be handled. The default directory must be local, in + ;; order to apply the correct `copy-program'. We don't + ;; set a timeout, because the copying of large files can + ;; last longer than 60 secs. + (let ((p (let ((default-directory + (tramp-compat-temporary-file-directory))) + (apply 'start-process + (tramp-get-connection-name v) + (tramp-get-connection-buffer v) + copy-program + (append copy-args (list source target)))))) + (tramp-message + orig-vec 6 "%s" + (mapconcat 'identity (process-command p) " ")) + (tramp-compat-set-process-query-on-exit-flag p nil) + (tramp-process-actions p v tramp-actions-copy-out-of-band))) + + ;; Reset the transfer process properties. + (tramp-message orig-vec 6 "%s" (buffer-string)) + (tramp-set-connection-property v "process-name" nil) + (tramp-set-connection-property v "process-buffer" nil))) + + ;; Handle KEEP-DATE argument. + (when (and keep-date (not copy-keep-date)) + (set-file-times newname (nth 5 (file-attributes filename)))) + + ;; Set the mode. + (unless (and keep-date copy-keep-date) + (ignore-errors + (set-file-modes newname (tramp-default-file-modes filename))))) + + ;; If the operation was `rename', delete the original file. + (unless (eq op 'copy) + (if (file-regular-p filename) + (delete-file filename) + (tramp-compat-delete-directory filename 'recursive)))))) + +(defun tramp-sh-handle-make-directory (dir &optional parents) + "Like `make-directory' for Tramp files." + (setq dir (expand-file-name dir)) + (with-parsed-tramp-file-name dir nil + (tramp-flush-directory-property v (file-name-directory localname)) + (save-excursion + (tramp-barf-unless-okay + v (format "%s %s" + (if parents "mkdir -p" "mkdir") + (tramp-shell-quote-argument localname)) + "Couldn't make directory %s" dir)))) + +(defun tramp-sh-handle-delete-directory (directory &optional recursive) + "Like `delete-directory' for Tramp files." + (setq directory (expand-file-name directory)) + (with-parsed-tramp-file-name directory nil + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-directory-property v localname) + (tramp-barf-unless-okay + v (format "%s %s" + (if recursive "rm -rf" "rmdir") + (tramp-shell-quote-argument localname)) + "Couldn't delete %s" directory))) + +(defun tramp-sh-handle-delete-file (filename &optional trash) + "Like `delete-file' for Tramp files." + (setq filename (expand-file-name filename)) + (with-parsed-tramp-file-name filename nil + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + (tramp-barf-unless-okay + v (format "%s %s" + (or (and trash (tramp-get-remote-trash v)) "rm -f") + (tramp-shell-quote-argument localname)) + "Couldn't delete %s" filename))) + +;; Dired. + +;; CCC: This does not seem to be enough. Something dies when +;; we try and delete two directories under Tramp :/ +(defun tramp-sh-handle-dired-recursive-delete-directory (filename) + "Recursively delete the directory given. +This is like `dired-recursive-delete-directory' for Tramp files." + (with-parsed-tramp-file-name filename nil + ;; Run a shell command 'rm -r ' + ;; Code shamelessly stolen from the dired implementation and, um, hacked :) + (unless (file-exists-p filename) + (tramp-error v 'file-error "No such directory: %s" filename)) + ;; Which is better, -r or -R? (-r works for me ) + (tramp-send-command + v + (format "rm -rf %s" (tramp-shell-quote-argument localname)) + ;; Don't read the output, do it explicitely. + nil t) + ;; Wait for the remote system to return to us... + ;; This might take a while, allow it plenty of time. + (tramp-wait-for-output (tramp-get-connection-process v) 120) + ;; Make sure that it worked... + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-directory-property v localname) + (and (file-exists-p filename) + (tramp-error + v 'file-error "Failed to recursively delete %s" filename)))) + +(defun tramp-sh-handle-dired-compress-file (file &rest ok-flag) + "Like `dired-compress-file' for Tramp files." + ;; OK-FLAG is valid for XEmacs only, but not implemented. + ;; Code stolen mainly from dired-aux.el. + (with-parsed-tramp-file-name file nil + (tramp-flush-file-property v localname) + (save-excursion + (let ((suffixes + (if (not (featurep 'xemacs)) + ;; Emacs case + (symbol-value 'dired-compress-file-suffixes) + ;; XEmacs has `dired-compression-method-alist', which is + ;; transformed into `dired-compress-file-suffixes' structure. + (mapcar + (lambda (x) + (list (concat (regexp-quote (nth 1 x)) "\\'") + nil + (mapconcat 'identity (nth 3 x) " "))) + (symbol-value 'dired-compression-method-alist)))) + suffix) + ;; See if any suffix rule matches this file name. + (while suffixes + (let (case-fold-search) + (if (string-match (car (car suffixes)) localname) + (setq suffix (car suffixes) suffixes nil)) + (setq suffixes (cdr suffixes)))) + + (cond ((file-symlink-p file) + nil) + ((and suffix (nth 2 suffix)) + ;; We found an uncompression rule. + (with-progress-reporter v 0 (format "Uncompressing %s" file) + (when (tramp-send-command-and-check + v (concat (nth 2 suffix) " " + (tramp-shell-quote-argument localname))) + ;; `dired-remove-file' is not defined in XEmacs. + (tramp-compat-funcall 'dired-remove-file file) + (string-match (car suffix) file) + (concat (substring file 0 (match-beginning 0)))))) + (t + ;; We don't recognize the file as compressed, so compress it. + ;; Try gzip. + (with-progress-reporter v 0 (format "Compressing %s" file) + (when (tramp-send-command-and-check + v (concat "gzip -f " + (tramp-shell-quote-argument localname))) + ;; `dired-remove-file' is not defined in XEmacs. + (tramp-compat-funcall 'dired-remove-file file) + (cond ((file-exists-p (concat file ".gz")) + (concat file ".gz")) + ((file-exists-p (concat file ".z")) + (concat file ".z")) + (t nil)))))))))) + +(defun tramp-sh-handle-insert-directory + (filename switches &optional wildcard full-directory-p) + "Like `insert-directory' for Tramp files." + (setq filename (expand-file-name filename)) + (with-parsed-tramp-file-name filename nil + (if (and (featurep 'ls-lisp) + (not (symbol-value 'ls-lisp-use-insert-directory-program))) + (tramp-run-real-handler + 'insert-directory (list filename switches wildcard full-directory-p)) + (when (stringp switches) + (setq switches (split-string switches))) + (when (and (member "--dired" switches) + (not (tramp-get-ls-command-with-dired v))) + (setq switches (delete "--dired" switches))) + (when wildcard + (setq wildcard (tramp-run-real-handler + 'file-name-nondirectory (list localname))) + (setq localname (tramp-run-real-handler + 'file-name-directory (list localname)))) + (unless full-directory-p + (setq switches (add-to-list 'switches "-d" 'append))) + (setq switches (mapconcat 'tramp-shell-quote-argument switches " ")) + (when wildcard + (setq switches (concat switches " " wildcard))) + (tramp-message + v 4 "Inserting directory `ls %s %s', wildcard %s, fulldir %s" + switches filename (if wildcard "yes" "no") + (if full-directory-p "yes" "no")) + ;; 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 + v + (format "%s %s %s 2>/dev/null" + (tramp-get-ls-command v) + switches + (if wildcard + localname + (tramp-shell-quote-argument (concat localname "."))))) + (tramp-barf-unless-okay + v + (format "cd %s" (tramp-shell-quote-argument + (tramp-run-real-handler + 'file-name-directory (list localname)))) + "Couldn't `cd %s'" + (tramp-shell-quote-argument + (tramp-run-real-handler 'file-name-directory (list localname)))) + (tramp-send-command + v + (format "%s %s %s" + (tramp-get-ls-command v) + switches + (if (or wildcard + (zerop (length + (tramp-run-real-handler + 'file-name-nondirectory (list localname))))) + "" + (tramp-shell-quote-argument + (tramp-run-real-handler + 'file-name-nondirectory (list localname))))))) + (let ((beg (point))) + ;; We cannot use `insert-buffer-substring' because the Tramp + ;; buffer changes its contents before insertion due to calling + ;; `expand-file' and alike. + (insert + (with-current-buffer (tramp-get-buffer v) + (buffer-string))) + + ;; Check for "--dired" output. + (forward-line -2) + (when (looking-at "//SUBDIRED//") + (forward-line -1)) + (when (looking-at "//DIRED//\\s-+") + (let ((databeg (match-end 0)) + (end (point-at-eol))) + ;; Now read the numeric positions of file names. + (goto-char databeg) + (while (< (point) end) + (let ((start (+ beg (read (current-buffer)))) + (end (+ beg (read (current-buffer))))) + (if (memq (char-after end) '(?\n ?\ )) + ;; End is followed by \n or by " -> ". + (put-text-property start end 'dired-filename t)))))) + ;; Remove trailing lines. + (goto-char (point-at-bol)) + (while (looking-at "//") + (forward-line 1) + (delete-region (match-beginning 0) (point))) + + ;; The inserted file could be from somewhere else. + (when (and (not wildcard) (not full-directory-p)) + (goto-char (point-max)) + (when (file-symlink-p filename) + (goto-char (search-backward "->" beg 'noerror))) + (search-backward + (if (zerop (length (file-name-nondirectory filename))) + "." + (file-name-nondirectory filename)) + beg 'noerror) + (replace-match (file-relative-name filename) t)) + + (goto-char (point-max)))))) + +;; Canonicalization of file names. + +(defun tramp-sh-handle-expand-file-name (name &optional dir) + "Like `expand-file-name' for Tramp files. +If the localname part of the given filename starts with \"/../\" then +the result will be a local, non-Tramp, filename." + ;; 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-connectable-p name)) + (tramp-run-real-handler 'expand-file-name (list name nil)) + ;; Dissect NAME. + (with-parsed-tramp-file-name name nil + (unless (tramp-run-real-handler 'file-name-absolute-p (list localname)) + (setq localname (concat "~/" localname))) + ;; 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 "\\`\\(~[^/]*\\)\\(.*\\)\\'" localname) + (let ((uname (match-string 1 localname)) + (fname (match-string 2 localname))) + ;; We cannot simply apply "~/", because under sudo "~/" is + ;; expanded to the local user home directory but to the + ;; root home directory. On the other hand, using always + ;; the default user name for tilde expansion is not + ;; appropriate either, because ssh and companions might + ;; use a user name from the config file. + (when (and (string-equal uname "~") + (string-match "\\`su\\(do\\)?\\'" method)) + (setq uname (concat uname user))) + (setq uname + (with-connection-property v uname + (tramp-send-command + v (format "cd %s; pwd" (tramp-shell-quote-argument uname))) + (with-current-buffer (tramp-get-buffer v) + (goto-char (point-min)) + (buffer-substring (point) (point-at-eol))))) + (setq localname (concat uname fname)))) + ;; There might be a double slash, for example when "~/" + ;; expands to "/". Remove this. + (while (string-match "//" localname) + (setq localname (replace-match "/" t t localname))) + ;; 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. `default-directory' is bound, + ;; because on Windows there would be problems with UNC shares or + ;; Cygwin mounts. + (let ((directory-sep-char ?/) + (default-directory (tramp-compat-temporary-file-directory))) + (tramp-make-tramp-file-name + method user host + (tramp-drop-volume-letter + (tramp-run-real-handler + 'expand-file-name (list localname)))))))) + +;;; Remote commands: + +(defun tramp-sh-handle-executable-find (command) + "Like `executable-find' for Tramp files." + (with-parsed-tramp-file-name default-directory nil + (tramp-find-executable v command (tramp-get-remote-path v) t))) + +(defun tramp-process-sentinel (proc event) + "Flush file caches." + (unless (memq (process-status proc) '(run open)) + (let ((vec (tramp-get-connection-property proc "vector" nil))) + (when vec + (tramp-message vec 5 "Sentinel called: `%s' `%s'" proc event) + (tramp-flush-directory-property vec ""))))) + +;; We use BUFFER also as connection buffer during setup. Because of +;; this, its original contents must be saved, and restored once +;; connection has been setup. +(defun tramp-sh-handle-start-file-process (name buffer program &rest args) + "Like `start-file-process' for Tramp files." + (with-parsed-tramp-file-name default-directory nil + ;; When PROGRAM is nil, we just provide a tty. + (let ((command + (when (stringp program) + (format "cd %s; exec %s" + (tramp-shell-quote-argument localname) + (mapconcat 'tramp-shell-quote-argument + (cons program args) " ")))) + (tramp-process-connection-type + (or (null program) tramp-process-connection-type)) + (bmp (and (buffer-live-p buffer) (buffer-modified-p buffer))) + (name1 name) + (i 0)) + (unwind-protect + (save-excursion + (save-restriction + (unless buffer + ;; BUFFER can be nil. We use a temporary buffer. + (setq buffer (generate-new-buffer tramp-temp-buffer-name))) + (while (get-process name1) + ;; NAME must be unique as process name. + (setq i (1+ i) + name1 (format "%s<%d>" name i))) + (setq name name1) + ;; Set the new process properties. + (tramp-set-connection-property v "process-name" name) + (tramp-set-connection-property v "process-buffer" buffer) + ;; Activate narrowing in order to save BUFFER contents. + ;; Clear also the modification time; otherwise we might + ;; be interrupted by `verify-visited-file-modtime'. + (with-current-buffer (tramp-get-connection-buffer v) + (let ((buffer-undo-list t)) + (clear-visited-file-modtime) + (narrow-to-region (point-max) (point-max)) + (if command + ;; Send the command. + (tramp-send-command v command nil t) ; nooutput + ;; Check, whether a pty is associated. + (tramp-maybe-open-connection v) + (unless (tramp-compat-process-get + (tramp-get-connection-process v) 'remote-tty) + (tramp-error + v 'file-error + "pty association is not supported for `%s'" name))))) + (let ((p (tramp-get-connection-process v))) + ;; Set sentinel and query flag for this process. + (tramp-set-connection-property p "vector" v) + (set-process-sentinel p 'tramp-process-sentinel) + (tramp-compat-set-process-query-on-exit-flag p t) + ;; Return process. + p))) + ;; Save exit. + (with-current-buffer (tramp-get-connection-buffer v) + (if (string-match tramp-temp-buffer-name (buffer-name)) + (progn + (set-process-buffer (tramp-get-connection-process v) nil) + (kill-buffer (current-buffer))) + (set-buffer-modified-p bmp))) + (tramp-set-connection-property v "process-name" nil) + (tramp-set-connection-property v "process-buffer" nil))))) + +(defun tramp-sh-handle-process-file + (program &optional infile destination display &rest args) + "Like `process-file' for Tramp files." + ;; The implementation is not complete yet. + (when (and (numberp destination) (zerop destination)) + (error "Implementation does not handle immediate return")) + + (with-parsed-tramp-file-name default-directory nil + (let (command input tmpinput stderr tmpstderr outbuf ret) + ;; Compute command. + (setq command (mapconcat 'tramp-shell-quote-argument + (cons program args) " ")) + ;; Determine input. + (if (null infile) + (setq input "/dev/null") + (setq infile (expand-file-name infile)) + (if (tramp-equal-remote default-directory infile) + ;; INFILE is on the same remote host. + (setq input (with-parsed-tramp-file-name infile nil localname)) + ;; INFILE must be copied to remote host. + (setq input (tramp-make-tramp-temp-file v) + tmpinput (tramp-make-tramp-file-name method user host input)) + (copy-file infile tmpinput t))) + (when input (setq command (format "%s <%s" command input))) + + ;; Determine output. + (cond + ;; Just a buffer. + ((bufferp destination) + (setq outbuf destination)) + ;; A buffer name. + ((stringp destination) + (setq outbuf (get-buffer-create destination))) + ;; (REAL-DESTINATION ERROR-DESTINATION) + ((consp destination) + ;; output. + (cond + ((bufferp (car destination)) + (setq outbuf (car destination))) + ((stringp (car destination)) + (setq outbuf (get-buffer-create (car destination)))) + ((car destination) + (setq outbuf (current-buffer)))) + ;; stderr. + (cond + ((stringp (cadr destination)) + (setcar (cdr destination) (expand-file-name (cadr destination))) + (if (tramp-equal-remote default-directory (cadr destination)) + ;; stderr is on the same remote host. + (setq stderr (with-parsed-tramp-file-name + (cadr destination) nil localname)) + ;; stderr must be copied to remote host. The temporary + ;; file must be deleted after execution. + (setq stderr (tramp-make-tramp-temp-file v) + tmpstderr (tramp-make-tramp-file-name + method user host stderr)))) + ;; stderr to be discarded. + ((null (cadr destination)) + (setq stderr "/dev/null")))) + ;; 't + (destination + (setq outbuf (current-buffer)))) + (when stderr (setq command (format "%s 2>%s" command stderr))) + + ;; Send the command. It might not return in time, so we protect + ;; it. Call it in a subshell, in order to preserve working + ;; directory. + (condition-case nil + (unwind-protect + (setq ret + (if (tramp-send-command-and-check + v (format "\\cd %s; %s" + (tramp-shell-quote-argument localname) + command) + t t) + 0 1)) + ;; We should show the output anyway. + (when outbuf + (with-current-buffer outbuf + (insert + (with-current-buffer (tramp-get-connection-buffer v) + (buffer-string)))) + (when display (display-buffer outbuf)))) + ;; When the user did interrupt, we should do it also. We use + ;; return code -1 as marker. + (quit + (kill-buffer (tramp-get-connection-buffer v)) + (setq ret -1)) + ;; Handle errors. + (error + (kill-buffer (tramp-get-connection-buffer v)) + (setq ret 1))) + + ;; Provide error file. + (when tmpstderr (rename-file tmpstderr (cadr destination) t)) + + ;; Cleanup. We remove all file cache values for the connection, + ;; because the remote process could have changed them. + (when tmpinput (delete-file tmpinput)) + + ;; `process-file-side-effects' has been introduced with GNU + ;; Emacs 23.2. If set to `nil', no remote file will be changed + ;; by `program'. If it doesn't exist, we assume its default + ;; value 't'. + (unless (and (boundp 'process-file-side-effects) + (not (symbol-value 'process-file-side-effects))) + (tramp-flush-directory-property v "")) + + ;; Return exit status. + (if (equal ret -1) + (keyboard-quit) + ret)))) + +(defun tramp-sh-handle-call-process-region + (start end program &optional delete buffer display &rest args) + "Like `call-process-region' for Tramp files." + (let ((tmpfile (tramp-compat-make-temp-file ""))) + (write-region start end tmpfile) + (when delete (delete-region start end)) + (unwind-protect + (apply 'call-process program tmpfile buffer display args) + (delete-file tmpfile)))) + +(defun tramp-sh-handle-shell-command + (command &optional output-buffer error-buffer) + "Like `shell-command' for Tramp files." + (let* ((asynchronous (string-match "[ \t]*&[ \t]*\\'" command)) + ;; We cannot use `shell-file-name' and `shell-command-switch', + ;; they are variables of the local host. + (args (list + (tramp-get-method-parameter + (tramp-file-name-method + (tramp-dissect-file-name default-directory)) + 'tramp-remote-sh) + "-c" (substring command 0 asynchronous))) + current-buffer-p + (output-buffer + (cond + ((bufferp output-buffer) output-buffer) + ((stringp output-buffer) (get-buffer-create output-buffer)) + (output-buffer + (setq current-buffer-p t) + (current-buffer)) + (t (get-buffer-create + (if asynchronous + "*Async Shell Command*" + "*Shell Command Output*"))))) + (error-buffer + (cond + ((bufferp error-buffer) error-buffer) + ((stringp error-buffer) (get-buffer-create error-buffer)))) + (buffer + (if (and (not asynchronous) error-buffer) + (with-parsed-tramp-file-name default-directory nil + (list output-buffer (tramp-make-tramp-temp-file v))) + output-buffer)) + (p (get-buffer-process output-buffer))) + + ;; Check whether there is another process running. Tramp does not + ;; support 2 (asynchronous) processes in parallel. + (when p + (if (yes-or-no-p "A command is running. Kill it? ") + (ignore-errors (kill-process p)) + (error "Shell command in progress"))) + + (if current-buffer-p + (progn + (barf-if-buffer-read-only) + (push-mark nil t)) + (with-current-buffer output-buffer + (setq buffer-read-only nil) + (erase-buffer))) + + (if (and (not current-buffer-p) (integerp asynchronous)) + (prog1 + ;; Run the process. + (apply 'start-file-process "*Async Shell*" buffer args) + ;; Display output. + (pop-to-buffer output-buffer) + (setq mode-line-process '(":%s")) + (shell-mode)) + + (prog1 + ;; Run the process. + (apply 'process-file (car args) nil buffer nil (cdr args)) + ;; Insert error messages if they were separated. + (when (listp buffer) + (with-current-buffer error-buffer + (insert-file-contents (cadr buffer))) + (delete-file (cadr buffer))) + (if current-buffer-p + ;; This is like exchange-point-and-mark, but doesn't + ;; activate the mark. It is cleaner to avoid activation, + ;; even though the command loop would deactivate the mark + ;; because we inserted text. + (goto-char (prog1 (mark t) + (set-marker (mark-marker) (point) + (current-buffer)))) + ;; There's some output, display it. + (when (with-current-buffer output-buffer (> (point-max) (point-min))) + (if (functionp 'display-message-or-buffer) + (tramp-compat-funcall 'display-message-or-buffer output-buffer) + (pop-to-buffer output-buffer)))))))) + +(defun tramp-sh-handle-file-local-copy (filename) + "Like `file-local-copy' for Tramp files." + (with-parsed-tramp-file-name filename nil + (unless (file-exists-p filename) + (tramp-error + v 'file-error + "Cannot make local copy of non-existing file `%s'" filename)) + + (let* ((size (nth 7 (file-attributes (file-truename filename)))) + (rem-enc (tramp-get-inline-coding v "remote-encoding" size)) + (loc-dec (tramp-get-inline-coding v "local-decoding" size)) + (tmpfile (tramp-compat-make-temp-file filename))) + + (condition-case err + (cond + ;; `copy-file' handles direct copy and out-of-band methods. + ((or (tramp-local-host-p v) + (tramp-method-out-of-band-p v size)) + (copy-file filename tmpfile t t)) + + ;; Use inline encoding for file transfer. + (rem-enc + (save-excursion + (with-progress-reporter + v 3 (format "Encoding remote file %s" filename) + (tramp-barf-unless-okay + v (format rem-enc (tramp-shell-quote-argument localname)) + "Encoding remote file failed")) + + (if (functionp loc-dec) + ;; If local decoding is a function, we call it. We + ;; must disable multibyte, because + ;; `uudecode-decode-region' doesn't handle it + ;; correctly. + (with-temp-buffer + (set-buffer-multibyte nil) + (insert-buffer-substring (tramp-get-buffer v)) + (with-progress-reporter + v 3 (format "Decoding remote file %s with function %s" + filename loc-dec) + (funcall loc-dec (point-min) (point-max)) + ;; Unset `file-name-handler-alist'. Otherwise, + ;; epa-file gets confused. + (let (file-name-handler-alist + (coding-system-for-write 'binary)) + (write-region (point-min) (point-max) tmpfile)))) + + ;; If tramp-decoding-function is not defined for this + ;; method, we invoke tramp-decoding-command instead. + (let ((tmpfile2 (tramp-compat-make-temp-file filename))) + ;; Unset `file-name-handler-alist'. Otherwise, + ;; epa-file gets confused. + (let (file-name-handler-alist + (coding-system-for-write 'binary)) + (write-region (point-min) (point-max) tmpfile2)) + (with-progress-reporter + v 3 (format "Decoding remote file %s with command %s" + filename loc-dec) + (unwind-protect + (tramp-call-local-coding-command + loc-dec tmpfile2 tmpfile) + (delete-file tmpfile2))))) + + ;; Set proper permissions. + (set-file-modes tmpfile (tramp-default-file-modes filename)) + ;; Set local user ownership. + (tramp-set-file-uid-gid tmpfile))) + + ;; Oops, I don't know what to do. + (t (tramp-error + v 'file-error "Wrong method specification for `%s'" method))) + + ;; Error handling. + ((error quit) + (delete-file tmpfile) + (signal (car err) (cdr err)))) + + (run-hooks 'tramp-handle-file-local-copy-hook) + tmpfile))) + +;; This is needed for XEmacs only. Code stolen from files.el. +(defun tramp-sh-handle-insert-file-contents-literally + (filename &optional visit beg end replace) + "Like `insert-file-contents-literally' for Tramp files." + (let ((format-alist nil) + (after-insert-file-functions nil) + (coding-system-for-read 'no-conversion) + (coding-system-for-write 'no-conversion) + (find-buffer-file-type-function + (if (fboundp 'find-buffer-file-type) + (symbol-function 'find-buffer-file-type) + nil)) + (inhibit-file-name-handlers '(jka-compr-handler image-file-handler)) + (inhibit-file-name-operation 'insert-file-contents)) + (unwind-protect + (progn + (fset 'find-buffer-file-type (lambda (filename) t)) + (insert-file-contents filename visit beg end replace)) + ;; Save exit. + (if find-buffer-file-type-function + (fset 'find-buffer-file-type find-buffer-file-type-function) + (fmakunbound 'find-buffer-file-type))))) + +(defun tramp-sh-handle-make-auto-save-file-name () + "Like `make-auto-save-file-name' for Tramp files. +Returns a file name in `tramp-auto-save-directory' for autosaving this file." + (let ((tramp-auto-save-directory tramp-auto-save-directory) + (buffer-file-name + (tramp-subst-strs-in-string + '(("_" . "|") + ("/" . "_a") + (":" . "_b") + ("|" . "__") + ("[" . "_l") + ("]" . "_r")) + (buffer-file-name)))) + ;; File name must be unique. This is ensured with Emacs 22 (see + ;; UNIQUIFY element of `auto-save-file-name-transforms'); but for + ;; all other cases we must do it ourselves. + (when (boundp 'auto-save-file-name-transforms) + (mapc + (lambda (x) + (when (and (string-match (car x) buffer-file-name) + (not (car (cddr x)))) + (setq tramp-auto-save-directory + (or tramp-auto-save-directory + (tramp-compat-temporary-file-directory))))) + (symbol-value 'auto-save-file-name-transforms))) + ;; Create directory. + (when tramp-auto-save-directory + (setq buffer-file-name + (expand-file-name buffer-file-name tramp-auto-save-directory)) + (unless (file-exists-p tramp-auto-save-directory) + (make-directory tramp-auto-save-directory t))) + ;; Run plain `make-auto-save-file-name'. There might be an advice when + ;; it is not a magic file name operation (since Emacs 22). + ;; We must deactivate it temporarily. + (if (not (ad-is-active 'make-auto-save-file-name)) + (tramp-run-real-handler 'make-auto-save-file-name nil) + ;; else + (ad-deactivate 'make-auto-save-file-name) + (prog1 + (tramp-run-real-handler 'make-auto-save-file-name nil) + (ad-activate 'make-auto-save-file-name))))) + +;; CCC grok LOCKNAME +(defun tramp-sh-handle-write-region + (start end filename &optional append visit lockname confirm) + "Like `write-region' for Tramp files." + (setq filename (expand-file-name filename)) + (with-parsed-tramp-file-name filename nil + ;; 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-sh-handle-write-region: LOCKNAME must be nil or equal FILENAME")) + + ;; XEmacs takes a coding system as the seventh 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)) + (tramp-error v 'file-error "File not overwritten"))) + + (let ((uid (or (nth 2 (tramp-compat-file-attributes filename 'integer)) + (tramp-get-remote-uid v 'integer))) + (gid (or (nth 3 (tramp-compat-file-attributes filename 'integer)) + (tramp-get-remote-gid v 'integer)))) + + (if (and (tramp-local-host-p v) + ;; `file-writable-p' calls `file-expand-file-name'. We + ;; cannot use `tramp-run-real-handler' therefore. + (let (file-name-handler-alist) + (and + (file-writable-p (file-name-directory localname)) + (or (file-directory-p localname) + (file-writable-p localname))))) + ;; Short track: if we are on the local host, we can run directly. + (tramp-run-real-handler + 'write-region + (list start end localname append 'no-message lockname confirm)) + + (let ((modes (save-excursion (tramp-default-file-modes filename))) + ;; 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 + ;; 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. + (tmpfile (or tramp-temp-buffer-file-name + (tramp-compat-make-temp-file filename)))) + + ;; If `append' is non-nil, we copy the file locally, and let + ;; the native `write-region' implementation do the job. + (when append (copy-file filename tmpfile 'ok)) + + ;; 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. We must ensure that `file-coding-system-alist' + ;; matches `tmpfile'. + (let (file-name-handler-alist + (file-coding-system-alist + (tramp-find-file-name-coding-system-alist filename tmpfile))) + (condition-case err + (tramp-run-real-handler + 'write-region + (list start end tmpfile append 'no-message lockname confirm)) + ((error quit) + (setq tramp-temp-buffer-file-name nil) + (delete-file tmpfile) + (signal (car err) (cdr err)))) + + ;; Now, `last-coding-system-used' has the right value. Remember it. + (when (boundp 'last-coding-system-used) + (setq coding-system-used + (symbol-value 'last-coding-system-used)))) + + ;; The permissions of the temporary file should be set. If + ;; filename does not exist (eq modes nil) it has been + ;; renamed to the backup file. This case `save-buffer' + ;; handles permissions. + ;; Ensure, that it is still readable. + (when modes + (set-file-modes + tmpfile + (logior (or modes 0) (tramp-compat-octal-to-decimal "0400")))) + + ;; 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. + (let* ((size (nth 7 (file-attributes tmpfile))) + (rem-dec (tramp-get-inline-coding v "remote-decoding" size)) + (loc-enc (tramp-get-inline-coding v "local-encoding" size))) + (cond + ;; `copy-file' handles direct copy and out-of-band methods. + ((or (tramp-local-host-p v) + (tramp-method-out-of-band-p v size)) + (if (and (not (stringp start)) + (= (or end (point-max)) (point-max)) + (= (or start (point-min)) (point-min)) + (tramp-get-method-parameter + method 'tramp-copy-keep-tmpfile)) + (progn + (setq tramp-temp-buffer-file-name tmpfile) + (condition-case err + ;; We keep the local file for performance + ;; reasons, useful for "rsync". + (copy-file tmpfile filename t) + ((error quit) + (setq tramp-temp-buffer-file-name nil) + (delete-file tmpfile) + (signal (car err) (cdr err))))) + (setq tramp-temp-buffer-file-name nil) + ;; Don't rename, in order to keep context in SELinux. + (unwind-protect + (copy-file tmpfile filename t) + (delete-file tmpfile)))) + + ;; Use inline file transfer. + (rem-dec + ;; Encode tmpfile. + (unwind-protect + (with-temp-buffer + (set-buffer-multibyte nil) + ;; Use encoding function or command. + (if (functionp loc-enc) + (with-progress-reporter + v 3 (format "Encoding region using function `%s'" + loc-enc) + (let ((coding-system-for-read 'binary)) + (insert-file-contents-literally tmpfile)) + ;; 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-compat-temporary-file-directory))) + (funcall loc-enc (point-min) (point-max)))) + + (with-progress-reporter + v 3 (format "Encoding region using command `%s'" + loc-enc) + (unless (zerop (tramp-call-local-coding-command + loc-enc tmpfile t)) + (tramp-error + v 'file-error + (concat "Cannot write to `%s', " + "local encoding command `%s' failed") + filename loc-enc)))) + + ;; Send buffer into remote decoding command which + ;; writes to remote file. Because this happens on + ;; the remote host, we cannot use the function. + (with-progress-reporter + v 3 + (format "Decoding region into remote file %s" filename) + (goto-char (point-max)) + (unless (bolp) (newline)) + (tramp-send-command + v + (format + (concat rem-dec " <<'EOF'\n%sEOF") + (tramp-shell-quote-argument localname) + (buffer-string))) + (tramp-barf-unless-okay + v nil + "Couldn't write region to `%s', decode using `%s' failed" + filename rem-dec) + ;; When `file-precious-flag' is set, the region is + ;; written to a temporary file. Check that the + ;; checksum is equal to that from the local tmpfile. + (when file-precious-flag + (erase-buffer) + (and + ;; cksum runs locally, if possible. + (zerop (tramp-compat-call-process "cksum" tmpfile t)) + ;; cksum runs remotely. + (tramp-send-command-and-check + v + (format + "cksum <%s" (tramp-shell-quote-argument localname))) + ;; ... they are different. + (not + (string-equal + (buffer-string) + (with-current-buffer (tramp-get-buffer v) + (buffer-string)))) + (tramp-error + v 'file-error + (concat "Couldn't write region to `%s'," + " decode using `%s' failed") + filename rem-dec))))) + + ;; Save exit. + (delete-file tmpfile))) + + ;; That's not expected. + (t + (tramp-error + v 'file-error + (concat "Method `%s' should specify both encoding and " + "decoding command or an rcp program") + method)))) + + ;; Make `last-coding-system-used' have the right value. + (when coding-system-used + (set 'last-coding-system-used coding-system-used)))) + + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + + ;; We must protect `last-coding-system-used', now we have set it + ;; to its correct value. + (let (last-coding-system-used (need-chown t)) + ;; Set file modification time. + (when (or (eq visit t) (stringp visit)) + (let ((file-attr (file-attributes filename))) + (set-visited-file-modtime + ;; We must pass modtime explicitely, because filename can + ;; be different from (buffer-file-name), f.e. if + ;; `file-precious-flag' is set. + (nth 5 file-attr)) + (when (and (eq (nth 2 file-attr) uid) + (eq (nth 3 file-attr) gid)) + (setq need-chown nil)))) + + ;; Set the ownership. + (when need-chown + (tramp-set-file-uid-gid filename uid gid)) + (when (or (eq visit t) (null visit) (stringp visit)) + (tramp-message v 0 "Wrote %s" filename)) + (run-hooks 'tramp-handle-write-region-hook))))) + +(defvar tramp-vc-registered-file-names nil + "List used to collect file names, which are checked during `vc-registered'.") + +;; VC backends check for the existence of various different special +;; files. This is very time consuming, because every single check +;; requires a remote command (the file cache must be invalidated). +;; Therefore, we apply a kind of optimization. We install the file +;; name handler `tramp-vc-file-name-handler', which does nothing but +;; remembers all file names for which `file-exists-p' or +;; `file-readable-p' has been applied. A first run of `vc-registered' +;; is performed. Afterwards, a script is applied for all collected +;; file names, using just one remote command. The result of this +;; script is used to fill the file cache with actual values. Now we +;; can reset the file name handlers, and we make a second run of +;; `vc-registered', which returns the expected result without sending +;; any other remote command. +(defun tramp-sh-handle-vc-registered (file) + "Like `vc-registered' for Tramp files." + (tramp-compat-with-temp-message "" + (with-parsed-tramp-file-name file nil + (with-progress-reporter + v 3 (format "Checking `vc-registered' for %s" file) + + ;; There could be new files, created by the vc backend. We + ;; cannot reuse the old cache entries, therefore. + (let (tramp-vc-registered-file-names + (remote-file-name-inhibit-cache (current-time)) + (file-name-handler-alist + `((,tramp-file-name-regexp . tramp-vc-file-name-handler)))) + + ;; Here we collect only file names, which need an operation. + (tramp-run-real-handler 'vc-registered (list file)) + (tramp-message v 10 "\n%s" tramp-vc-registered-file-names) + + ;; Send just one command, in order to fill the cache. + (when tramp-vc-registered-file-names + (tramp-maybe-send-script + v + (format tramp-vc-registered-read-file-names + (tramp-get-file-exists-command v) + (format "%s -r" (tramp-get-test-command v))) + "tramp_vc_registered_read_file_names") + + (dolist + (elt + (tramp-send-command-and-read + v + (format + "tramp_vc_registered_read_file_names <<'EOF'\n%s\nEOF\n" + (mapconcat 'tramp-shell-quote-argument + tramp-vc-registered-file-names + "\n")))) + + (tramp-set-file-property + v (car elt) (cadr elt) (cadr (cdr elt)))))) + + ;; Second run. Now all `file-exists-p' or `file-readable-p' + ;; calls shall be answered from the file cache. We unset + ;; `process-file-side-effects' in order to keep the cache when + ;; `process-file' calls appear. + (let (process-file-side-effects) + (tramp-run-real-handler 'vc-registered (list file))))))) + +;;;###tramp-autoload +(defun tramp-sh-file-name-handler (operation &rest args) + "Invoke remote-shell Tramp file name handler. +Fall back to normal file name handler if no Tramp handler exists." + (when (and tramp-locked (not tramp-locker)) + (setq tramp-locked nil) + (signal 'file-error (list "Forbidden reentrant call of Tramp"))) + (let ((tl tramp-locked)) + (unwind-protect + (progn + (setq tramp-locked t) + (let ((tramp-locker t)) + (save-match-data + (let ((fn (assoc operation tramp-sh-file-name-handler-alist))) + (if fn + (apply (cdr fn) args) + (tramp-run-real-handler operation args)))))) + (setq tramp-locked tl)))) + +(defun tramp-vc-file-name-handler (operation &rest args) + "Invoke special file name handler, which collects files to be handled." + (save-match-data + (let ((filename + (tramp-replace-environment-variables + (apply 'tramp-file-name-for-operation operation args))) + (fn (assoc operation tramp-sh-file-name-handler-alist))) + (with-parsed-tramp-file-name filename nil + (cond + ;; That's what we want: file names, for which checks are + ;; applied. We assume, that VC uses only `file-exists-p' and + ;; `file-readable-p' checks; otherwise we must extend the + ;; list. We do not perform any action, but return nil, in + ;; order to keep `vc-registered' running. + ((and fn (memq operation '(file-exists-p file-readable-p))) + (add-to-list 'tramp-vc-registered-file-names localname 'append) + nil) + ;; Tramp file name handlers like `expand-file-name'. They + ;; must still work. + (fn + (save-match-data (apply (cdr fn) args))) + ;; Default file name handlers, we don't care. + (t (tramp-run-real-handler operation args))))))) + +;;; Internal Functions: + +(defun tramp-maybe-send-script (vec script name) + "Define in remote shell function NAME implemented as SCRIPT. +Only send the definition if it has not already been done." + (let* ((p (tramp-get-connection-process vec)) + (scripts (tramp-get-connection-property p "scripts" nil))) + (unless (member name scripts) + (with-progress-reporter vec 5 (format "Sending script `%s'" name) + ;; The script could contain a call of Perl. This is masked with `%s'. + (tramp-barf-unless-okay + vec + (format "%s () {\n%s\n}" name + (format script (tramp-get-remote-perl vec))) + "Script %s sending failed" name) + (tramp-set-connection-property p "scripts" (cons name scripts)))))) + +(defun tramp-set-auto-save () + (when (and ;; ange-ftp has its own auto-save mechanism + (eq (tramp-find-foreign-file-name-handler (buffer-file-name)) + 'tramp-sh-file-name-handler) + auto-save-default) + (auto-save-mode 1))) +(add-hook 'find-file-hooks 'tramp-set-auto-save t) +(add-hook 'tramp-unload-hook + (lambda () + (remove-hook 'find-file-hooks 'tramp-set-auto-save))) + +(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." + (with-parsed-tramp-file-name filename nil + (tramp-send-command-and-check + v + (format + "%s %s %s" + (tramp-get-test-command v) + switch + (tramp-shell-quote-argument localname))))) + +(defun tramp-run-test2 (format-string file1 file2) + "Run `test'-like program on the remote system, given FILE1, FILE2. +FORMAT-STRING contains the program name, switches, and place holders. +Returns the exit code of the `test' program. Barfs if the methods, +hosts, or files, disagree." + (unless (tramp-equal-remote file1 file2) + (with-parsed-tramp-file-name (if (tramp-tramp-file-p file1) file1 file2) nil + (tramp-error + v 'file-error + "tramp-run-test2 only implemented for same method, user, host"))) + (with-parsed-tramp-file-name file1 v1 + (with-parsed-tramp-file-name file1 v2 + (tramp-send-command-and-check + v1 + (format format-string + (tramp-shell-quote-argument v1-localname) + (tramp-shell-quote-argument v2-localname)))))) + +(defun tramp-find-executable + (vec progname dirlist &optional ignore-tilde ignore-path) + "Searches for PROGNAME in $PATH and all directories mentioned in DIRLIST. +First arg VEC specifies 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. If IGNORE-PATH is non-nil, searches +only in DIRLIST. + +Returns the absolute file name of PROGNAME, if found, and nil otherwise. + +This function expects to be in the right *tramp* buffer." + (with-current-buffer (tramp-get-connection-buffer vec) + (let (result) + ;; Check whether the executable is in $PATH. "which(1)" does not + ;; report always a correct error code; therefore we check the + ;; number of words it returns. + (unless ignore-path + (tramp-send-command vec (format "which \\%s | wc -w" progname)) + (goto-char (point-min)) + (if (looking-at "^\\s-*1$") + (setq result (concat "\\" progname)))) + (unless result + (when ignore-tilde + ;; Remove all ~/foo directories from dirlist. In XEmacs, + ;; `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 + vec + (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'\n" + "%s\nEOF") + progname progname progname (mapconcat 'identity dirlist "\n"))) + (goto-char (point-max)) + (when (search-backward "tramp_executable " nil t) + (skip-chars-forward "^ ") + (skip-chars-forward " ") + (setq result (buffer-substring (point) (point-at-eol))))) + result))) + +(defun tramp-set-remote-path (vec) + "Sets the remote environment PATH to existing directories. +I.e., for each directory in `tramp-remote-path', it is tested +whether it exists and if so, it is added to the environment +variable PATH." + (tramp-message vec 5 (format "Setting $PATH environment variable")) + (tramp-send-command + vec (format "PATH=%s; export PATH" + (mapconcat 'identity (tramp-get-remote-path vec) ":")))) + +;; ------------------------------------------------------------ +;; -- Communication with external shell -- +;; ------------------------------------------------------------ + +(defun tramp-find-file-exists-command (vec) + "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." + (let ((existing "/") + (nonexisting + (tramp-shell-quote-argument "/ this file does not exist ")) + result) + ;; 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 result (format "%s -e" (tramp-get-test-command vec))) + (tramp-send-command-and-check + vec (format "%s %s" result existing)) + (not (tramp-send-command-and-check + vec (format "%s %s" result nonexisting)))) + (and (setq result "/bin/test -e") + (tramp-send-command-and-check + vec (format "%s %s" result existing)) + (not (tramp-send-command-and-check + vec (format "%s %s" result nonexisting)))) + (and (setq result "/usr/bin/test -e") + (tramp-send-command-and-check + vec (format "%s %s" result existing)) + (not (tramp-send-command-and-check + vec (format "%s %s" result nonexisting)))) + (and (setq result (format "%s -d" (tramp-get-ls-command vec))) + (tramp-send-command-and-check + vec (format "%s %s" result existing)) + (not (tramp-send-command-and-check + vec (format "%s %s" result nonexisting))))) + (tramp-error + vec 'file-error "Couldn't find command to check if file exists")) + result)) + +(defun tramp-open-shell (vec shell) + "Opens shell SHELL." + (with-progress-reporter vec 5 (format "Opening remote shell `%s'" shell) + ;; Find arguments for this shell. + (let ((tramp-end-of-output tramp-initial-end-of-output) + (alist tramp-sh-extra-args) + item extra-args) + (while (and alist (null extra-args)) + (setq item (pop alist)) + (when (string-match (car item) shell) + (setq extra-args (cdr item)))) + (when extra-args (setq shell (concat shell " " extra-args))) + (tramp-send-command + vec (format "exec env ENV='' PROMPT_COMMAND='' PS1=%s PS2='' PS3='' %s" + (shell-quote-argument tramp-end-of-output) shell) + t)) + ;; Setting prompts. + (tramp-send-command + vec (format "PS1=%s" (shell-quote-argument tramp-end-of-output)) t) + (tramp-send-command vec "PS2=''" t) + (tramp-send-command vec "PS3=''" t) + (tramp-send-command vec "PROMPT_COMMAND=''" t))) + +(defun tramp-find-shell (vec) + "Opens a shell on the remote host which groks tilde expansion." + (unless (tramp-get-connection-property vec "remote-shell" nil) + (let (shell) + (with-current-buffer (tramp-get-buffer vec) + (tramp-send-command vec "echo ~root" t) + (cond + ((or (string-match "^~root$" (buffer-string)) + ;; The default shell (ksh93) of OpenSolaris is buggy. + (string-equal (tramp-get-connection-property vec "uname" "") + "SunOS 5.11")) + (setq shell + (or (tramp-find-executable + vec "bash" (tramp-get-remote-path vec) t t) + (tramp-find-executable + vec "ksh" (tramp-get-remote-path vec) t t))) + (unless shell + (tramp-error + vec 'file-error + "Couldn't find a shell which groks tilde expansion")) + (tramp-message + vec 5 "Starting remote shell `%s' for tilde expansion" shell) + (tramp-open-shell vec shell)) + + (t (tramp-message + vec 5 "Remote `%s' groks tilde expansion, good" + (tramp-set-connection-property + vec "remote-shell" + (tramp-get-method-parameter + (tramp-file-name-method vec) 'tramp-remote-sh))))))))) + +;; Utility functions. + +(defun tramp-barf-if-no-shell-prompt (proc timeout &rest error-args) + "Wait for shell prompt and barf if none appears. +Looks at process PROC to see if a shell prompt appears in TIMEOUT +seconds. If not, it produces an error message with the given ERROR-ARGS." + (unless + (tramp-wait-for-regexp + proc timeout + (format + "\\(%s\\|%s\\)\\'" shell-prompt-pattern tramp-shell-prompt-pattern)) + (apply 'tramp-error-with-buffer nil proc 'file-error error-args))) + +(defun tramp-open-connection-setup-interactive-shell (proc vec) + "Set up an interactive shell. +Mainly sets the prompt and the echo correctly. PROC is the shell +process to set up. VEC specifies the connection." + (let ((tramp-end-of-output tramp-initial-end-of-output)) + ;; It is useful to set the prompt in the following command because + ;; some people have a setting for $PS1 which /bin/sh doesn't know + ;; about and thus /bin/sh will display a strange prompt. For + ;; example, if $PS1 has "${CWD}" in the value, then ksh will + ;; display the current working directory but /bin/sh will display + ;; a dollar sign. The following command line sets $PS1 to a sane + ;; value, and works under Bourne-ish shells as well as csh-like + ;; shells. Daniel Pittman reports that the unusual positioning of + ;; the single quotes makes it work under `rc', too. We also unset + ;; the variable $ENV because that is read by some sh + ;; implementations (eg, bash when called as sh) on startup; this + ;; way, we avoid the startup file clobbering $PS1. $PROMPT_COMMAND + ;; is another way to set the prompt in /bin/bash, it must be + ;; discarded as well. + (tramp-open-shell + vec + (tramp-get-method-parameter (tramp-file-name-method vec) 'tramp-remote-sh)) + + ;; Disable echo. + (tramp-message vec 5 "Setting up remote shell environment") + (tramp-send-command vec "stty -inlcr -echo kill '^U' erase '^H'" t) + ;; Check whether the echo has really been disabled. Some + ;; implementations, like busybox of embedded GNU/Linux, don't + ;; support disabling. + (tramp-send-command vec "echo foo" t) + (with-current-buffer (process-buffer proc) + (goto-char (point-min)) + (when (looking-at "echo foo") + (tramp-set-connection-property proc "remote-echo" t) + (tramp-message vec 5 "Remote echo still on. Ok.") + ;; Make sure backspaces and their echo are enabled and no line + ;; width magic interferes with them. + (tramp-send-command vec "stty icanon erase ^H cols 32767" t)))) + + (tramp-message vec 5 "Setting shell prompt") + (tramp-send-command + vec (format "PS1=%s" (shell-quote-argument tramp-end-of-output)) t) + (tramp-send-command vec "PS2=''" t) + (tramp-send-command vec "PS3=''" t) + (tramp-send-command vec "PROMPT_COMMAND=''" t) + + ;; Try to set up the coding system correctly. + ;; CCC this can't be the right way to do it. Hm. + (tramp-message vec 5 "Determining coding system") + (tramp-send-command vec "echo foo ; echo bar" t) + (with-current-buffer (process-buffer proc) + (goto-char (point-min)) + (if (featurep 'mule) + ;; Use MULE to select the right EOL convention for communicating + ;; with the process. + (let* ((cs (or (tramp-compat-funcall 'process-coding-system proc) + (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-compat-coding-system-change-eol-conversion + cs-encode 'unix)) + (when (search-forward "\r" nil t) + (setq cs-decode (tramp-compat-coding-system-change-eol-conversion + cs-decode 'dos))) + (tramp-compat-funcall + 'set-buffer-process-coding-system cs-decode cs-encode) + (tramp-message + vec 5 "Setting coding system to `%s' and `%s'" 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-send-command vec "stty -onlcr" t)))) + + (tramp-send-command vec "set +o vi +o emacs" t) + + ;; Check whether the output of "uname -sr" has been changed. If + ;; yes, this is a strong indication that we must expire all + ;; connection properties. We start again with + ;; `tramp-maybe-open-connection', it will be catched there. + (tramp-message vec 5 "Checking system information") + (let ((old-uname (tramp-get-connection-property vec "uname" nil)) + (new-uname + (tramp-set-connection-property + vec "uname" + (tramp-send-command-and-read vec "echo \\\"`uname -sr`\\\"")))) + (when (and (stringp old-uname) (not (string-equal old-uname new-uname))) + (with-current-buffer (tramp-get-debug-buffer vec) + ;; Keep the debug buffer. + (rename-buffer + (generate-new-buffer-name tramp-temp-buffer-name) 'unique) + (tramp-cleanup-connection vec) + (if (= (point-min) (point-max)) + (kill-buffer nil) + (rename-buffer (tramp-debug-buffer-name vec) 'unique)) + ;; We call `tramp-get-buffer' in order to keep the debug buffer. + (tramp-get-buffer vec) + (tramp-message + vec 3 + "Connection reset, because remote host changed from `%s' to `%s'" + old-uname new-uname) + (throw 'uname-changed (tramp-maybe-open-connection vec))))) + + ;; Check whether the remote host suffers from buggy + ;; `send-process-string'. This is known for FreeBSD (see comment in + ;; `send_process', file process.c). I've tested sending 624 bytes + ;; successfully, sending 625 bytes failed. Emacs makes a hack when + ;; this host type is detected locally. It cannot handle remote + ;; hosts, though. + (with-connection-property proc "chunksize" + (cond + ((and (integerp tramp-chunksize) (> tramp-chunksize 0)) + tramp-chunksize) + (t + (tramp-message + vec 5 "Checking remote host type for `send-process-string' bug") + (if (string-match + "^FreeBSD" (tramp-get-connection-property vec "uname" "")) + 500 0)))) + + ;; Set remote PATH variable. + (tramp-set-remote-path vec) + + ;; 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 OpenSolaris /bin/sh is a problem. There are also other + ;; problems with /bin/sh of OpenSolaris, like redirection of stderr + ;; in function declarations, or changing HISTFILE in place. + ;; Therefore, OpenSolaris' /bin/sh is replaced by bash, when + ;; detected. + (tramp-find-shell vec) + + ;; Disable unexpected output. + (tramp-send-command vec "mesg n; biff n" t) + + ;; IRIX64 bash expands "!" even when in single quotes. This + ;; destroys our shell functions, we must disable it. See + ;; . + (when (string-match "^IRIX64" (tramp-get-connection-property vec "uname" "")) + (tramp-send-command vec "set +H" t)) + + ;; On BSD-like systems, ?\t is expanded to spaces. Suppress this. + (when (string-match "BSD\\|Darwin" + (tramp-get-connection-property vec "uname" "")) + (tramp-send-command vec "stty -oxtabs" t)) + + ;; Set `remote-tty' process property. + (ignore-errors + (let ((tty (tramp-send-command-and-read vec "echo \\\"`tty`\\\""))) + (unless (zerop (length tty)) + (tramp-compat-process-put proc 'remote-tty tty)))) + + ;; Dump stty settings in the traces. + (when (>= tramp-verbose 9) + (tramp-send-command vec "stty -a" t)) + + ;; Set the environment. + (tramp-message vec 5 "Setting default environment") + + (let ((env (copy-sequence tramp-remote-process-environment)) + unset item) + (while env + (setq item (tramp-compat-split-string (car env) "=")) + (setcdr item (mapconcat 'identity (cdr item) "=")) + (if (and (stringp (cdr item)) (not (string-equal (cdr item) ""))) + (tramp-send-command + vec (format "%s=%s; export %s" (car item) (cdr item) (car item)) t) + (push (car item) unset)) + (setq env (cdr env))) + (when unset + (tramp-send-command + vec (format "unset %s" (mapconcat 'identity unset " ")) t)))) + +;; CCC: We should either implement a Perl version of base64 encoding +;; and decoding. Then we just use that in the last item. The other +;; alternative is to use the Perl version of UU encoding. But then +;; we need a Lisp version of uuencode. +;; +;; Old text from documentation of tramp-methods: +;; 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. + +(autoload 'uudecode-decode-region "uudecode") + +(defconst tramp-local-coding-commands + '((b64 base64-encode-region base64-decode-region) + (uu tramp-uuencode-region uudecode-decode-region) + (pack + "perl -e 'binmode STDIN; binmode STDOUT; print pack(q{u*}, join q{}, <>)'" + "perl -e 'binmode STDIN; binmode STDOUT; print unpack(q{u*}, join q{}, <>)'")) + "List of local coding commands for inline transfer. +Each item is a list that looks like this: + +\(FORMAT ENCODING DECODING\) + +FORMAT is symbol describing the encoding/decoding format. It can be +`b64' for base64 encoding, `uu' for uu encoding, or `pack' for simple packing. + +ENCODING and DECODING can be strings, giving commands, or symbols, +giving functions. If they are strings, then they can contain +the \"%s\" format specifier. If that specifier is present, the input +filename will be put into the command line at that spot. If the +specifier is not present, the input should be read from standard +input. + +If they are functions, they will be called with two arguments, start +and end of region, and are expected to replace the region contents +with the encoded or decoded results, respectively.") + +(defconst tramp-remote-coding-commands + '((b64 "base64" "base64 -d -i") + ;; "-i" is more robust with older base64 from GNU coreutils. + ;; However, I don't know whether all base64 versions do supports + ;; this option. + (b64 "base64" "base64 -d") + (b64 "mimencode -b" "mimencode -u -b") + (b64 "mmencode -b" "mmencode -u -b") + (b64 "recode data..base64" "recode base64..data") + (b64 tramp-perl-encode-with-module tramp-perl-decode-with-module) + (b64 tramp-perl-encode tramp-perl-decode) + (uu "uuencode xxx" "uudecode -o /dev/stdout") + (uu "uuencode xxx" "uudecode -o -") + (uu "uuencode xxx" "uudecode -p") + (uu "uuencode xxx" tramp-uudecode) + (pack + "perl -e 'binmode STDIN; binmode STDOUT; print pack(q{u*}, join q{}, <>)'" + "perl -e 'binmode STDIN; binmode STDOUT; print unpack(q{u*}, join q{}, <>)'")) + "List of remote coding commands for inline transfer. +Each item is a list that looks like this: + +\(FORMAT ENCODING DECODING\) + +FORMAT is symbol describing the encoding/decoding format. It can be +`b64' for base64 encoding, `uu' for uu encoding, or `pack' for simple packing. + +ENCODING and DECODING can be strings, giving commands, or symbols, +giving variables. If they are strings, then they can contain +the \"%s\" format specifier. If that specifier is present, the input +filename will be put into the command line at that spot. If the +specifier is not present, the input should be read from standard +input. + +If they are variables, this variable is a string containing a Perl +implementation for this functionality. This Perl program will be transferred +to the remote host, and it is available as shell function with the same name.") + +(defun tramp-find-inline-encoding (vec) + "Find an inline transfer encoding that works. +Goes through the list `tramp-local-coding-commands' and +`tramp-remote-coding-commands'." + (save-excursion + (let ((local-commands tramp-local-coding-commands) + (magic "xyzzy") + loc-enc loc-dec rem-enc rem-dec litem ritem found) + (while (and local-commands (not found)) + (setq litem (pop local-commands)) + (catch 'wont-work-local + (let ((format (nth 0 litem)) + (remote-commands tramp-remote-coding-commands)) + (setq loc-enc (nth 1 litem)) + (setq loc-dec (nth 2 litem)) + ;; If the local encoder or decoder is a string, the + ;; corresponding command has to work locally. + (if (not (stringp loc-enc)) + (tramp-message + vec 5 "Checking local encoding function `%s'" loc-enc) + (tramp-message + vec 5 "Checking local encoding command `%s' for sanity" loc-enc) + (unless (zerop (tramp-call-local-coding-command + loc-enc nil nil)) + (throw 'wont-work-local nil))) + (if (not (stringp loc-dec)) + (tramp-message + vec 5 "Checking local decoding function `%s'" loc-dec) + (tramp-message + vec 5 "Checking local decoding command `%s' for sanity" loc-dec) + (unless (zerop (tramp-call-local-coding-command + loc-dec nil nil)) + (throw 'wont-work-local nil))) + ;; Search for remote coding commands with the same format + (while (and remote-commands (not found)) + (setq ritem (pop remote-commands)) + (catch 'wont-work-remote + (when (equal format (nth 0 ritem)) + (setq rem-enc (nth 1 ritem)) + (setq rem-dec (nth 2 ritem)) + ;; Check if remote encoding and decoding commands can be + ;; called remotely with null input and output. This makes + ;; sure there are no syntax errors and the command is really + ;; found. Note that we do not redirect stdout to /dev/null, + ;; for two reasons: when checking the decoding command, we + ;; actually check the output it gives. And also, when + ;; redirecting "mimencode" output to /dev/null, then as root + ;; it might change the permissions of /dev/null! + (when (not (stringp rem-enc)) + (let ((name (symbol-name rem-enc))) + (while (string-match (regexp-quote "-") name) + (setq name (replace-match "_" nil t name))) + (tramp-maybe-send-script vec (symbol-value rem-enc) name) + (setq rem-enc name))) + (tramp-message + vec 5 + "Checking remote encoding command `%s' for sanity" rem-enc) + (unless (tramp-send-command-and-check + vec (format "%s " output) "")))) + +(defconst tramp-inline-compress-commands + '(("gzip" "gzip -d") + ("bzip2" "bzip2 -d") + ("compress" "compress -d")) + "List of compress and decompress commands for inline transfer. +Each item is a list that looks like this: + +\(COMPRESS DECOMPRESS\) + +COMPRESS or DECOMPRESS are strings with the respective commands.") + +(defun tramp-find-inline-compress (vec) + "Find an inline transfer compress command that works. +Goes through the list `tramp-inline-compress-commands'." + (save-excursion + (let ((commands tramp-inline-compress-commands) + (magic "xyzzy") + item compress decompress + found) + (while (and commands (not found)) + (catch 'next + (setq item (pop commands) + compress (nth 0 item) + decompress (nth 1 item)) + (tramp-message + vec 5 + "Checking local compress command `%s', `%s' for sanity" + compress decompress) - (unless (zerop (tramp-call-local-coding-command - (format "echo %s | %s | %s" - magic compress decompress) nil nil)) ++ (unless ++ (zerop ++ (tramp-call-local-coding-command ++ (format ++ ;; Windows shells need the program file name after ++ ;; the pipe symbol be quoted if they use forward ++ ;; slashes as directory separators. ++ (if (memq system-type '(windows-nt)) ++ "echo %s | \"%s\" | \"%s\"" ++ "echo %s | %s | %s") ++ magic compress decompress) nil nil)) + (throw 'next nil)) + (tramp-message + vec 5 + "Checking remote compress command `%s', `%s' for sanity" + compress decompress) + (unless (tramp-send-command-and-check + vec (format "echo %s | %s | %s" magic compress decompress) t) + (throw 'next nil)) + (setq found t))) + + ;; Did we find something? + (if found + (progn + ;; Set connection properties. + (tramp-message + vec 5 "Using inline transfer compress command `%s'" compress) + (tramp-set-connection-property vec "inline-compress" compress) + (tramp-message + vec 5 "Using inline transfer decompress command `%s'" decompress) + (tramp-set-connection-property vec "inline-decompress" decompress)) + + (tramp-set-connection-property vec "inline-compress" nil) + (tramp-set-connection-property vec "inline-decompress" nil) + (tramp-message + vec 2 "Couldn't find an inline transfer compress command"))))) + +(defun tramp-compute-multi-hops (vec) + "Expands VEC according to `tramp-default-proxies-alist'. +Gateway hops are already opened." + (let ((target-alist `(,vec)) + (choices tramp-default-proxies-alist) + item proxy) + + ;; Look for proxy hosts to be passed. + (while choices + (setq item (pop choices) + proxy (eval (nth 2 item))) + (when (and + ;; host + (string-match (or (eval (nth 0 item)) "") + (or (tramp-file-name-host (car target-alist)) "")) + ;; user + (string-match (or (eval (nth 1 item)) "") + (or (tramp-file-name-user (car target-alist)) ""))) + (if (null proxy) + ;; No more hops needed. + (setq choices nil) + ;; Replace placeholders. + (setq proxy + (format-spec + proxy + (format-spec-make + ?u (or (tramp-file-name-user (car target-alist)) "") + ?h (or (tramp-file-name-host (car target-alist)) "")))) + (with-parsed-tramp-file-name proxy l + ;; Add the hop. + (add-to-list 'target-alist l) + ;; Start next search. + (setq choices tramp-default-proxies-alist))))) + + ;; Handle gateways. + (when (string-match + (format + "^\\(%s\\|%s\\)$" tramp-gw-tunnel-method tramp-gw-socks-method) + (tramp-file-name-method (car target-alist))) + (let ((gw (pop target-alist)) + (hop (pop target-alist))) + ;; Is the method prepared for gateways? + (unless (tramp-file-name-port hop) + (tramp-error + vec 'file-error + "Connection `%s' is not supported for gateway access." hop)) + ;; Open the gateway connection. + (add-to-list + 'target-alist + (vector + (tramp-file-name-method hop) (tramp-file-name-user hop) + (tramp-compat-funcall 'tramp-gw-open-connection vec gw hop) nil)) + ;; For the password prompt, we need the correct values. + ;; Therefore, we must remember the gateway vector. But we + ;; cannot do it as connection property, because it shouldn't + ;; be persistent. And we have no started process yet either. + (tramp-set-file-property (car target-alist) "" "gateway" hop))) + + ;; Foreign and out-of-band methods are not supported for multi-hops. + (when (cdr target-alist) + (setq choices target-alist) + (while choices + (setq item (pop choices)) + (when + (or + (not + (tramp-get-method-parameter + (tramp-file-name-method item) 'tramp-login-program)) + (tramp-get-method-parameter + (tramp-file-name-method item) 'tramp-copy-program)) + (tramp-error + vec 'file-error + "Method `%s' is not supported for multi-hops." + (tramp-file-name-method item))))) + + ;; In case the host name is not used for the remote shell + ;; command, the user could be misguided by applying a random + ;; hostname. + (let* ((v (car target-alist)) + (method (tramp-file-name-method v)) + (host (tramp-file-name-host v))) + (unless + (or + ;; There are multi-hops. + (cdr target-alist) + ;; The host name is used for the remote shell command. + (member + '("%h") (tramp-get-method-parameter method 'tramp-login-args)) + ;; The host is local. We cannot use `tramp-local-host-p' + ;; here, because it opens a connection as well. + (string-match tramp-local-host-regexp host)) + (tramp-error + v 'file-error + "Host `%s' looks like a remote host, `%s' can only use the local host" + host method))) + + ;; Result. + target-alist)) + +(defun tramp-maybe-open-connection (vec) + "Maybe open a connection VEC. +Does not do anything if a connection is already open, but re-opens the +connection if a previous connection has died for some reason." + (catch 'uname-changed + (let ((p (tramp-get-connection-process vec)) + (process-name (tramp-get-connection-property vec "process-name" nil)) + (process-environment (copy-sequence process-environment))) + + ;; If too much time has passed since last command was sent, look + ;; whether process is still alive. If it isn't, kill it. When + ;; using ssh, it can sometimes happen that the remote end has + ;; hung up but the local ssh client doesn't recognize this until + ;; it tries to send some data to the remote end. So that's why + ;; we try to send a command from time to time, then look again + ;; whether the process is really alive. + (condition-case nil + (when (and (> (tramp-time-diff + (current-time) + (tramp-get-connection-property + p "last-cmd-time" '(0 0 0))) + 60) + p (processp p) (memq (process-status p) '(run open))) + (tramp-send-command vec "echo are you awake" t t) + (unless (and (memq (process-status p) '(run open)) + (tramp-wait-for-output p 10)) + ;; The error will be catched locally. + (tramp-error vec 'file-error "Awake did fail"))) + (file-error + (tramp-flush-connection-property vec) + (tramp-flush-connection-property p) + (delete-process p) + (setq p nil))) + + ;; New connection must be opened. + (unless (and p (processp p) (memq (process-status p) '(run open))) + + ;; We call `tramp-get-buffer' in order to get a debug buffer for + ;; messages from the beginning. + (tramp-get-buffer vec) + (with-progress-reporter + vec 3 + (if (zerop (length (tramp-file-name-user vec))) + (format "Opening connection for %s using %s" + (tramp-file-name-host vec) + (tramp-file-name-method vec)) + (format "Opening connection for %s@%s using %s" + (tramp-file-name-user vec) + (tramp-file-name-host vec) + (tramp-file-name-method vec))) + + ;; Start new process. + (when (and p (processp p)) + (delete-process p)) + (setenv "TERM" tramp-terminal-type) + (setenv "LC_ALL" "C") + (setenv "PROMPT_COMMAND") + (setenv "PS1" tramp-initial-end-of-output) + (let* ((target-alist (tramp-compute-multi-hops vec)) + (process-connection-type tramp-process-connection-type) + (process-adaptive-read-buffering nil) + (coding-system-for-read nil) + ;; This must be done in order to avoid our file name handler. + (p (let ((default-directory + (tramp-compat-temporary-file-directory))) + (start-process + (tramp-get-connection-name vec) + (tramp-get-connection-buffer vec) + tramp-encoding-shell)))) + + (tramp-message + vec 6 "%s" (mapconcat 'identity (process-command p) " ")) + + ;; Check whether process is alive. + (tramp-compat-set-process-query-on-exit-flag p nil) + (tramp-barf-if-no-shell-prompt + p 60 "Couldn't find local shell prompt %s" tramp-encoding-shell) + + ;; Now do all the connections as specified. + (while target-alist + (let* ((hop (car target-alist)) + (l-method (tramp-file-name-method hop)) + (l-user (tramp-file-name-user hop)) + (l-host (tramp-file-name-host hop)) + (l-port nil) + (login-program + (tramp-get-method-parameter + l-method 'tramp-login-program)) + (login-args + (tramp-get-method-parameter l-method 'tramp-login-args)) + (async-args + (tramp-get-method-parameter l-method 'tramp-async-args)) + (gw-args + (tramp-get-method-parameter l-method 'tramp-gw-args)) + (gw (tramp-get-file-property hop "" "gateway" nil)) + (g-method (and gw (tramp-file-name-method gw))) + (g-user (and gw (tramp-file-name-user gw))) + (g-host (and gw (tramp-file-name-host gw))) + (command login-program) + ;; We don't create the temporary file. In fact, + ;; it is just a prefix for the ControlPath option + ;; of ssh; the real temporary file has another + ;; name, and it is created and protected by ssh. + ;; It is also removed by ssh, when the connection + ;; is closed. + (tmpfile + (tramp-set-connection-property + p "temp-file" + (make-temp-name + (expand-file-name + tramp-temp-name-prefix + (tramp-compat-temporary-file-directory))))) + spec) + + ;; Add arguments for asynchrononous processes. + (when (and process-name async-args) + (setq login-args (append async-args login-args))) + + ;; Add gateway arguments if necessary. + (when (and gw gw-args) + (setq login-args (append gw-args login-args))) + + ;; Check for port number. Until now, there's no need + ;; for handling like method, user, host. + (when (string-match tramp-host-with-port-regexp l-host) + (setq l-port (match-string 2 l-host) + l-host (match-string 1 l-host))) + + ;; Set variables for computing the prompt for reading + ;; password. They can also be derived from a gateway. + (setq tramp-current-method (or g-method l-method) + tramp-current-user (or g-user l-user) + tramp-current-host (or g-host l-host)) + + ;; Replace login-args place holders. + (setq + l-host (or l-host "") + l-user (or l-user "") + l-port (or l-port "") + spec (format-spec-make + ?h l-host ?u l-user ?p l-port ?t tmpfile) + command + (concat + ;; We do not want to see the trailing local prompt in + ;; `start-file-process'. + (unless (memq system-type '(windows-nt)) "exec ") + command " " + (mapconcat + (lambda (x) + (setq x (mapcar (lambda (y) (format-spec y spec)) x)) + (unless (member "" x) (mapconcat 'identity x " "))) + login-args " ") + ;; Local shell could be a Windows COMSPEC. It + ;; doesn't know the ";" syntax, but we must exit + ;; always for `start-file-process'. "exec" does not + ;; work either. + (if (memq system-type '(windows-nt)) " && exit || exit"))) + + ;; Send the command. + (tramp-message vec 3 "Sending command `%s'" command) + (tramp-send-command vec command t t) + (tramp-process-actions p vec tramp-actions-before-shell 60) + (tramp-message + vec 3 "Found remote shell prompt on `%s'" l-host)) + ;; Next hop. + (setq target-alist (cdr target-alist))) + + ;; Make initial shell settings. + (tramp-open-connection-setup-interactive-shell p vec))))))) + +(defun tramp-send-command (vec command &optional neveropen nooutput) + "Send the COMMAND to connection VEC. +Erases temporary buffer before sending the command. If optional +arg NEVEROPEN is non-nil, never try to open the connection. This +is meant to be used from `tramp-maybe-open-connection' only. The +function waits for output unless NOOUTPUT is set." + (unless neveropen (tramp-maybe-open-connection vec)) + (let ((p (tramp-get-connection-process vec))) + (when (tramp-get-connection-property p "remote-echo" nil) + ;; We mark the command string that it can be erased in the output buffer. + (tramp-set-connection-property p "check-remote-echo" t) + (setq command (format "%s%s%s" tramp-echo-mark command tramp-echo-mark))) + (when (string-match "<<'EOF'" command) + ;; Unset $PS1 when using here documents, in order to avoid + ;; multiple prompts. + (setq command (concat "(PS1= ; " command "\n)"))) + ;; Send the command. + (tramp-message vec 6 "%s" command) + (tramp-send-string vec command) + (unless nooutput (tramp-wait-for-output p)))) + +(defun tramp-wait-for-output (proc &optional timeout) + "Wait for output from remote command." + (unless (buffer-live-p (process-buffer proc)) + (delete-process proc) + (tramp-error proc 'file-error "Process `%s' not available, try again" proc)) + (with-current-buffer (process-buffer proc) + (let* (;; Initially, `tramp-end-of-output' is "#$ ". There might + ;; be leading escape sequences, which must be ignored. + (regexp (format "[^#$\n]*%s\r?$" (regexp-quote tramp-end-of-output))) + ;; Sometimes, the commands do not return a newline but a + ;; null byte before the shell prompt, for example "git + ;; ls-files -c -z ...". + (regexp1 (format "\\(^\\|\000\\)%s" regexp)) + (found (tramp-wait-for-regexp proc timeout regexp1))) + (if found + (let (buffer-read-only) + ;; A simple-minded busybox has sent " ^H" sequences. + ;; Delete them. + (goto-char (point-min)) + (when (re-search-forward "^\\(.\b\\)+$" (point-at-eol) t) + (forward-line 1) + (delete-region (point-min) (point))) + ;; Delete the prompt. + (goto-char (point-max)) + (re-search-backward regexp nil t) + (delete-region (point) (point-max))) + (if timeout + (tramp-error + proc 'file-error + "[[Remote prompt `%s' not found in %d secs]]" + tramp-end-of-output timeout) + (tramp-error + proc 'file-error + "[[Remote prompt `%s' not found]]" tramp-end-of-output))) + ;; Return value is whether end-of-output sentinel was found. + found))) + +(defun tramp-send-command-and-check + (vec command &optional subshell dont-suppress-err) + "Run COMMAND and check its exit status. +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. If +DONT-SUPPRESS-ERR is non-nil, stderr won't be sent to /dev/null." + (tramp-send-command + vec + (concat (if subshell "( " "") + command + (if command (if dont-suppress-err "; " " 2>/dev/null; ") "") + "echo tramp_exit_status $?" + (if subshell " )" ""))) + (with-current-buffer (tramp-get-connection-buffer vec) + (goto-char (point-max)) + (unless (re-search-backward "tramp_exit_status [0-9]+" nil t) + (tramp-error + vec 'file-error "Couldn't find exit status of `%s'" command)) + (skip-chars-forward "^ ") + (prog1 + (zerop (read (current-buffer))) + (let (buffer-read-only) + (delete-region (match-beginning 0) (point-max)))))) + +(defun tramp-barf-unless-okay (vec command 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 (tramp-send-command-and-check vec command) + (apply 'tramp-error vec 'file-error fmt args))) + +(defun tramp-send-command-and-read (vec command) + "Run COMMAND and return the output, which must be a Lisp expression. +In case there is no valid Lisp expression, it raises an error" + (tramp-barf-unless-okay vec command "`%s' returns with error" command) + (with-current-buffer (tramp-get-connection-buffer vec) + ;; Read the expression. + (goto-char (point-min)) + (condition-case nil + (prog1 (read (current-buffer)) + ;; Error handling. + (when (re-search-forward "\\S-" (point-at-eol) t) + (error nil))) + (error (tramp-error + vec 'file-error + "`%s' does not return a valid Lisp expression: `%s'" + command (buffer-string)))))) + +(defun tramp-convert-file-attributes (vec attr) + "Convert file-attributes ATTR generated by perl script, stat or ls. +Convert file mode bits to string and set virtual device number. +Return ATTR." + (when attr + ;; Convert last access time. + (unless (listp (nth 4 attr)) + (setcar (nthcdr 4 attr) + (list (floor (nth 4 attr) 65536) + (floor (mod (nth 4 attr) 65536))))) + ;; Convert last modification time. + (unless (listp (nth 5 attr)) + (setcar (nthcdr 5 attr) + (list (floor (nth 5 attr) 65536) + (floor (mod (nth 5 attr) 65536))))) + ;; Convert last status change time. + (unless (listp (nth 6 attr)) + (setcar (nthcdr 6 attr) + (list (floor (nth 6 attr) 65536) + (floor (mod (nth 6 attr) 65536))))) + ;; Convert file size. + (when (< (nth 7 attr) 0) + (setcar (nthcdr 7 attr) -1)) + (when (and (floatp (nth 7 attr)) + (<= (nth 7 attr) (tramp-compat-most-positive-fixnum))) + (setcar (nthcdr 7 attr) (round (nth 7 attr)))) + ;; Convert file mode bits to string. + (unless (stringp (nth 8 attr)) + (setcar (nthcdr 8 attr) (tramp-file-mode-from-int (nth 8 attr))) + (when (stringp (car attr)) + (aset (nth 8 attr) 0 ?l))) + ;; Convert directory indication bit. + (when (string-match "^d" (nth 8 attr)) + (setcar attr t)) + ;; Convert symlink from `tramp-do-file-attributes-with-stat'. + (when (consp (car attr)) + (if (and (stringp (caar attr)) + (string-match ".+ -> .\\(.+\\)." (caar attr))) + (setcar attr (match-string 1 (caar attr))) + (setcar attr nil))) + ;; Set file's gid change bit. + (setcar (nthcdr 9 attr) + (if (numberp (nth 3 attr)) + (not (= (nth 3 attr) + (tramp-get-remote-gid vec 'integer))) + (not (string-equal + (nth 3 attr) + (tramp-get-remote-gid vec 'string))))) + ;; Convert inode. + (unless (listp (nth 10 attr)) + (setcar (nthcdr 10 attr) + (condition-case nil + (cons (floor (nth 10 attr) 65536) + (floor (mod (nth 10 attr) 65536))) + ;; Inodes can be incredible huge. We must hide this. + (error (tramp-get-inode vec))))) + ;; Set virtual device number. + (setcar (nthcdr 11 attr) + (tramp-get-device vec)) + attr)) + +(defun tramp-check-cached-permissions (vec access) + "Check `file-attributes' caches for VEC. +Return t if according to the cache access type ACCESS is known to +be granted." + (let ((result nil) + (offset (cond + ((eq ?r access) 1) + ((eq ?w access) 2) + ((eq ?x access) 3)))) + (dolist (suffix '("string" "integer") result) + (setq + result + (or + result + (let ((file-attr + (tramp-get-file-property + vec (tramp-file-name-localname vec) + (concat "file-attributes-" suffix) nil)) + (remote-uid + (tramp-get-connection-property + vec (concat "uid-" suffix) nil)) + (remote-gid + (tramp-get-connection-property + vec (concat "gid-" suffix) nil))) + (and + file-attr + (or + ;; Not a symlink + (eq t (car file-attr)) + (null (car file-attr))) + (or + ;; World accessible. + (eq access (aref (nth 8 file-attr) (+ offset 6))) + ;; User accessible and owned by user. + (and + (eq access (aref (nth 8 file-attr) offset)) + (equal remote-uid (nth 2 file-attr))) + ;; Group accessible and owned by user's + ;; principal group. + (and + (eq access (aref (nth 8 file-attr) (+ offset 3))) + (equal remote-gid (nth 3 file-attr))))))))))) + +(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-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 + "")) + +(defun tramp-make-copy-program-file-name (vec) + "Create a file name suitable to be passed to `rcp' and workalikes." + (let ((user (tramp-file-name-user vec)) + (host (tramp-file-name-real-host vec)) + (localname (tramp-shell-quote-argument + (tramp-file-name-localname vec)))) + (if (not (zerop (length user))) + (format "%s@%s:%s" user host localname) + (format "%s:%s" host localname)))) + +(defun tramp-method-out-of-band-p (vec size) + "Return t if this is an out-of-band method, nil otherwise." + (and + ;; It shall be an out-of-band method. + (tramp-get-method-parameter (tramp-file-name-method vec) 'tramp-copy-program) + ;; Either the file size is large enough, or (in rare cases) there + ;; does not exist a remote encoding. + (or (null tramp-copy-size-limit) + (> size tramp-copy-size-limit) + (null (tramp-get-inline-coding vec "remote-encoding" size))))) + +;; Variables local to connection. + +(defun tramp-get-remote-path (vec) + (with-connection-property + ;; When `tramp-own-remote-path' is in `tramp-remote-path', we + ;; cache the result for the session only. Otherwise, the result + ;; is cached persistently. + (if (memq 'tramp-own-remote-path tramp-remote-path) + (tramp-get-connection-process vec) + vec) + "remote-path" + (let* ((remote-path (copy-tree tramp-remote-path)) + (elt1 (memq 'tramp-default-remote-path remote-path)) + (elt2 (memq 'tramp-own-remote-path remote-path)) + (default-remote-path + (when elt1 + (condition-case nil + (tramp-send-command-and-read + vec "echo \\\"`getconf PATH`\\\"") + ;; Default if "getconf" is not available. + (error + (tramp-message + vec 3 + "`getconf PATH' not successful, using default value \"%s\"." + "/bin:/usr/bin") + "/bin:/usr/bin")))) + (own-remote-path + (when elt2 + (condition-case nil + (tramp-send-command-and-read vec "echo \\\"$PATH\\\"") + ;; Default if "getconf" is not available. + (error + (tramp-message + vec 3 "$PATH not set, ignoring `tramp-own-remote-path'.") + nil))))) + + ;; Replace place holder `tramp-default-remote-path'. + (when elt1 + (setcdr elt1 + (append + (tramp-compat-split-string default-remote-path ":") + (cdr elt1))) + (setq remote-path (delq 'tramp-default-remote-path remote-path))) + + ;; Replace place holder `tramp-own-remote-path'. + (when elt2 + (setcdr elt2 + (append + (tramp-compat-split-string own-remote-path ":") + (cdr elt2))) + (setq remote-path (delq 'tramp-own-remote-path remote-path))) + + ;; Remove double entries. + (setq elt1 remote-path) + (while (consp elt1) + (while (and (car elt1) (setq elt2 (member (car elt1) (cdr elt1)))) + (setcar elt2 nil)) + (setq elt1 (cdr elt1))) + + ;; Remove non-existing directories. + (delq + nil + (mapcar + (lambda (x) + (and + (stringp x) + (file-directory-p + (tramp-make-tramp-file-name + (tramp-file-name-method vec) + (tramp-file-name-user vec) + (tramp-file-name-host vec) + x)) + x)) + remote-path))))) + +(defun tramp-get-remote-tmpdir (vec) + (with-connection-property vec "tmp-directory" + (let ((dir (tramp-shell-quote-argument "/tmp"))) + (if (and (tramp-send-command-and-check + vec (format "%s -d %s" (tramp-get-test-command vec) dir)) + (tramp-send-command-and-check + vec (format "%s -w %s" (tramp-get-test-command vec) dir))) + dir + (tramp-error vec 'file-error "Directory %s not accessible" dir))))) + +(defun tramp-get-ls-command (vec) + (with-connection-property vec "ls" + (tramp-message vec 5 "Finding a suitable `ls' command") + (or + (catch 'ls-found + (dolist (cmd '("ls" "gnuls" "gls")) + (let ((dl (tramp-get-remote-path vec)) + result) + (while (and dl (setq result (tramp-find-executable vec cmd dl t t))) + ;; Check parameters. On busybox, "ls" output coloring is + ;; enabled by default sometimes. So we try to disable it + ;; when possible. $LS_COLORING is not supported there. + ;; Some "ls" versions are sensible wrt the order of + ;; arguments, they fail when "-al" is after the + ;; "--color=never" argument (for example on FreeBSD). + (when (tramp-send-command-and-check + vec (format "%s -lnd /" result)) + (when (tramp-send-command-and-check + vec (format + "%s --color=never -al /dev/null" result)) + (setq result (concat result " --color=never"))) + (throw 'ls-found result)) + (setq dl (cdr dl)))))) + (tramp-error vec 'file-error "Couldn't find a proper `ls' command")))) + +(defun tramp-get-ls-command-with-dired (vec) + (save-match-data + (with-connection-property vec "ls-dired" + (tramp-message vec 5 "Checking, whether `ls --dired' works") + ;; Some "ls" versions are sensible wrt the order of arguments, + ;; they fail when "-al" is after the "--dired" argument (for + ;; example on FreeBSD). + (tramp-send-command-and-check + vec (format "%s --dired -al /dev/null" (tramp-get-ls-command vec)))))) + +(defun tramp-get-test-command (vec) + (with-connection-property vec "test" + (tramp-message vec 5 "Finding a suitable `test' command") + (if (tramp-send-command-and-check vec "test 0") + "test" + (tramp-find-executable vec "test" (tramp-get-remote-path vec))))) + +(defun tramp-get-test-nt-command (vec) + ;; 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. + (with-connection-property vec "test-nt" + (or + (progn + (tramp-send-command + vec (format "( %s / -nt / )" (tramp-get-test-command vec))) + (with-current-buffer (tramp-get-buffer vec) + (goto-char (point-min)) + (when (looking-at (regexp-quote tramp-end-of-output)) + (format "%s %%s -nt %%s" (tramp-get-test-command vec))))) + (progn + (tramp-send-command + vec + (format + "tramp_test_nt () {\n%s -n \"`find $1 -prune -newer $2 -print`\"\n}" + (tramp-get-test-command vec))) + "tramp_test_nt %s %s")))) + +(defun tramp-get-file-exists-command (vec) + (with-connection-property vec "file-exists" + (tramp-message vec 5 "Finding command to check if file exists") + (tramp-find-file-exists-command vec))) + +(defun tramp-get-remote-ln (vec) + (with-connection-property vec "ln" + (tramp-message vec 5 "Finding a suitable `ln' command") + (tramp-find-executable vec "ln" (tramp-get-remote-path vec)))) + +(defun tramp-get-remote-perl (vec) + (with-connection-property vec "perl" + (tramp-message vec 5 "Finding a suitable `perl' command") + (let ((result + (or (tramp-find-executable vec "perl5" (tramp-get-remote-path vec)) + (tramp-find-executable + vec "perl" (tramp-get-remote-path vec))))) + ;; We must check also for some Perl modules. + (when result + (with-connection-property vec "perl-file-spec" + (tramp-send-command-and-check + vec (format "%s -e 'use File::Spec;'" result))) + (with-connection-property vec "perl-cwd-realpath" + (tramp-send-command-and-check + vec (format "%s -e 'use Cwd \"realpath\";'" result)))) + result))) + +(defun tramp-get-remote-stat (vec) + (with-connection-property vec "stat" + (tramp-message vec 5 "Finding a suitable `stat' command") + (let ((result (tramp-find-executable + vec "stat" (tramp-get-remote-path vec))) + tmp) + ;; Check whether stat(1) returns usable syntax. %s does not + ;; work on older AIX systems. + (when result + (setq tmp + ;; We don't want to display an error message. + (tramp-compat-with-temp-message (or (current-message) "") + (ignore-errors + (tramp-send-command-and-read + vec (format "%s -c '(\"%%N\" %%s)' /" result))))) + (unless (and (listp tmp) (stringp (car tmp)) + (string-match "^./.$" (car tmp)) + (integerp (cadr tmp))) + (setq result nil))) + result))) + +(defun tramp-get-remote-readlink (vec) + (with-connection-property vec "readlink" + (tramp-message vec 5 "Finding a suitable `readlink' command") + (let ((result (tramp-find-executable + vec "readlink" (tramp-get-remote-path vec)))) + (when (and result + ;; We don't want to display an error message. + (tramp-compat-with-temp-message (or (current-message) "") + (ignore-errors + (tramp-send-command-and-check + vec (format "%s --canonicalize-missing /" result))))) + result)))) + +(defun tramp-get-remote-trash (vec) + (with-connection-property vec "trash" + (tramp-message vec 5 "Finding a suitable `trash' command") + (tramp-find-executable vec "trash" (tramp-get-remote-path vec)))) + +(defun tramp-get-remote-id (vec) + (with-connection-property vec "id" + (tramp-message vec 5 "Finding POSIX `id' command") + (or + (catch 'id-found + (let ((dl (tramp-get-remote-path vec)) + result) + (while (and dl (setq result (tramp-find-executable vec "id" dl t t))) + ;; Check POSIX parameter. + (when (tramp-send-command-and-check vec (format "%s -u" result)) + (throw 'id-found result)) + (setq dl (cdr dl))))) + (tramp-error vec 'file-error "Couldn't find a POSIX `id' command")))) + +(defun tramp-get-remote-uid (vec id-format) + (with-connection-property vec (format "uid-%s" id-format) + (let ((res (tramp-send-command-and-read + vec + (format "%s -u%s %s" + (tramp-get-remote-id vec) + (if (equal id-format 'integer) "" "n") + (if (equal id-format 'integer) + "" "| sed -e s/^/\\\"/ -e s/\$/\\\"/"))))) + ;; The command might not always return a number. + (if (and (equal id-format 'integer) (not (integerp res))) -1 res)))) + +(defun tramp-get-remote-gid (vec id-format) + (with-connection-property vec (format "gid-%s" id-format) + (let ((res (tramp-send-command-and-read + vec + (format "%s -g%s %s" + (tramp-get-remote-id vec) + (if (equal id-format 'integer) "" "n") + (if (equal id-format 'integer) + "" "| sed -e s/^/\\\"/ -e s/\$/\\\"/"))))) + ;; The command might not always return a number. + (if (and (equal id-format 'integer) (not (integerp res))) -1 res)))) + +(defun tramp-get-local-uid (id-format) + (if (equal id-format 'integer) (user-uid) (user-login-name))) + +(defun tramp-get-local-gid (id-format) + (nth 3 (tramp-compat-file-attributes "~/" id-format))) + +;; Some predefined connection properties. +(defun tramp-get-inline-compress (vec prop size) + "Return the compress command related to PROP. +PROP is either `inline-compress' or `inline-decompress'. SIZE is +the length of the file to be compressed. + +If no corresponding command is found, nil is returned." + (when (and (integerp tramp-inline-compress-start-size) + (> size tramp-inline-compress-start-size)) + (with-connection-property vec prop + (tramp-find-inline-compress vec) + (tramp-get-connection-property vec prop nil)))) + +(defun tramp-get-inline-coding (vec prop size) + "Return the coding command related to PROP. +PROP is either `remote-encoding', `remode-decoding', +`local-encoding' or `local-decoding'. + +SIZE is the length of the file to be coded. Depending on SIZE, +compression might be applied. + +If no corresponding command is found, nil is returned. +Otherwise, either a string is returned which contains a `%s' mark +to be used for the respective input or output file; or a Lisp +function cell is returned to be applied on a buffer." + ;; We must catch the errors, because we want to return `nil', when + ;; no inline coding is found. + (ignore-errors + (let ((coding + (with-connection-property vec prop + (tramp-find-inline-encoding vec) + (tramp-get-connection-property vec prop nil))) + (prop1 (if (string-match "encoding" prop) + "inline-compress" "inline-decompress")) + compress) + ;; The connection property might have been cached. So we must + ;; send the script to the remote side - maybe. + (when (and coding (symbolp coding) (string-match "remote" prop)) + (let ((name (symbol-name coding))) + (while (string-match (regexp-quote "-") name) + (setq name (replace-match "_" nil t name))) + (tramp-maybe-send-script vec (symbol-value coding) name) + (setq coding name))) + (when coding + ;; Check for the `compress' command. + (setq compress (tramp-get-inline-compress vec prop1 size)) + ;; Return the value. + (cond + ((and compress (symbolp coding)) + (if (string-match "decompress" prop1) + `(lambda (beg end) + (,coding beg end) + (let ((coding-system-for-write 'binary) + (coding-system-for-read 'binary)) + (apply + 'call-process-region (point-min) (point-max) + (car (split-string ,compress)) t t nil + (cdr (split-string ,compress))))) + `(lambda (beg end) + (let ((coding-system-for-write 'binary) + (coding-system-for-read 'binary)) + (apply + 'call-process-region beg end + (car (split-string ,compress)) t t nil + (cdr (split-string ,compress)))) + (,coding (point-min) (point-max))))) + ((symbolp coding) + coding) + ((and compress (string-match "decoding" prop)) - (format "(%s | %s >%%s)" coding compress)) ++ (format ++ ;; Windows shells need the program file name after ++ ;; the pipe symbol be quoted if they use forward ++ ;; slashes as directory separators. ++ (if (and (string-match "local" prop) ++ (memq system-type '(windows-nt))) ++ "(%s | \"%s\" >%%s)" ++ "(%s | %s >%%s)") ++ coding compress)) + (compress - (format "(%s <%%s | %s)" compress coding)) ++ (format ++ ;; Windows shells need the program file name after ++ ;; the pipe symbol be quoted if they use forward ++ ;; slashes as directory separators. ++ (if (and (string-match "local" prop) ++ (memq system-type '(windows-nt))) ++ "(%s <%%s | \"%s\")" ++ "(%s <%%s | %s)") ++ compress coding)) + ((string-match "decoding" prop) + (format "%s >%%s" coding)) + (t + (format "%s <%%s" coding))))))) + +;;; Integration of eshell.el: + +(eval-when-compile + (defvar eshell-path-env)) + +;; eshell.el keeps the path in `eshell-path-env'. We must change it +;; when `default-directory' points to another host. +(defun tramp-eshell-directory-change () + "Set `eshell-path-env' to $PATH of the host related to `default-directory'." + (setq eshell-path-env + (if (file-remote-p default-directory) + (with-parsed-tramp-file-name default-directory nil + (mapconcat + 'identity + (tramp-get-remote-path v) + ":")) + (getenv "PATH")))) + +(eval-after-load "esh-util" + '(progn + (tramp-eshell-directory-change) + (add-hook 'eshell-directory-change-hook + 'tramp-eshell-directory-change) + (add-hook 'tramp-unload-hook + (lambda () + (remove-hook 'eshell-directory-change-hook + 'tramp-eshell-directory-change))))) + +(add-hook 'tramp-unload-hook + (lambda () + (unload-feature 'tramp-sh 'force))) + +(provide 'tramp-sh) + +;;; TODO: + +;; * 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. +;; * Make it work for different encodings, and for different file name +;; encodings, too. (Daniel Pittman) +;; * 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. +;; * Allow out-of-band methods as _last_ multi-hop. Open a connection +;; until the last but one hop via `start-file-process'. Apply it +;; also for ftp and smb. +;; * WIBNI if we had a command "trampclient"? If I was editing in +;; some shell with root priviledges, it would be nice if I could +;; just call +;; trampclient filename.c +;; as an editor, and the _current_ shell would connect to an Emacs +;; server and would be used in an existing non-priviledged Emacs +;; session for doing the editing in question. +;; That way, I need not tell Emacs my password again and be afraid +;; that it makes it into core dumps or other ugly stuff (I had Emacs +;; once display a just typed password in the context of a keyboard +;; sequence prompt for a question immediately following in a shell +;; script run within Emacs -- nasty). +;; And if I have some ssh session running to a different computer, +;; having the possibility of passing a local file there to a local +;; Emacs session (in case I can arrange for a connection back) would +;; be nice. +;; Likely the corresponding Tramp server should not allow the +;; equivalent of the emacsclient -eval option in order to make this +;; reasonably unproblematic. And maybe trampclient should have some +;; way of passing credentials, like by using an SSL socket or +;; something. (David Kastrup) +;; * Reconnect directly to a compliant shell without first going +;; through the user's default shell. (Pete Forman) +;; * How can I interrupt the remote process with a signal +;; (interrupt-process seems not to work)? (Markus Triska) +;; * Avoid the local shell entirely for starting remote processes. If +;; so, I think even a signal, when delivered directly to the local +;; SSH instance, would correctly be propagated to the remote process +;; automatically; possibly SSH would have to be started with +;; "-t". (Markus Triska) +;; * It makes me wonder if tramp couldn't fall back to ssh when scp +;; isn't on the remote host. (Mark A. Hershberger) +;; * Use lsh instead of ssh. (Alfred M. Szmidt) +;; * Optimize out-of-band copying, when both methods are scp-like (not +;; rsync). +;; * Keep a second connection open for out-of-band methods like scp or +;; rsync. +;; * Try telnet+curl as new method. It might be useful for busybox, +;; without built-in uuencode/uudecode. + +;;; tramp-sh.el ends here diff --cc lisp/net/tramp.el index cd9ef314acc,0ac5048ef3b..ef312af80ba --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@@ -1,7 -1,9 +1,7 @@@ ;;; tramp.el --- Transparent Remote Access, Multiple Protocol --;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, --;; 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. - -;; (copyright statements below in code to be updated with the above notice) ++;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, ++;; 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. ;; Author: Kai Großjohann ;; Michael Albinus @@@ -291,8 -780,8 +291,11 @@@ shouldn't return t when it isn't. ;; password caching. "scpc" is chosen if we detect that the user is ;; running OpenSSH 4.0 or newer. (cond -- ;; PuTTY is installed. -- ((executable-find "pscp") ++ ;; PuTTY is installed. We don't take it, if it is installed on a ++ ;; non-windows system, or pscp from the pssh (parallel ssh) package ++ ;; is found. ++ ((and (eq system-type 'windows-nt) ++ (executable-find "pscp")) (if (or (fboundp 'password-read) (fboundp 'auth-source-user-or-password) ;; Pageant is running. diff --cc lisp/progmodes/grep.el index 8e36d3f5c6d,dac14d07ef8..b327cf6b87d --- a/lisp/progmodes/grep.el +++ b/lisp/progmodes/grep.el @@@ -565,7 -561,7 +565,10 @@@ Set up `compilation-exit-message-functi (unless grep-find-command (setq grep-find-command (cond ((eq grep-find-use-xargs 'gnu) -- (format "%s . -type f -print0 | %s -0 -e %s" ++ ;; Windows shells need the program file name ++ ;; after the pipe symbol be quoted if they use ++ ;; forward slashes as directory separators. ++ (format "%s . -type f -print0 | \"%s\" -0 -e %s" find-program xargs-program grep-command)) ((eq grep-find-use-xargs 'exec) (let ((cmd0 (format "%s . -type f -exec %s" @@@ -576,21 -572,21 +579,21 @@@ (shell-quote-argument ";")) (1+ (length cmd0))))) (t -- (format "%s . -type f -print | %s %s" ++ (format "%s . -type f -print | \"%s\" %s" find-program xargs-program grep-command))))) (unless grep-find-template (setq grep-find-template (let ((gcmd (format "%s %s " grep-program grep-options))) (cond ((eq grep-find-use-xargs 'gnu) -- (format "%s . -type f -print0 | %s -0 -e %s" ++ (format "%s . -type f -print0 | \"%s\" -0 -e %s" find-program xargs-program gcmd)) ((eq grep-find-use-xargs 'exec) (format "%s . -type f -exec %s {} %s %s" find-program gcmd null-device (shell-quote-argument ";"))) (t -- (format "%s . -type f -print | %s %s" ++ (format "%s . -type f -print | \"%s\" %s" find-program xargs-program gcmd)))))))) (when (eq grep-highlight-matches 'auto-detect) (setq grep-highlight-matches diff --cc lisp/tmm.el index 3d8433e281f,3d8433e281f..0341b5384f0 --- a/lisp/tmm.el +++ b/lisp/tmm.el @@@ -168,14 -168,14 +168,13 @@@ Its value should be an event that has ;; It has no other elements. ;; The order of elements in tmm-km-list is the order of the menu bar. (mapc (lambda (elt) -- (if (stringp elt) -- (setq gl-str elt) -- (cond -- ((listp elt) (tmm-get-keymap elt not-menu)) -- ((vectorp elt) -- (dotimes (i (length elt)) -- (tmm-get-keymap (cons i (aref elt i)) not-menu)))))) -- menu) ++ (cond ++ ((stringp elt) (setq gl-str elt)) ++ ((listp elt) (tmm-get-keymap elt not-menu)) ++ ((vectorp elt) ++ (dotimes (i (length elt)) ++ (tmm-get-keymap (cons i (aref elt i)) not-menu))))) ++ menu) ;; Choose an element of tmm-km-list; put it in choice. (if (and not-menu (= 1 (length tmm-km-list))) ;; If this is the top-level of an x-popup-menu menu, @@@ -368,32 -368,32 +367,31 @@@ Stores a list of all the shortcuts in t (add-hook 'minibuffer-exit-hook 'tmm-delete-map nil t) (unless tmm-c-prompt (error "No active menu entries")) -- (let ((win (selected-window))) -- (setq tmm-old-mb-map (tmm-define-keys t)) -- ;; Get window and hide it for electric mode to get correct size -- (save-window-excursion -- (let ((completions -- (mapcar 'car minibuffer-completion-table))) -- (or tmm-completion-prompt -- (add-hook 'completion-setup-hook -- 'tmm-completion-delete-prompt 'append)) -- (unwind-protect -- (with-output-to-temp-buffer "*Completions*" -- (display-completion-list completions)) -- (remove-hook 'completion-setup-hook 'tmm-completion-delete-prompt))) -- (set-buffer "*Completions*") -- (tmm-remove-inactive-mouse-face) -- (when tmm-completion-prompt -- (let ((buffer-read-only nil)) -- (goto-char (point-min)) -- (insert tmm-completion-prompt)))) -- (save-selected-window -- (other-window 1) ; Electric-pop-up-window does ++ (setq tmm-old-mb-map (tmm-define-keys t)) ++ ;; Get window and hide it for electric mode to get correct size ++ (save-window-excursion ++ (let ((completions ++ (mapcar 'car minibuffer-completion-table))) ++ (or tmm-completion-prompt ++ (add-hook 'completion-setup-hook ++ 'tmm-completion-delete-prompt 'append)) ++ (unwind-protect ++ (with-output-to-temp-buffer "*Completions*" ++ (display-completion-list completions)) ++ (remove-hook 'completion-setup-hook 'tmm-completion-delete-prompt))) ++ (set-buffer "*Completions*") ++ (tmm-remove-inactive-mouse-face) ++ (when tmm-completion-prompt ++ (let ((buffer-read-only nil)) ++ (goto-char (point-min)) ++ (insert tmm-completion-prompt)))) ++ (save-selected-window ++ (other-window 1) ; Electric-pop-up-window does ; not work in minibuffer -- (Electric-pop-up-window "*Completions*") -- (with-current-buffer "*Completions*" -- (setq tmm-old-comp-map (tmm-define-keys nil)))) -- (insert tmm-c-prompt))) ++ (Electric-pop-up-window "*Completions*") ++ (with-current-buffer "*Completions*" ++ (setq tmm-old-comp-map (tmm-define-keys nil)))) ++ (insert tmm-c-prompt)) (defun tmm-delete-map () (remove-hook 'minibuffer-exit-hook 'tmm-delete-map t) @@@ -497,7 -497,7 +495,7 @@@ It uses the free variable `tmm-table-un (if (or in-x-menu (stringp (car-safe elt))) (setq str event event nil km elt) (setq str event event nil km (cons 'keymap elt))))) -- (unless (eq km 'ignore) ++ (unless (or (eq km 'ignore) (null str)) (let ((binding (where-is-internal km nil t))) (when binding (setq binding (key-description binding)) diff --cc lisp/vc/vc-bzr.el index d5d3c9d4888,00000000000..1f368e1169b mode 100644,000000..100644 --- a/lisp/vc/vc-bzr.el +++ b/lisp/vc/vc-bzr.el @@@ -1,1172 -1,0 +1,1172 @@@ +;;; vc-bzr.el --- VC backend for the bzr revision control system + +;; Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. + +;; Author: Dave Love +;; Riccardo Murri +;; Maintainer: FSF +;; Keywords: vc tools +;; Created: Sept 2006 +;; Package: vc + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; See concerning bzr. + +;; This library provides bzr support in VC. + +;; Known bugs +;; ========== + +;; When editing a symlink and *both* the symlink and its target +;; are bzr-versioned, `vc-bzr` presently runs `bzr status` on the +;; symlink, thereby not detecting whether the actual contents +;; (that is, the target contents) are changed. +;; See https://bugs.launchpad.net/vc-bzr/+bug/116607 + +;;; Properties of the backend + +(defun vc-bzr-revision-granularity () 'repository) +(defun vc-bzr-checkout-model (files) 'implicit) + +;;; Code: + +(eval-when-compile + (require 'cl) + (require 'vc) ;; for vc-exec-after + (require 'vc-dir)) + +;; Clear up the cache to force vc-call to check again and discover +;; new functions when we reload this file. +(put 'Bzr 'vc-functions nil) + +(defgroup vc-bzr nil + "VC bzr backend." + :version "22.2" + :group 'vc) + +(defcustom vc-bzr-program "bzr" + "Name of the bzr command (excluding any arguments)." + :group 'vc-bzr + :type 'string) + +(defcustom vc-bzr-diff-switches nil + "String or list of strings specifying switches for bzr diff under VC. +If nil, use the value of `vc-diff-switches'. If t, use no switches." + :type '(choice (const :tag "Unspecified" nil) + (const :tag "None" t) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :group 'vc-bzr) + +(defcustom vc-bzr-log-switches nil + "String or list of strings specifying switches for bzr log under VC." + :type '(choice (const :tag "None" nil) + (string :tag "Argument String") + (repeat :tag "Argument List" :value ("") string)) + :group 'vc-bzr) + +;; since v0.9, bzr supports removing the progress indicators +;; by setting environment variable BZR_PROGRESS_BAR to "none". +(defun vc-bzr-command (bzr-command buffer okstatus file-or-list &rest args) + "Wrapper round `vc-do-command' using `vc-bzr-program' as COMMAND. +Invoke the bzr command adding `BZR_PROGRESS_BAR=none' and +`LC_MESSAGES=C' to the environment." + (let ((process-environment + (list* "BZR_PROGRESS_BAR=none" ; Suppress progress output (bzr >=0.9) + "LC_MESSAGES=C" ; Force English output + process-environment))) + (apply 'vc-do-command (or buffer "*vc*") okstatus vc-bzr-program + file-or-list bzr-command args))) + + +;;;###autoload +(defconst vc-bzr-admin-dirname ".bzr" + "Name of the directory containing Bzr repository status files.") +;; Used in the autoloaded vc-bzr-registered; see below. +;;;###autoload +(defconst vc-bzr-admin-checkout-format-file + (concat vc-bzr-admin-dirname "/checkout/format")) +(defconst vc-bzr-admin-dirstate + (concat vc-bzr-admin-dirname "/checkout/dirstate")) +(defconst vc-bzr-admin-branch-format-file + (concat vc-bzr-admin-dirname "/branch/format")) +(defconst vc-bzr-admin-revhistory + (concat vc-bzr-admin-dirname "/branch/revision-history")) +(defconst vc-bzr-admin-lastrev + (concat vc-bzr-admin-dirname "/branch/last-revision")) +(defconst vc-bzr-admin-branchconf + (concat vc-bzr-admin-dirname "/branch/branch.conf")) + +;;;###autoload (defun vc-bzr-registered (file) +;;;###autoload (if (vc-find-root file vc-bzr-admin-checkout-format-file) +;;;###autoload (progn +;;;###autoload (load "vc-bzr") +;;;###autoload (vc-bzr-registered file)))) + +(defun vc-bzr-root (file) + "Return the root directory of the bzr repository containing FILE." + ;; Cache technique copied from vc-arch.el. + (or (vc-file-getprop file 'bzr-root) + (let ((root (vc-find-root file vc-bzr-admin-checkout-format-file))) + (when root (vc-file-setprop file 'bzr-root root))))) + +(defun vc-bzr--branch-conf (file) + "Return the Bzr branch config for file FILE, as a string." + (with-temp-buffer + (insert-file-contents + (expand-file-name vc-bzr-admin-branchconf (vc-bzr-root file))) + (buffer-string))) + +(require 'sha1) ;For sha1-program + +(defun vc-bzr-sha1 (file) + (with-temp-buffer + (set-buffer-multibyte nil) + (let ((prog sha1-program) + (args nil) + process-file-side-effects) + (when (consp prog) + (setq args (cdr prog)) + (setq prog (car prog))) + (apply 'process-file prog (file-relative-name file) t nil args) + (buffer-substring (point-min) (+ (point-min) 40))))) + +(defun vc-bzr-state-heuristic (file) + "Like `vc-bzr-state' but hopefully without running Bzr." + ;; `bzr status' was excruciatingly slow with large histories and + ;; pending merges, so try to avoid using it until they fix their + ;; performance problems. + ;; This function tries first to parse Bzr internal file + ;; `checkout/dirstate', but it may fail if Bzr internal file format + ;; has changed. As a safeguard, the `checkout/dirstate' file is + ;; only parsed if it contains the string `#bazaar dirstate flat + ;; format 3' in the first line. + ;; If the `checkout/dirstate' file cannot be parsed, fall back to + ;; running `vc-bzr-state'." + (lexical-let ((root (vc-bzr-root file))) + (when root ; Short cut. + ;; This looks at internal files. May break if they change + ;; their format. + (lexical-let ((dirstate (expand-file-name vc-bzr-admin-dirstate root))) + (condition-case nil + (with-temp-buffer + (insert-file-contents dirstate) + (goto-char (point-min)) + (if (not (looking-at "#bazaar dirstate flat format 3")) + (vc-bzr-state file) ; Some other unknown format? + (let* ((relfile (file-relative-name file root)) + (reldir (file-name-directory relfile))) + (if (re-search-forward + (concat "^\0" + (if reldir (regexp-quote + (directory-file-name reldir))) + "\0" + (regexp-quote (file-name-nondirectory relfile)) + "\0" + "[^\0]*\0" ;id? + "\\([^\0]*\\)\0" ;"a/f/d", a=removed? + "\\([^\0]*\\)\0" ;sha1 (empty if conflicted)? + "\\([^\0]*\\)\0" ;size?p + ;; y/n. Whether or not the current copy + ;; was executable the last time bzr checked? + "[^\0]*\0" + "[^\0]*\0" ;? + "\\([^\0]*\\)\0" ;"a/f/d" a=added? + "\\([^\0]*\\)\0" ;sha1 again? + "\\([^\0]*\\)\0" ;size again? + ;; y/n. Whether or not the repo thinks + ;; the file should be executable? + "\\([^\0]*\\)\0" + "[^\0]*\0" ;last revid? + ;; There are more fields when merges are pending. + ) + nil t) + ;; Apparently the second sha1 is the one we want: when + ;; there's a conflict, the first sha1 is absent (and the + ;; first size seems to correspond to the file with + ;; conflict markers). + (cond + ((eq (char-after (match-beginning 1)) ?a) 'removed) + ((eq (char-after (match-beginning 4)) ?a) 'added) + ((or (and (eq (string-to-number (match-string 3)) + (nth 7 (file-attributes file))) + (equal (match-string 5) + (vc-bzr-sha1 file)) + ;; For a file, does the executable state match? + ;; (Bug#7544) + (or (not + (eq (char-after (match-beginning 1)) ?f)) + (let ((exe + (memq + ?x + (mapcar + 'identity + (nth 8 (file-attributes file)))))) + (if (eq (char-after (match-beginning 7)) + ?y) + exe + (not exe))))) + (and + ;; It looks like for lightweight + ;; checkouts \2 is empty and we need to + ;; look for size in \6. + (eq (match-beginning 2) (match-end 2)) + (eq (string-to-number (match-string 6)) + (nth 7 (file-attributes file))) + (equal (match-string 5) + (vc-bzr-sha1 file)))) + 'up-to-date) + (t 'edited)) + 'unregistered)))) + ;; Either the dirstate file can't be read, or the sha1 + ;; executable is missing, or ... + ;; In either case, recent versions of Bzr aren't that slow + ;; any more. + (error (vc-bzr-state file))))))) + + +(defun vc-bzr-registered (file) + "Return non-nil if FILE is registered with bzr." + (let ((state (vc-bzr-state-heuristic file))) + (not (memq state '(nil unregistered ignored))))) + +(defconst vc-bzr-state-words + "added\\|ignored\\|kind changed\\|modified\\|removed\\|renamed\\|unknown" + "Regexp matching file status words as reported in `bzr' output.") + +;; History of Bzr commands. +(defvar vc-bzr-history nil) + +(defun vc-bzr-file-name-relative (filename) + "Return file name FILENAME stripped of the initial Bzr repository path." + (lexical-let* + ((filename* (expand-file-name filename)) + (rootdir (vc-bzr-root filename*))) + (when rootdir + (file-relative-name filename* rootdir)))) + +(defun vc-bzr-async-command (command args) + "Run Bzr COMMAND asynchronously with ARGS, displaying the result. +Send the output to a buffer named \"*vc-bzr : NAME*\", where NAME +is the root of the current Bzr branch. Display the buffer in +some window, but don't select it." + ;; TODO: set up hyperlinks. + (let* ((dir default-directory) + (root (vc-bzr-root default-directory)) + (buffer (get-buffer-create + (format "*vc-bzr : %s*" + (expand-file-name root))))) + (with-current-buffer buffer + (setq default-directory root) + (goto-char (point-max)) + (unless (eq (point) (point-min)) + (insert " \n")) + (insert "Running \"" vc-bzr-program " " command) + (dolist (arg args) + (insert " " arg)) + (insert "\"...\n") + ;; Run bzr in the original working directory. + (let ((default-directory dir)) + (apply 'vc-bzr-command command t 'async nil args))) + (display-buffer buffer))) + +(defun vc-bzr-pull (prompt) + "Pull changes into the current Bzr branch. +Normally, this runs \"bzr pull\". However, if the branch is a +bound branch, run \"bzr update\" instead. If there is no default +location from which to pull or update, or if PROMPT is non-nil, +prompt for the Bzr command to run." + (let* ((vc-bzr-program vc-bzr-program) + (branch-conf (vc-bzr--branch-conf default-directory)) + ;; Check whether the branch is bound. + (bound (string-match "^bound\\s-*=\\s-*True" branch-conf)) + ;; If we need to do a "bzr pull", check for a parent. If it + ;; does not exist, bzr will need a pull location. + (parent (unless bound + (string-match + "^parent_location\\s-*=\\s-*[^\n[:space:]]+" + branch-conf))) + (command (if bound "update" "pull")) + args) + ;; If necessary, prompt for the exact command. + (when (or prompt (not (or bound parent))) + (setq args (split-string + (read-shell-command + "Run Bzr (like this): " + (concat vc-bzr-program " " command) + 'vc-bzr-history) + " " t)) + (setq vc-bzr-program (car args) + command (cadr args) + args (cddr args))) + (vc-bzr-async-command command args))) + +(defun vc-bzr-merge-branch () + "Merge another Bzr branch into the current one. +Prompt for the Bzr command to run, providing a pre-defined merge +source (an upstream branch or a previous merge source) as a +default if it is available." + (let* ((branch-conf (vc-bzr--branch-conf default-directory)) + ;; "bzr merge" without an argument defaults to submit_branch, + ;; then parent_location. We extract the specific location + ;; and add it explicitly to the command line. + (location + (cond + ((string-match + "^submit_branch\\s-*=\\s-*\\(?:file://\\)?\\([^\n[:space:]]+\\)$" + branch-conf) + (match-string 1 branch-conf)) + ((string-match + "^parent_location\\s-*=\\s-*\\(?:file://\\)?\\([^\n[:space:]]+\\)$" + branch-conf) + (match-string 1 branch-conf)))) + (cmd + (split-string + (read-shell-command + "Run Bzr (like this): " + (concat vc-bzr-program " merge --pull" + (if location (concat " " location) "")) + 'vc-bzr-history) + " " t)) + (vc-bzr-program (car cmd)) + (command (cadr cmd)) + (args (cddr cmd))) + (vc-bzr-async-command command args))) + +(defun vc-bzr-status (file) + "Return FILE status according to Bzr. +Return value is a cons (STATUS . WARNING), where WARNING is a +string or nil, and STATUS is one of the symbols: `added', +`ignored', `kindchanged', `modified', `removed', `renamed', `unknown', +which directly correspond to `bzr status' output, or 'unchanged +for files whose copy in the working tree is identical to the one +in the branch repository, or nil for files that are not +registered with Bzr. + +If any error occurred in running `bzr status', then return nil." + (with-temp-buffer + (let ((ret (condition-case nil + (vc-bzr-command "status" t 0 file) + (file-error nil))) ; vc-bzr-program not found. + (status 'unchanged)) + ;; the only secure status indication in `bzr status' output + ;; is a couple of lines following the pattern:: + ;; | : + ;; | + ;; if the file is up-to-date, we get no status report from `bzr', + ;; so if the regexp search for the above pattern fails, we consider + ;; the file to be up-to-date. + (goto-char (point-min)) + (when (re-search-forward + ;; bzr prints paths relative to the repository root. + (concat "^\\(" vc-bzr-state-words "\\):[ \t\n]+" + (regexp-quote (vc-bzr-file-name-relative file)) + ;; Bzr appends a '/' to directory names and + ;; '*' to executable files + (if (file-directory-p file) "/?" "\\*?") + "[ \t\n]*$") + nil t) + (lexical-let ((statusword (match-string 1))) + ;; Erase the status text that matched. + (delete-region (match-beginning 0) (match-end 0)) + (setq status + (intern (replace-regexp-in-string " " "" statusword))))) + (when status + (goto-char (point-min)) + (skip-chars-forward " \n\t") ;Throw away spaces. + (cons status + ;; "bzr" will output warnings and informational messages to + ;; stderr; due to Emacs' `vc-do-command' (and, it seems, + ;; `start-process' itself) limitations, we cannot catch stderr + ;; and stdout into different buffers. So, if there's anything + ;; left in the buffer after removing the above status + ;; keywords, let us just presume that any other message from + ;; "bzr" is a user warning, and display it. + (unless (eobp) (buffer-substring (point) (point-max)))))))) + +(defun vc-bzr-state (file) + (lexical-let ((result (vc-bzr-status file))) + (when (consp result) + (when (cdr result) + (message "Warnings in `bzr' output: %s" (cdr result))) + (cdr (assq (car result) + '((added . added) + (kindchanged . edited) + (renamed . edited) + (modified . edited) + (removed . removed) + (ignored . ignored) + (unknown . unregistered) + (unchanged . up-to-date))))))) + +(defun vc-bzr-resolve-when-done () + "Call \"bzr resolve\" if the conflict markers have been removed." + (save-excursion + (goto-char (point-min)) + (unless (re-search-forward "^<<<<<<< " nil t) + (vc-bzr-command "resolve" nil 0 buffer-file-name) + ;; Remove the hook so that it is not called multiple times. + (remove-hook 'after-save-hook 'vc-bzr-resolve-when-done t)))) + +(defun vc-bzr-find-file-hook () + (when (and buffer-file-name + ;; FIXME: We should check that "bzr status" says "conflict". + (file-exists-p (concat buffer-file-name ".BASE")) + (file-exists-p (concat buffer-file-name ".OTHER")) + (file-exists-p (concat buffer-file-name ".THIS")) + ;; If "bzr status" says there's a conflict but there are no + ;; conflict markers, it's not clear what we should do. + (save-excursion + (goto-char (point-min)) + (re-search-forward "^<<<<<<< " nil t))) + ;; TODO: the merge algorithm used in `bzr merge' is nicely configurable, + ;; but the one in `bzr pull' isn't, so it would be good to provide an + ;; elisp function to remerge from the .BASE/OTHER/THIS files. + (smerge-start-session) + (add-hook 'after-save-hook 'vc-bzr-resolve-when-done nil t) + (message "There are unresolved conflicts in this file"))) + +(defun vc-bzr-workfile-unchanged-p (file) + (eq 'unchanged (car (vc-bzr-status file)))) + +(defun vc-bzr-working-revision (file) + ;; Together with the code in vc-state-heuristic, this makes it possible + ;; to get the initial VC state of a Bzr file even if Bzr is not installed. + (lexical-let* + ((rootdir (vc-bzr-root file)) + (branch-format-file (expand-file-name vc-bzr-admin-branch-format-file + rootdir)) + (revhistory-file (expand-file-name vc-bzr-admin-revhistory rootdir)) + (lastrev-file (expand-file-name vc-bzr-admin-lastrev rootdir))) + ;; This looks at internal files to avoid forking a bzr process. + ;; May break if they change their format. + (if (and (file-exists-p branch-format-file) + ;; For lightweight checkouts (obtained with bzr checkout --lightweight) + ;; the branch-format-file does not contain the revision + ;; information, we need to look up the branch-format-file + ;; in the place where the lightweight checkout comes + ;; from. We only do that if it's a local file. + (let ((location-fname (expand-file-name + (concat vc-bzr-admin-dirname + "/branch/location") rootdir))) + ;; The existence of this file is how we distinguish + ;; lightweight checkouts. + (if (file-exists-p location-fname) + (with-temp-buffer + (insert-file-contents location-fname) + ;; If the lightweight checkout points to a + ;; location in the local file system, then we can + ;; look there for the version information. + (when (re-search-forward "file://\\(.+\\)" nil t) + (let ((l-c-parent-dir (match-string 1))) + (when (and (memq system-type '(ms-dos windows-nt)) + (string-match-p "^/[[:alpha:]]:" l-c-parent-dir)) + ;;; The non-Windows code takes a shortcut by using the host/path + ;;; separator slash as the start of the absolute path. That + ;;; does not work on Windows, so we must remove it (bug#5345) + (setq l-c-parent-dir (substring l-c-parent-dir 1))) + (setq branch-format-file + (expand-file-name vc-bzr-admin-branch-format-file + l-c-parent-dir)) + (setq lastrev-file + (expand-file-name vc-bzr-admin-lastrev l-c-parent-dir)) + ;; FIXME: maybe it's overkill to check if both these files exist. + (and (file-exists-p branch-format-file) + (file-exists-p lastrev-file))))) + t))) + (with-temp-buffer + (insert-file-contents branch-format-file) + (goto-char (point-min)) + (cond + ((or + (looking-at "Bazaar-NG branch, format 0.0.4") + (looking-at "Bazaar-NG branch format 5")) + ;; count lines in .bzr/branch/revision-history + (insert-file-contents revhistory-file) + (number-to-string (count-lines (line-end-position) (point-max)))) + ((or + (looking-at "Bazaar Branch Format 6 (bzr 0.15)") + (looking-at "Bazaar Branch Format 7 (needs bzr 1.6)")) + ;; revno is the first number in .bzr/branch/last-revision + (insert-file-contents lastrev-file) + (when (re-search-forward "[0-9]+" nil t) + (buffer-substring (match-beginning 0) (match-end 0)))))) + ;; fallback to calling "bzr revno" + (lexical-let* + ((result (vc-bzr-command-discarding-stderr + vc-bzr-program "revno" (file-relative-name file))) + (exitcode (car result)) + (output (cdr result))) + (cond + ((eq exitcode 0) (substring output 0 -1)) + (t nil)))))) + +(defun vc-bzr-create-repo () + "Create a new Bzr repository." + (vc-bzr-command "init" nil 0 nil)) + +(defun vc-bzr-init-revision (&optional file) + "Always return nil, as Bzr cannot register explicit versions." + nil) + +(defun vc-bzr-previous-revision (file rev) + (if (string-match "\\`[0-9]+\\'" rev) + (number-to-string (1- (string-to-number rev))) + (concat "before:" rev))) + +(defun vc-bzr-next-revision (file rev) + (if (string-match "\\`[0-9]+\\'" rev) + (number-to-string (1+ (string-to-number rev))) + (error "Don't know how to compute the next revision of %s" rev))) + +(defun vc-bzr-register (files &optional rev comment) + "Register FILES under bzr. +Signal an error unless REV is nil. +COMMENT is ignored." + (if rev (error "Can't register explicit revision with bzr")) + (vc-bzr-command "add" nil 0 files)) + +;; Could run `bzr status' in the directory and see if it succeeds, but +;; that's relatively expensive. +(defalias 'vc-bzr-responsible-p 'vc-bzr-root + "Return non-nil if FILE is (potentially) controlled by bzr. +The criterion is that there is a `.bzr' directory in the same +or a superior directory.") + +(defun vc-bzr-could-register (file) + "Return non-nil if FILE could be registered under bzr." + (and (vc-bzr-responsible-p file) ; shortcut + (condition-case () + (with-temp-buffer + (vc-bzr-command "add" t 0 file "--dry-run") + ;; The command succeeds with no output if file is + ;; registered (in bzr 0.8). + (goto-char (point-min)) + (looking-at "added ")) + (error)))) + +(defun vc-bzr-unregister (file) + "Unregister FILE from bzr." + (vc-bzr-command "remove" nil 0 file "--keep")) + +(declare-function log-edit-extract-headers "log-edit" (headers string)) + +(defun vc-bzr-checkin (files rev comment) + "Check FILES in to bzr with log message COMMENT. +REV non-nil gets an error." + (if rev (error "Can't check in a specific revision with bzr")) + (apply 'vc-bzr-command "commit" nil 0 + files (cons "-m" (log-edit-extract-headers '(("Author" . "--author") + ("Date" . "--commit-time") + ("Fixes" . "--fixes")) + comment)))) + +(defun vc-bzr-find-revision (file rev buffer) + "Fetch revision REV of file FILE and put it into BUFFER." + (with-current-buffer buffer + (if (and rev (stringp rev) (not (string= rev ""))) + (vc-bzr-command "cat" t 0 file "-r" rev) + (vc-bzr-command "cat" t 0 file)))) + +(defun vc-bzr-checkout (file &optional editable rev) + (if rev (error "Operation not supported") + ;; Else, there's nothing to do. + nil)) + +(defun vc-bzr-revert (file &optional contents-done) + (unless contents-done + (with-temp-buffer (vc-bzr-command "revert" t 0 file)))) + +(defvar log-view-message-re) +(defvar log-view-file-re) +(defvar log-view-font-lock-keywords) +(defvar log-view-current-tag-function) +(defvar log-view-per-file-logs) + +(define-derived-mode vc-bzr-log-view-mode log-view-mode "Bzr-Log-View" + (remove-hook 'log-view-mode-hook 'vc-bzr-log-view-mode) ;Deactivate the hack. + (require 'add-log) + (set (make-local-variable 'log-view-per-file-logs) nil) + (set (make-local-variable 'log-view-file-re) "\\`a\\`") + (set (make-local-variable 'log-view-message-re) + (if (eq vc-log-view-type 'short) + "^ *\\([0-9.]+\\): \\(.*?\\)[ \t]+\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)\\( \\[merge\\]\\)?" + "^ *\\(?:revno: \\([0-9.]+\\)\\|merged: .+\\)")) + (set (make-local-variable 'log-view-font-lock-keywords) + ;; log-view-font-lock-keywords is careful to use the buffer-local + ;; value of log-view-message-re only since Emacs-23. + (if (eq vc-log-view-type 'short) + (append `((,log-view-message-re + (1 'log-view-message-face) + (2 'change-log-name) + (3 'change-log-date) + (4 'change-log-list nil lax)))) + (append `((,log-view-message-re . 'log-view-message-face)) + ;; log-view-font-lock-keywords + '(("^ *\\(?:committer\\|author\\): \ +\\([^<(]+?\\)[ ]*[(<]\\([[:alnum:]_.+-]+@[[:alnum:]_.-]+\\)[>)]" + (1 'change-log-name) + (2 'change-log-email)) + ("^ *timestamp: \\(.*\\)" (1 'change-log-date-face))))))) + +(defun vc-bzr-print-log (files buffer &optional shortlog start-revision limit) + "Get bzr change log for FILES into specified BUFFER." + ;; `vc-do-command' creates the buffer, but we need it before running + ;; the command. + (vc-setup-buffer buffer) + ;; If the buffer exists from a previous invocation it might be + ;; read-only. + ;; FIXME: `vc-bzr-command' runs `bzr log' with `LC_MESSAGES=C', so + ;; the log display may not what the user wants - but I see no other + ;; way of getting the above regexps working. + (with-current-buffer buffer + (apply 'vc-bzr-command "log" buffer 'async files + (append + (when shortlog '("--line")) + (when start-revision (list (format "-r..%s" start-revision))) + (when limit (list "-l" (format "%s" limit))) + (if (stringp vc-bzr-log-switches) + (list vc-bzr-log-switches) + vc-bzr-log-switches))))) + +(defun vc-bzr-log-incoming (buffer remote-location) + (apply 'vc-bzr-command "missing" buffer 'async nil + (list "--theirs-only" (unless (string= remote-location "") remote-location)))) + +(defun vc-bzr-log-outgoing (buffer remote-location) + (apply 'vc-bzr-command "missing" buffer 'async nil + (list "--mine-only" (unless (string= remote-location "") remote-location)))) + +(defun vc-bzr-show-log-entry (revision) + "Find entry for patch name REVISION in bzr change log buffer." + (goto-char (point-min)) + (when revision + (let (case-fold-search + found) + (if (re-search-forward + ;; "revno:" can appear either at the beginning of a line, + ;; or indented. + (concat "^[ ]*-+\n[ ]*revno: " + ;; The revision can contain ".", quote it so that it + ;; does not interfere with regexp matching. + (regexp-quote revision) "$") nil t) + (progn + (beginning-of-line 0) + (setq found t)) + (goto-char (point-min))) + found))) + +(defun vc-bzr-diff (files &optional rev1 rev2 buffer) + "VC bzr backend for diff." + ;; `bzr diff' exits with code 1 if diff is non-empty. + (apply #'vc-bzr-command "diff" (or buffer "*vc-diff*") + (if vc-disable-async-diff 1 'async) files + "--diff-options" (mapconcat 'identity + (vc-switches 'bzr 'diff) + " ") + ;; This `when' is just an optimization because bzr-1.2 is *much* + ;; faster when the revision argument is not given. + (when (or rev1 rev2) + (list "-r" (format "%s..%s" + (or rev1 "revno:-1") + (or rev2 "")))))) + + +;; FIXME: vc-{next,previous}-revision need fixing in vc.el to deal with +;; straight integer revisions. + +(defun vc-bzr-delete-file (file) + "Delete FILE and delete it in the bzr repository." + (condition-case () + (delete-file file) + (file-error nil)) + (vc-bzr-command "remove" nil 0 file)) + +(defun vc-bzr-rename-file (old new) + "Rename file from OLD to NEW using `bzr mv'." + (vc-bzr-command "mv" nil 0 new old)) + +(defvar vc-bzr-annotation-table nil + "Internal use.") +(make-variable-buffer-local 'vc-bzr-annotation-table) + +(defun vc-bzr-annotate-command (file buffer &optional revision) + "Prepare BUFFER for `vc-annotate' on FILE. +Each line is tagged with the revision number, which has a `help-echo' +property containing author and date information." + (apply #'vc-bzr-command "annotate" buffer 'async file "--long" "--all" + (if revision (list "-r" revision))) + (lexical-let ((table (make-hash-table :test 'equal))) + (set-process-filter + (get-buffer-process buffer) + (lambda (proc string) + (when (process-buffer proc) + (with-current-buffer (process-buffer proc) + (setq string (concat (process-get proc :vc-left-over) string)) + ;; Eg: 102020 Gnus developers 20101020 | regexp." + ;; As of bzr 2.2.2, no email address in whoami (which can + ;; lead to spaces in the author field) is allowed but discouraged. + ;; See bug#7792. + (while (string-match "^\\( *[0-9.]+ *\\) \\(.+?\\) +\\([0-9]\\{8\\}\\)\\( |.*\n\\)" string) + (let* ((rev (match-string 1 string)) + (author (match-string 2 string)) + (date (match-string 3 string)) + (key (substring string (match-beginning 0) + (match-beginning 4))) + (line (match-string 4 string)) + (tag (gethash key table)) + (inhibit-read-only t)) + (setq string (substring string (match-end 0))) + (unless tag + (setq tag + (propertize + (format "%s %-7.7s" rev author) + 'help-echo (format "Revision: %d, author: %s, date: %s" + (string-to-number rev) + author date) + 'mouse-face 'highlight)) + (puthash key tag table)) + (goto-char (process-mark proc)) + (insert tag line) + (move-marker (process-mark proc) (point)))) + (process-put proc :vc-left-over string))))))) + +(declare-function vc-annotate-convert-time "vc-annotate" (time)) + +(defun vc-bzr-annotate-time () - (when (re-search-forward "^ *[0-9.]+ +.* +|" nil t) ++ (when (re-search-forward "^ *[0-9.]+ +.+? +|" nil t) + (let ((prop (get-text-property (line-beginning-position) 'help-echo))) + (string-match "[0-9]+\\'" prop) + (let ((str (match-string-no-properties 0 prop))) + (vc-annotate-convert-time + (encode-time 0 0 0 + (string-to-number (substring str 6 8)) + (string-to-number (substring str 4 6)) + (string-to-number (substring str 0 4)))))))) + +(defun vc-bzr-annotate-extract-revision-at-line () + "Return revision for current line of annotation buffer, or nil. +Return nil if current line isn't annotated." + (save-excursion + (beginning-of-line) + (if (looking-at "^ *\\([0-9.]+\\) +.* +|") + (match-string-no-properties 1)))) + +(defun vc-bzr-command-discarding-stderr (command &rest args) + "Execute shell command COMMAND (with ARGS); return its output and exitcode. +Return value is a cons (EXITCODE . OUTPUT), where EXITCODE is +the (numerical) exit code of the process, and OUTPUT is a string +containing whatever the process sent to its standard output +stream. Standard error output is discarded." + (with-temp-buffer + (cons + (apply #'process-file command nil (list (current-buffer) nil) nil args) + (buffer-substring (point-min) (point-max))))) + +(defstruct (vc-bzr-extra-fileinfo + (:copier nil) + (:constructor vc-bzr-create-extra-fileinfo (extra-name)) + (:conc-name vc-bzr-extra-fileinfo->)) + extra-name) ;; original name for rename targets, new name for + +(defun vc-bzr-dir-printer (info) + "Pretty-printer for the vc-dir-fileinfo structure." + (let ((extra (vc-dir-fileinfo->extra info))) + (vc-default-dir-printer 'Bzr info) + (when extra + (insert (propertize + (format " (renamed from %s)" + (vc-bzr-extra-fileinfo->extra-name extra)) + 'face 'font-lock-comment-face))))) + +;; FIXME: this needs testing, it's probably incomplete. +(defun vc-bzr-after-dir-status (update-function relative-dir) + (let ((status-str nil) + (translation '(("+N " . added) + ("-D " . removed) + (" M " . edited) ;; file text modified + (" *" . edited) ;; execute bit changed + (" M*" . edited) ;; text modified + execute bit changed + ;; FIXME: what about ignored files? + (" D " . missing) + ;; For conflicts, should we list the .THIS/.BASE/.OTHER? + ("C " . conflict) + ("? " . unregistered) + ;; No such state, but we need to distinguish this case. + ("R " . renamed) + ("RM " . renamed) + ;; For a non existent file FOO, the output is: + ;; bzr: ERROR: Path(s) do not exist: FOO + ("bzr" . not-found) + ;; If the tree is not up to date, bzr will print this warning: + ;; working tree is out of date, run 'bzr update' + ;; ignore it. + ;; FIXME: maybe this warning can be put in the vc-dir header... + ("wor" . not-found) + ;; Ignore "P " and "P." for pending patches. + ("P " . not-found) + ("P. " . not-found) + )) + (translated nil) + (result nil)) + (goto-char (point-min)) + (while (not (eobp)) + (setq status-str + (buffer-substring-no-properties (point) (+ (point) 3))) + (setq translated (cdr (assoc status-str translation))) + (cond + ((eq translated 'conflict) + ;; For conflicts the file appears twice in the listing: once + ;; with the M flag and once with the C flag, so take care + ;; not to add it twice to `result'. Ugly. + (let* ((file + (buffer-substring-no-properties + ;;For files with conflicts the format is: + ;;C Text conflict in FILENAME + ;; Bah. + (+ (point) 21) (line-end-position))) + (entry (assoc file result))) + (when entry + (setf (nth 1 entry) 'conflict)))) + ((eq translated 'renamed) + (re-search-forward "R[ M] \\(.*\\) => \\(.*\\)$" (line-end-position) t) + (let ((new-name (file-relative-name (match-string 2) relative-dir)) + (old-name (file-relative-name (match-string 1) relative-dir))) + (push (list new-name 'edited + (vc-bzr-create-extra-fileinfo old-name)) result))) + ;; do nothing for non existent files + ((eq translated 'not-found)) + (t + (push (list (file-relative-name + (buffer-substring-no-properties + (+ (point) 4) + (line-end-position)) relative-dir) + translated) result))) + (forward-line)) + (funcall update-function result))) + +(defun vc-bzr-dir-status (dir update-function) + "Return a list of conses (file . state) for DIR." + (vc-bzr-command "status" (current-buffer) 'async dir "-v" "-S") + (vc-exec-after + `(vc-bzr-after-dir-status (quote ,update-function) + ;; "bzr status" results are relative to + ;; the bzr root directory, NOT to the + ;; directory "bzr status" was invoked in. + ;; Ugh. + ;; We pass the relative directory here so + ;; that `vc-bzr-after-dir-status' can + ;; frob the results accordingly. + (file-relative-name ,dir (vc-bzr-root ,dir))))) + +(defun vc-bzr-dir-status-files (dir files default-state update-function) + "Return a list of conses (file . state) for DIR." + (apply 'vc-bzr-command "status" (current-buffer) 'async dir "-v" "-S" files) + (vc-exec-after + `(vc-bzr-after-dir-status (quote ,update-function) + (file-relative-name ,dir (vc-bzr-root ,dir))))) + +(defvar vc-bzr-shelve-map + (let ((map (make-sparse-keymap))) + ;; Turn off vc-dir marking + (define-key map [mouse-2] 'ignore) + + (define-key map [down-mouse-3] 'vc-bzr-shelve-menu) + (define-key map "\C-k" 'vc-bzr-shelve-delete-at-point) + (define-key map "=" 'vc-bzr-shelve-show-at-point) + (define-key map "\C-m" 'vc-bzr-shelve-show-at-point) + (define-key map "A" 'vc-bzr-shelve-apply-and-keep-at-point) + (define-key map "P" 'vc-bzr-shelve-apply-at-point) + (define-key map "S" 'vc-bzr-shelve-snapshot) + map)) + +(defvar vc-bzr-shelve-menu-map + (let ((map (make-sparse-keymap "Bzr Shelve"))) + (define-key map [de] + '(menu-item "Delete shelf" vc-bzr-shelve-delete-at-point + :help "Delete the current shelf")) + (define-key map [ap] + '(menu-item "Apply and keep shelf" vc-bzr-shelve-apply-and-keep-at-point + :help "Apply the current shelf and keep it")) + (define-key map [po] + '(menu-item "Apply and remove shelf (pop)" vc-bzr-shelve-apply-at-point + :help "Apply the current shelf and remove it")) + (define-key map [sh] + '(menu-item "Show shelve" vc-bzr-shelve-show-at-point + :help "Show the contents of the current shelve")) + map)) + +(defvar vc-bzr-extra-menu-map + (let ((map (make-sparse-keymap))) + (define-key map [bzr-sn] + '(menu-item "Shelve a snapshot" vc-bzr-shelve-snapshot + :help "Shelve the current state of the tree and keep the current state")) + (define-key map [bzr-sh] + '(menu-item "Shelve..." vc-bzr-shelve + :help "Shelve changes")) + map)) + +(defun vc-bzr-extra-menu () vc-bzr-extra-menu-map) + +(defun vc-bzr-extra-status-menu () vc-bzr-extra-menu-map) + +(defun vc-bzr-dir-extra-headers (dir) + (let* + ((str (with-temp-buffer + (vc-bzr-command "info" t 0 dir) + (buffer-string))) + (shelve (vc-bzr-shelve-list)) + (shelve-help-echo "Use M-x vc-bzr-shelve to create shelves") + (root-dir (vc-bzr-root dir)) + (pending-merge + ;; FIXME: looking for .bzr/checkout/merge-hashes is not a + ;; reliable method to detect pending merges, disable this + ;; until a proper solution is implemented. + (and nil + (file-exists-p + (expand-file-name ".bzr/checkout/merge-hashes" root-dir)))) + (pending-merge-help-echo + (format "A merge has been performed.\nA commit from the top-level directory (%s)\nis required before being able to check in anything else" root-dir)) + (light-checkout + (when (string-match ".+light checkout root: \\(.+\\)$" str) + (match-string 1 str))) + (light-checkout-branch + (when light-checkout + (when (string-match ".+checkout of branch: \\(.+\\)$" str) + (match-string 1 str))))) + (concat + (propertize "Parent branch : " 'face 'font-lock-type-face) + (propertize + (if (string-match "parent branch: \\(.+\\)$" str) + (match-string 1 str) + "None") + 'face 'font-lock-variable-name-face) + "\n" + (when light-checkout + (concat + (propertize "Light checkout root: " 'face 'font-lock-type-face) + (propertize light-checkout 'face 'font-lock-variable-name-face) + "\n")) + (when light-checkout-branch + (concat + (propertize "Checkout of branch : " 'face 'font-lock-type-face) + (propertize light-checkout-branch 'face 'font-lock-variable-name-face) + "\n")) + (when pending-merge + (concat + (propertize "Warning : " 'face 'font-lock-warning-face + 'help-echo pending-merge-help-echo) + (propertize "Pending merges, commit recommended before any other action" + 'help-echo pending-merge-help-echo + 'face 'font-lock-warning-face) + "\n")) + (if shelve + (concat + (propertize "Shelves :\n" 'face 'font-lock-type-face + 'help-echo shelve-help-echo) + (mapconcat + (lambda (x) + (propertize x + 'face 'font-lock-variable-name-face + 'mouse-face 'highlight + 'help-echo "mouse-3: Show shelve menu\nA: Apply and keep shelf\nP: Apply and remove shelf (pop)\nS: Snapshot to a shelf\nC-k: Delete shelf" + 'keymap vc-bzr-shelve-map)) + shelve "\n")) + (concat + (propertize "Shelves : " 'face 'font-lock-type-face + 'help-echo shelve-help-echo) + (propertize "No shelved changes" + 'help-echo shelve-help-echo + 'face 'font-lock-variable-name-face)))))) + +(defun vc-bzr-shelve (name) + "Create a shelve." + (interactive "sShelf name: ") + (let ((root (vc-bzr-root default-directory))) + (when root + (vc-bzr-command "shelve" nil 0 nil "--all" "-m" name) + (vc-resynch-buffer root t t)))) + +(defun vc-bzr-shelve-show (name) + "Show the contents of shelve NAME." + (interactive "sShelve name: ") + (vc-setup-buffer "*vc-diff*") + ;; FIXME: how can you show the contents of a shelf? + (vc-bzr-command "unshelve" "*vc-diff*" 'async nil "--preview" name) + (set-buffer "*vc-diff*") + (diff-mode) + (setq buffer-read-only t) + (pop-to-buffer (current-buffer))) + +(defun vc-bzr-shelve-apply (name) + "Apply shelve NAME and remove it afterwards." + (interactive "sApply (and remove) shelf: ") + (vc-bzr-command "unshelve" nil 0 nil "--apply" name) + (vc-resynch-buffer (vc-bzr-root default-directory) t t)) + +(defun vc-bzr-shelve-apply-and-keep (name) + "Apply shelve NAME and keep it afterwards." + (interactive "sApply (and keep) shelf: ") + (vc-bzr-command "unshelve" nil 0 nil "--apply" "--keep" name) + (vc-resynch-buffer (vc-bzr-root default-directory) t t)) + +(defun vc-bzr-shelve-snapshot () + "Create a stash with the current tree state." + (interactive) + (vc-bzr-command "shelve" nil 0 nil "--all" "-m" + (let ((ct (current-time))) + (concat + (format-time-string "Snapshot on %Y-%m-%d" ct) + (format-time-string " at %H:%M" ct)))) + (vc-bzr-command "unshelve" nil 0 nil "--apply" "--keep") + (vc-resynch-buffer (vc-bzr-root default-directory) t t)) + +(defun vc-bzr-shelve-list () + (with-temp-buffer + (vc-bzr-command "shelve" (current-buffer) 1 nil "--list" "-q") + (delete + "" + (split-string + (buffer-substring (point-min) (point-max)) + "\n")))) + +(defun vc-bzr-shelve-get-at-point (point) + (save-excursion + (goto-char point) + (beginning-of-line) + (if (looking-at "^ +\\([0-9]+\\):") + (match-string 1) + (error "Cannot find shelf at point")))) + +(defun vc-bzr-shelve-delete-at-point () + (interactive) + (let ((shelve (vc-bzr-shelve-get-at-point (point)))) + (when (y-or-n-p (format "Remove shelf %s ? " shelve)) + (vc-bzr-command "unshelve" nil 0 nil "--delete-only" shelve) + (vc-dir-refresh)))) + +(defun vc-bzr-shelve-show-at-point () + (interactive) + (vc-bzr-shelve-show (vc-bzr-shelve-get-at-point (point)))) + +(defun vc-bzr-shelve-apply-at-point () + (interactive) + (vc-bzr-shelve-apply (vc-bzr-shelve-get-at-point (point)))) + +(defun vc-bzr-shelve-apply-and-keep-at-point () + (interactive) + (vc-bzr-shelve-apply-and-keep (vc-bzr-shelve-get-at-point (point)))) + +(defun vc-bzr-shelve-menu (e) + (interactive "e") + (vc-dir-at-event e (popup-menu vc-bzr-shelve-menu-map e))) + +(defun vc-bzr-revision-table (files) + (let ((vc-bzr-revisions '()) + (default-directory (file-name-directory (car files)))) + (with-temp-buffer + (vc-bzr-command "log" t 0 files "--line") + (let ((start (point-min)) + (loglines (buffer-substring-no-properties (point-min) (point-max)))) + (while (string-match "^\\([0-9]+\\):" loglines) + (push (match-string 1 loglines) vc-bzr-revisions) + (setq start (+ start (match-end 0))) + (setq loglines (buffer-substring-no-properties start (point-max)))))) + vc-bzr-revisions)) + +(defun vc-bzr-conflicted-files (dir) + (let ((default-directory (vc-bzr-root dir)) + (files ())) + (with-temp-buffer + (vc-bzr-command "status" t 0 default-directory) + (goto-char (point-min)) + (when (re-search-forward "^conflicts:\n" nil t) + (while (looking-at " \\(?:Text conflict in \\(.*\\)\\|.*\\)\n") + (if (match-end 1) + (push (expand-file-name (match-string 1)) files)) + (goto-char (match-end 0))))) + files)) + +;;; Revision completion + +(eval-and-compile + (defconst vc-bzr-revision-keywords + '("revno" "revid" "last" "before" + "tag" "date" "ancestor" "branch" "submit"))) + +(defun vc-bzr-revision-completion-table (files) + (lexical-let ((files files)) + ;; What about using `files'?!? --Stef + (lambda (string pred action) + (cond + ((string-match "\\`\\(ancestor\\|branch\\|\\(revno:\\)?[-0-9]+:\\):" + string) + (completion-table-with-context (substring string 0 (match-end 0)) + (apply-partially + 'completion-table-with-predicate + 'completion-file-name-table + 'file-directory-p t) + (substring string (match-end 0)) + pred + action)) + ((string-match "\\`\\(before\\):" string) + (completion-table-with-context (substring string 0 (match-end 0)) + (vc-bzr-revision-completion-table files) + (substring string (match-end 0)) + pred + action)) + ((string-match "\\`\\(tag\\):" string) + (let ((prefix (substring string 0 (match-end 0))) + (tag (substring string (match-end 0))) + (table nil) + process-file-side-effects) + (with-temp-buffer + ;; "bzr-1.2 tags" is much faster with --show-ids. + (process-file vc-bzr-program nil '(t) nil "tags" "--show-ids") + ;; The output is ambiguous, unless we assume that revids do not + ;; contain spaces. + (goto-char (point-min)) + (while (re-search-forward "^\\(.*[^ \n]\\) +[^ \n]*$" nil t) + (push (match-string-no-properties 1) table))) + (completion-table-with-context prefix table tag pred action))) + + ((string-match "\\`\\([a-z]+\\):" string) + ;; no actual completion for the remaining keywords. + (completion-table-with-context (substring string 0 (match-end 0)) + (if (member (match-string 1 string) + vc-bzr-revision-keywords) + ;; If it's a valid keyword, + ;; use a non-empty table to + ;; indicate it. + '("") nil) + (substring string (match-end 0)) + pred + action)) + (t + ;; Could use completion-table-with-terminator, except that it + ;; currently doesn't work right w.r.t pcm and doesn't give + ;; the *Completions* output we want. + (complete-with-action action (eval-when-compile + (mapcar (lambda (s) (concat s ":")) + vc-bzr-revision-keywords)) + string pred)))))) + +(provide 'vc-bzr) + +;;; vc-bzr.el ends here diff --cc lisp/whitespace.el index 6a8b3b671e8,353d7fef189..16f9bb451f5 --- a/lisp/whitespace.el +++ b/lisp/whitespace.el @@@ -1188,7 -1188,7 +1188,8 @@@ See also `whitespace-newline' and `whit :global t :group 'whitespace (let ((whitespace-style '(newline-mark newline))) -- (global-whitespace-mode global-whitespace-newline-mode) ++ (global-whitespace-mode (if global-whitespace-newline-mode ++ 1 -1)) ;; sync states (running a batch job) (setq global-whitespace-newline-mode global-whitespace-mode)))