diff options
Diffstat (limited to 'lisp/net')
41 files changed, 14121 insertions, 7193 deletions
diff --git a/lisp/net/ange-ftp.el b/lisp/net/ange-ftp.el index 7428dcd380a..0680581c7cd 100644 --- a/lisp/net/ange-ftp.el +++ b/lisp/net/ange-ftp.el @@ -3813,7 +3813,7 @@ Value is (0 0) if the modification time cannot be determined." (ange-ftp-call-cont cont result line))) (defun ange-ftp-copy-file (filename newname &optional ok-if-already-exists - keep-date) + keep-date preserve-uid-gid) (interactive "fCopy file: \nFCopy %s to file: \np") (ange-ftp-copy-file-internal filename newname @@ -4132,8 +4132,19 @@ directory, so that Emacs will know its current contents." (format "Getting %s" fn1)) tmp1)))) -(defun ange-ftp-file-remote-p (file) - (ange-ftp-replace-name-component file "")) +(defun ange-ftp-file-remote-p (file &optional identification connected) + (let* ((parsed (ange-ftp-ftp-name file)) + (host (nth 0 parsed)) + (user (nth 1 parsed))) + (and (or (not connected) + (let ((proc (get-process (ange-ftp-ftp-process-buffer host user)))) + (and proc (processp proc) + (memq (process-status proc) '(run open))))) + (cond + ((eq identification 'method) (and parsed "ftp")) + ((eq identification 'user) user) + ((eq identification 'host) host) + (t (ange-ftp-replace-name-component file "")))))) (defun ange-ftp-load (file &optional noerror nomessage nosuffix) (if (ange-ftp-ftp-name file) @@ -4361,11 +4372,20 @@ NEWNAME should be the name to give the new compressed or uncompressed file.") ;; Treat each name as its own truename. (put 'file-truename 'ange-ftp 'identity) +;; We must return non-nil in order to mask our inability to do the job. +;; Otherwise there are errors when applied to the target file during +;; copying from a (localhost) Tramp file. +(put 'set-file-modes 'ange-ftp 'ignore) +(put 'set-file-times 'ange-ftp 'ignore) + ;; Turn off RCS/SCCS processing to save time. ;; This returns nil for any file name as argument. (put 'vc-registered 'ange-ftp 'null) -(put 'dired-call-process 'ange-ftp 'ange-ftp-dired-call-process) +;; We can handle process-file in a restricted way (just for chown). +;; Nothing possible for `start-file-process'. +(put 'process-file 'ange-ftp 'ange-ftp-process-file) +(put 'start-file-process 'ange-ftp 'ignore) (put 'shell-command 'ange-ftp 'ange-ftp-shell-command) ;;; Define ways of getting at unmodified Emacs primitives, @@ -4528,8 +4548,8 @@ NEWNAME should be the name to give the new compressed or uncompressed file.") ;; default-directory is in ange-ftp syntax for remote file names. (ange-ftp-real-shell-command command output-buffer error-buffer)))) -;;; This is the handler for call-process. -(defun ange-ftp-dired-call-process (program discard &rest arguments) +;;; This is the handler for process-file. +(defun ange-ftp-process-file (program infile buffer display &rest arguments) ;; PROGRAM is always one of those below in the cond in dired.el. ;; The ARGUMENTS are (nearly) always files. (if (ange-ftp-ftp-name default-directory) @@ -4549,7 +4569,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.") 1) (error (insert (format "%s\n" (nth 1 oops))) 1)) - (apply 'call-process program nil (not discard) nil arguments))) + (apply 'call-process program infile buffer display arguments))) ;; Handle an attempt to run chmod on a remote file ;; by using the ftp chmod command. @@ -4560,7 +4580,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.") (rest (cdr args))) (if (equal "--" (car rest)) (setq rest (cdr rest))) - (mapcar + (mapc (lambda (file) (setq file (expand-file-name file)) (let ((parsed (ange-ftp-ftp-name file))) @@ -6035,8 +6055,8 @@ Other orders of $ and _ seem to all work just fine.") (puthash ".." t tbl) ;; add all additional pubsets, if not listing one of them (if (not (member pubset ange-ftp-bs2000-additional-pubsets)) - (mapcar (lambda (pubset) (puthash pubset t tbl)) - ange-ftp-bs2000-additional-pubsets)) + (mapc (lambda (pubset) (puthash pubset t tbl)) + ange-ftp-bs2000-additional-pubsets)) tbl)) (add-to-list 'ange-ftp-parse-list-func-alist diff --git a/lisp/net/browse-url.el b/lisp/net/browse-url.el index 8ea4409731e..f5ff75774e2 100644 --- a/lisp/net/browse-url.el +++ b/lisp/net/browse-url.el @@ -46,12 +46,13 @@ ;; browse-url-cci XMosaic 2.5 ;; browse-url-w3 w3 0 ;; browse-url-w3-gnudoit w3 remotely -;; browse-url-lynx-* Lynx 0 +;; browse-url-text-* Any text browser 0 ;; browse-url-generic arbitrary ;; browse-url-default-windows-browser MS-Windows browser ;; browse-url-default-macosx-browser Mac OS X browser ;; browse-url-gnome-moz GNOME interface to Mozilla ;; browse-url-kde KDE konqueror (kfm) +;; browse-url-elinks Elinks Don't know (tried with 0.12.GIT) ;; [A version of the Netscape browser is now free software ;; <URL:http://www.mozilla.org/>, albeit not GPLed, so it is @@ -68,7 +69,7 @@ ;; control but which window DO you want to control and how do you ;; discover its id? -;; William M. Perry's excellent "w3" WWW browser for +;; William M. Perry's excellent "w3" WWW browser for ;; Emacs <URL:ftp://cs.indiana.edu/pub/elisp/w3/> ;; has a function w3-follow-url-at-point, but that ;; doesn't let you edit the URL like browse-url. @@ -205,7 +206,8 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Variables -(eval-when-compile (require 'thingatpt) +(eval-when-compile (require 'cl) + (require 'thingatpt) (require 'term) (require 'dired) (require 'executable) @@ -244,11 +246,12 @@ regexp should probably be \".\" to specify a default browser." (function-item :tag "Netscape" :value browse-url-netscape) (function-item :tag "Mosaic" :value browse-url-mosaic) (function-item :tag "Mosaic using CCI" :value browse-url-cci) - (function-item :tag "Lynx in an xterm window" - :value browse-url-lynx-xterm) - (function-item :tag "Lynx in an Emacs window" - :value browse-url-lynx-emacs) + (function-item :tag "Text browser in an xterm window" + :value browse-url-text-xterm) + (function-item :tag "Text browser in an Emacs window" + :value browse-url-text-emacs) (function-item :tag "KDE" :value browse-url-kde) + (function-item :tag "Elinks" :value browse-url-elinks) (function-item :tag "Specified by `Browse Url Generic Program'" :value browse-url-generic) (function-item :tag "Default Windows browser" @@ -413,7 +416,7 @@ window." :group 'browse-url) (defcustom browse-url-new-window-flag nil - "If non-nil, always open a new browser window with appropriate browsers. + "Non-nil means always open a new browser window with appropriate browsers. Passing an interactive argument to \\[browse-url], or specific browser commands reverses the effect of this variable. Requires Netscape version 1.1N or later or XMosaic version 2.5 or later if using those browsers." @@ -499,9 +502,9 @@ enabled. The port number should be set in `browse-url-CCI-port'." (defvar browse-url-temp-file-name nil) (make-variable-buffer-local 'browse-url-temp-file-name) - + (defcustom browse-url-xterm-program "xterm" - "The name of the terminal emulator used by `browse-url-lynx-xterm'. + "The name of the terminal emulator used by `browse-url-text-xterm'. This might, for instance, be a separate color version of xterm." :type 'string :group 'browse-url) @@ -512,17 +515,6 @@ These might set its size, for instance." :type '(repeat (string :tag "Argument")) :group 'browse-url) -(defcustom browse-url-lynx-emacs-args (and (not window-system) - '("-show_cursor")) - "A list of strings defining options for Lynx in an Emacs buffer. - -The default is none in a window system, otherwise `-show_cursor' to -indicate the position of the current link in the absence of -highlighting, assuming the normal default for showing the cursor." - :type '(repeat (string :tag "Argument")) - :version "20.3" - :group 'browse-url) - (defcustom browse-url-gnudoit-program "gnudoit" "The name of the `gnudoit' program used by `browse-url-w3-gnudoit'." :type 'string @@ -559,28 +551,47 @@ incompatibly at version 4." :type 'number :group 'browse-url) -(defcustom browse-url-lynx-input-field 'avoid - "Action on selecting an existing Lynx buffer at an input field. -What to do when sending a new URL to an existing Lynx buffer in Emacs -if the Lynx cursor is on an input field (in which case the `g' command +(defcustom browse-url-text-browser "lynx" + "The name of the text browser to invoke." + :type 'string + :group 'browse-url + :version "23.1") + +(defcustom browse-url-text-emacs-args (and (not window-system) + '("-show_cursor")) + "A list of strings defining options for a text browser in an Emacs buffer. + +The default is none in a window system, otherwise `-show_cursor' to +indicate the position of the current link in the absence of +highlighting, assuming the normal default for showing the cursor." + :type '(repeat (string :tag "Argument")) + :version "23.1" + :group 'browse-url) + +(defcustom browse-url-text-input-field 'avoid + "Action on selecting an existing text browser buffer at an input field. +What to do when sending a new URL to an existing text browser buffer in Emacs +if the browser cursor is on an input field (in which case the `g' command would be entered as data). Such fields are recognized by the -underlines ____. Allowed values: nil: disregard it, 'warn: warn the -user and don't emit the URL, 'avoid: try to avoid the field by moving +underlines ____. Allowed values: nil: disregard it, `warn': warn the +user and don't emit the URL, `avoid': try to avoid the field by moving down (this *won't* always work)." :type '(choice (const :tag "Move to try to avoid field" :value avoid) (const :tag "Disregard" :value nil) (const :tag "Warn, don't emit URL" :value warn)) - :version "20.3" + :version "23.1" :group 'browse-url) -(defcustom browse-url-lynx-input-attempts 10 - "How many times to try to move down from a series of lynx input fields." +(defcustom browse-url-text-input-attempts 10 + "How many times to try to move down from a series of text browser input fields." :type 'integer + :version "23.1" :group 'browse-url) -(defcustom browse-url-lynx-input-delay 0.2 - "How many seconds to wait for lynx between moves down from an input field." +(defcustom browse-url-text-input-delay 0.2 + "Seconds to wait for a text browser between moves down from an input field." :type 'number + :version "23.1" :group 'browse-url) (defcustom browse-url-kde-program "kfmclient" @@ -594,6 +605,34 @@ down (this *won't* always work)." :type '(repeat (string :tag "Argument")) :group 'browse-url) +(defcustom browse-url-elinks-wrapper '("xterm" "-e") + "*Wrapper command prepended to the Elinks command-line." + :type '(repeat (string :tag "Wrapper")) + :group 'browse-url) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; URL encoding + +(defun browse-url-url-encode-chars (text chars) + "URL-encode the chars in TEXT that match CHARS. +CHARS is a regexp-like character alternative (e.g., \"[,)$]\")." + (let ((encoded-text (copy-sequence text)) + (s 0)) + (while (setq s (string-match chars encoded-text s)) + (setq encoded-text + (replace-match (format "%%%x" + (string-to-char (match-string 0 encoded-text))) + t t encoded-text) + s (1+ s))) + encoded-text)) + +(defun browse-url-encode-url (url) + "Escape annoying characters in URL. +The annoying characters are those that can mislead a webbrowser +regarding its parameter treatment. For instance, `,' can +be misleading because it could be used to separate URLs." + (browse-url-url-encode-chars url "[,)$]")) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; URL input @@ -666,14 +705,7 @@ Use variable `browse-url-filename-alist' to map filenames to URLs." (or file-name-coding-system default-file-name-coding-system)))) (if coding (setq file (encode-coding-string file coding)))) - ;; URL-encode special chars, do % first - (let ((s 0)) - (while (setq s (string-match "%" file s)) - (setq file (replace-match "%25" t t file) - s (1+ s)))) - (while (string-match "[*\"()',=;? ]" file) - (let ((enc (format "%%%x" (aref file (match-beginning 0))))) - (setq file (replace-match enc t t file)))) + (setq file (browse-url-url-encode-chars file "[*\"()',=;?% ]")) (dolist (map browse-url-filename-alist) (when (and map (string-match (car map) file)) (setq file (replace-match (cdr map) t nil file)))) @@ -793,6 +825,7 @@ to use." ;; --- Default MS-Windows browser --- (defvar dos-windows-version) +(declare-function w32-shell-execute "w32fns.c") ;; Defined in C. (defun browse-url-default-windows-browser (url &optional new-window) (interactive (browse-url-interactive-arg "URL: ")) @@ -844,19 +877,19 @@ used instead of `browse-url-new-window-flag'. The order attempted is gnome-moz-remote, Mozilla, Firefox, Galeon, Konqueror, Netscape, Mosaic, Lynx in an xterm, and then W3." (apply - (cond - ((executable-find browse-url-gnome-moz-program) 'browse-url-gnome-moz) - ((executable-find browse-url-mozilla-program) 'browse-url-mozilla) - ((executable-find browse-url-firefox-program) 'browse-url-firefox) - ((executable-find browse-url-galeon-program) 'browse-url-galeon) - ((executable-find browse-url-kde-program) 'browse-url-kde) - ((executable-find browse-url-netscape-program) 'browse-url-netscape) - ((executable-find browse-url-mosaic-program) 'browse-url-mosaic) - ((executable-find browse-url-xterm-program) 'browse-url-lynx-xterm) - ((locate-library "w3") 'browse-url-w3) - (t - (lambda (&ignore args) (error "No usable browser found")))) - url args)) + (cond + ((executable-find browse-url-gnome-moz-program) 'browse-url-gnome-moz) + ((executable-find browse-url-mozilla-program) 'browse-url-mozilla) + ((executable-find browse-url-firefox-program) 'browse-url-firefox) + ((executable-find browse-url-galeon-program) 'browse-url-galeon) + ((executable-find browse-url-kde-program) 'browse-url-kde) + ((executable-find browse-url-netscape-program) 'browse-url-netscape) + ((executable-find browse-url-mosaic-program) 'browse-url-mosaic) + ((executable-find browse-url-xterm-program) 'browse-url-text-xterm) + ((locate-library "w3") 'browse-url-w3) + (t + (lambda (&ignore args) (error "No usable browser found")))) + url args)) ;;;###autoload (defun browse-url-netscape (url &optional new-window) @@ -876,11 +909,7 @@ is loaded in a new tab in an existing window instead. When called non-interactively, optional second argument NEW-WINDOW is used instead of `browse-url-new-window-flag'." (interactive (browse-url-interactive-arg "URL: ")) - ;; URL encode any `confusing' characters in the URL. This needs to - ;; include at least commas; presumably also close parens and dollars. - (while (string-match "[,)$]" url) - (setq url (replace-match - (format "%%%x" (string-to-char (match-string 0 url))) t t url))) + (setq url (browse-url-encode-url url)) (let* ((process-environment (browse-url-process-environment)) (process (apply 'start-process @@ -922,7 +951,7 @@ How depends on `browse-url-netscape-version'." ;; <peter.kruse@psychologie.uni-regensburg.de>. (browse-url-netscape-send (if (>= browse-url-netscape-version 4) "xfeDoCommand(reload)" - "reload"))) + "reload"))) (defun browse-url-netscape-send (command) "Send a remote control command to Netscape." @@ -950,11 +979,7 @@ new tab in an existing window instead. When called non-interactively, optional second argument NEW-WINDOW is used instead of `browse-url-new-window-flag'." (interactive (browse-url-interactive-arg "URL: ")) - ;; URL encode any `confusing' characters in the URL. This needs to - ;; include at least commas; presumably also close parens and dollars. - (while (string-match "[,)$]" url) - (setq url (replace-match - (format "%%%x" (string-to-char (match-string 0 url))) t t url))) + (setq url (browse-url-encode-url url)) (let* ((process-environment (browse-url-process-environment)) (process (apply 'start-process @@ -1012,11 +1037,7 @@ command line parameter. Therefore, the are ignored as well. Firefox on Windows will always open the requested URL in a new window." (interactive (browse-url-interactive-arg "URL: ")) - ;; URL encode any `confusing' characters in the URL. This needs to - ;; include at least commas; presumably also close parens. - (while (string-match "[,)]" url) - (setq url (replace-match - (format "%%%x" (string-to-char (match-string 0 url))) t t url))) + (setq url (browse-url-encode-url url)) (let* ((process-environment (browse-url-process-environment)) (process (apply 'start-process @@ -1068,11 +1089,7 @@ new tab in an existing window instead. When called non-interactively, optional second argument NEW-WINDOW is used instead of `browse-url-new-window-flag'." (interactive (browse-url-interactive-arg "URL: ")) - ;; URL encode any `confusing' characters in the URL. This needs to - ;; include at least commas; presumably also close parens and dollars. - (while (string-match "[,)$]" url) - (setq url (replace-match - (format "%%%x" (string-to-char (match-string 0 url))) t t url))) + (setq url (browse-url-encode-url url)) (let* ((process-environment (browse-url-process-environment)) (process (apply 'start-process (concat "galeon " url) @@ -1117,11 +1134,7 @@ new tab in an existing window instead. When called non-interactively, optional second argument NEW-WINDOW is used instead of `browse-url-new-window-flag'." (interactive (browse-url-interactive-arg "URL: ")) - ;; URL encode any `confusing' characters in the URL. This needs to - ;; include at least commas; presumably also close parens and dollars. - (while (string-match "[,)$]" url) - (setq url (replace-match - (format "%%%x" (string-to-char (match-string 0 url))) t t url))) + (setq url (browse-url-encode-url url)) (let* ((process-environment (browse-url-process-environment)) (process (apply 'start-process (concat "epiphany " url) @@ -1149,6 +1162,8 @@ used instead of `browse-url-new-window-flag'." browse-url-epiphany-program (append browse-url-epiphany-startup-arguments (list url)))))) +(defvar url-handler-regexp) + ;;;###autoload (defun browse-url-emacs (url &optional new-window) "Ask Emacs to load URL into a buffer and show it in another window." @@ -1183,7 +1198,7 @@ used instead of `browse-url-new-window-flag'." (append browse-url-gnome-moz-arguments (if (browse-url-maybe-new-window new-window) - '("--newwin")) + '("--newwin")) (list "--raise" url)))) ;; --- Mosaic --- @@ -1269,6 +1284,10 @@ used instead of `browse-url-new-window-flag'." ;; --- W3 --- +;; External. +(declare-function w3-fetch-other-window "ext:w3m" (&optional url)) +(declare-function w3-fetch "ext:w3m" (&optional url target)) + ;;;###autoload (defun browse-url-w3 (url &optional new-window) "Ask the w3 WWW browser to load URL. @@ -1281,7 +1300,7 @@ prefix argument reverses the effect of `browse-url-new-window-flag'. When called non-interactively, optional second argument NEW-WINDOW is used instead of `browse-url-new-window-flag'." (interactive (browse-url-interactive-arg "W3 URL: ")) - (require 'w3) ; w3-fetch-other-window not autoloaded + (require 'w3) ; w3-fetch-other-window not autoloaded (if (browse-url-maybe-new-window new-window) (w3-fetch-other-window url) (w3-fetch url))) @@ -1293,47 +1312,50 @@ used instead of `browse-url-new-window-flag'." The `browse-url-gnudoit-program' program is used with options given by `browse-url-gnudoit-args'. Default to the URL around or before point." (interactive (browse-url-interactive-arg "W3 URL: ")) - (apply 'start-process (concat "gnudoit:" url) nil - browse-url-gnudoit-program - (append browse-url-gnudoit-args - (list (concat "(w3-fetch \"" url "\")") - "(raise-frame)")))) + (apply 'start-process (concat "gnudoit:" url) nil + browse-url-gnudoit-program + (append browse-url-gnudoit-args + (list (concat "(w3-fetch \"" url "\")") + "(raise-frame)")))) ;; --- Lynx in an xterm --- ;;;###autoload -(defun browse-url-lynx-xterm (url &optional new-window) +(defun browse-url-text-xterm (url &optional new-window) ;; new-window ignored - "Ask the Lynx WWW browser to load URL. -Default to the URL around or before point. A new Lynx process is run + "Ask a text browser to load URL. +URL defaults to the URL around or before point. +This runs the text browser specified by `browse-url-text-browser'. in an Xterm window using the Xterm program named by `browse-url-xterm-program' with possible additional arguments `browse-url-xterm-args'." - (interactive (browse-url-interactive-arg "Lynx URL: ")) - (apply #'start-process `(,(concat "lynx" url) nil ,browse-url-xterm-program - ,@browse-url-xterm-args "-e" "lynx" - ,url))) + (interactive (browse-url-interactive-arg "Text browser URL: ")) + (apply #'start-process `(,(concat browse-url-text-browser url) + nil ,browse-url-xterm-program + ,@browse-url-xterm-args "-e" browse-url-text-browser + ,url))) ;; --- Lynx in an Emacs "term" window --- ;;;###autoload -(defun browse-url-lynx-emacs (url &optional new-buffer) - "Ask the Lynx WWW browser to load URL. -Default to the URL around or before point. With a prefix argument, run -a new Lynx process in a new buffer. +(defun browse-url-text-emacs (url &optional new-buffer) + "Ask a text browser to load URL. +URL defaults to the URL around or before point. +This runs the text browser specified by `browse-url-text-browser'. +With a prefix argument, it runs a new browser process in a new buffer. When called interactively, if variable `browse-url-new-window-flag' is -non-nil, load the document in a new lynx in a new term window, +non-nil, load the document in a new browser process in a new term window, otherwise use any existing one. A non-nil interactive prefix argument reverses the effect of `browse-url-new-window-flag'. When called non-interactively, optional second argument NEW-WINDOW is used instead of `browse-url-new-window-flag'." - (interactive (browse-url-interactive-arg "Lynx URL: ")) - (let* ((system-uses-terminfo t) ; Lynx uses terminfo + (interactive (browse-url-interactive-arg "Text browser URL: ")) + (let* ((system-uses-terminfo t) ; Lynx uses terminfo ;; (term-term-name "vt100") ; ?? - (buf (get-buffer "*lynx*")) + (buf (get-buffer "*text browser*")) (proc (and buf (get-buffer-process buf))) - (n browse-url-lynx-input-attempts)) + (n browse-url-text-input-attempts)) (if (and (browse-url-maybe-new-window new-buffer) buf) ;; Rename away the OLD buffer. This isn't very polite, but ;; term insists on working in a buffer named *lynx* and would @@ -1344,11 +1366,13 @@ used instead of `browse-url-new-window-flag'." (not buf) (not proc) (not (memq (process-status proc) '(run stop)))) - ;; start a new lynx + ;; start a new text browser (progn (setq buf (apply #'make-term - `("lynx" "lynx" nil ,@browse-url-lynx-emacs-args + `(,browse-url-text-browser + ,browse-url-text-browser + nil ,@browse-url-text-emacs-args ,url))) (switch-to-buffer buf) (term-char-mode) @@ -1360,21 +1384,21 @@ used instead of `browse-url-new-window-flag'." (if (not (memq (process-status process) '(run stop))) (let ((buf (process-buffer process))) (if buf (kill-buffer buf))))))) - ;; send the url to lynx in the old buffer + ;; Send the url to the text browser in the old buffer (let ((win (get-buffer-window buf t))) (if win (select-window win) (switch-to-buffer buf))) (if (eq (following-char) ?_) - (cond ((eq browse-url-lynx-input-field 'warn) + (cond ((eq browse-url-text-input-field 'warn) (error "Please move out of the input field first")) - ((eq browse-url-lynx-input-field 'avoid) + ((eq browse-url-text-input-field 'avoid) (while (and (eq (following-char) ?_) (> n 0)) - (term-send-down) ; down arrow - (sit-for browse-url-lynx-input-delay)) + (term-send-down) ; down arrow + (sit-for browse-url-text-input-delay)) (if (eq (following-char) ?_) (error "Cannot move out of the input field, sorry"))))) - (term-send-string proc (concat "g" ; goto + (term-send-string proc (concat "g" ; goto "\C-u" ; kill default url url "\r"))))) @@ -1431,7 +1455,7 @@ browser is started up in a new process with possible additional arguments don't offer a form of remote control." (interactive (browse-url-interactive-arg "URL: ")) (if (not browse-url-generic-program) - (error "No browser defined (`browse-url-generic-program')")) + (error "No browser defined (`browse-url-generic-program')")) (apply 'call-process browse-url-generic-program nil 0 nil (append browse-url-generic-args (list url)))) @@ -1443,7 +1467,56 @@ Default to the URL around or before point." (interactive (browse-url-interactive-arg "KDE URL: ")) (message "Sending URL to KDE...") (apply #'start-process (concat "KDE " url) nil browse-url-kde-program - (append browse-url-kde-args (list url)))) + (append browse-url-kde-args (list url)))) + +(defun browse-url-elinks-new-window (url) + "Ask the Elinks WWW browser to load URL in a new window." + (let ((process-environment (browse-url-process-environment))) + (apply #'start-process + (append (list (concat "elinks:" url) + nil) + browse-url-elinks-wrapper + (list "elinks" url))))) + +;;;###autoload +(defun browse-url-elinks (url &optional new-window) + "Ask the Elinks WWW browser to load URL. +Default to the URL around the point. + +The document is loaded in a new tab of a running Elinks or, if +none yet running, a newly started instance. + +The Elinks command will be prepended by the program+arguments +from `browse-url-elinks-wrapper'." + (interactive (browse-url-interactive-arg "URL: ")) + (setq url (browse-url-encode-url url)) + (if new-window + (browse-url-elinks-new-window url) + (let ((process-environment (browse-url-process-environment)) + (elinks-ping-process (start-process "elinks-ping" nil + "elinks" "-remote" "ping()"))) + (set-process-sentinel elinks-ping-process + `(lambda (process change) + (browse-url-elinks-sentinel process ,url)))))) + +(defun browse-url-elinks-sentinel (process url) + "Determines if Elinks is running or a new one has to be started." + (let ((exit-status (process-exit-status process))) + ;; Try to determine if an instance is running or if we have to + ;; create a new one. + (case exit-status + (5 + ;; No instance, start a new one. + (browse-url-elinks-new-window url)) + (0 + ;; Found an instance, open URL in new tab. + (let ((process-environment (browse-url-process-environment))) + (start-process (concat "elinks:" url) nil + "elinks" "-remote" + (concat "openURL(\"" url "\",new-tab)")))) + (otherwise + (error "Unrecognized exit-code %d of process `elinks'" + exit-status))))) (provide 'browse-url) diff --git a/lisp/net/dbus.el b/lisp/net/dbus.el new file mode 100644 index 00000000000..83075762b73 --- /dev/null +++ b/lisp/net/dbus.el @@ -0,0 +1,354 @@ +;;; -*- no-byte-compile: t; -*- +;;; dbus.el --- Elisp bindings for D-Bus. + +;; Copyright (C) 2007, 2008 Free Software Foundation, Inc. + +;; Author: Michael Albinus <michael.albinus@gmx.de> +;; Keywords: comm, hardware + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This package provides language bindings for the D-Bus API. D-Bus +;; is a message bus system, a simple way for applications to talk to +;; one another. See <http://dbus.freedesktop.org/> for details. + +;; Low-level language bindings are implemented in src/dbusbind.c. + +;;; Code: + +(require 'xml) + +(defconst dbus-service-dbus "org.freedesktop.DBus" + "The bus name used to talk to the bus itself.") + +(defconst dbus-path-dbus "/org/freedesktop/DBus" + "The object path used to talk to the bus itself.") + +(defconst dbus-interface-dbus "org.freedesktop.DBus" + "The interface exported by the object with `dbus-service-dbus' and `dbus-path-dbus'.") + +(defconst dbus-interface-introspectable "org.freedesktop.DBus.Introspectable" + "The interface supported by introspectable objects.") + + +;;; Hash table of registered functions. + +;; We create it here. So we have a simple test in dbusbind.c, whether +;; the Lisp code has been loaded. +(setq dbus-registered-functions-table (make-hash-table :test 'equal)) + +(defun dbus-list-hash-table () + "Returns all registered member registrations to D-Bus. +The return value is a list, with elements of kind (KEY . VALUE). +See `dbus-registered-functions-table' for a description of the +hash table." + (let (result) + (maphash + '(lambda (key value) (add-to-list 'result (cons key value) 'append)) + dbus-registered-functions-table) + result)) + +(defun dbus-name-owner-changed-handler (&rest args) + "Reapplies all member registrations to D-Bus. +This handler is applied when a \"NameOwnerChanged\" signal has +arrived. SERVICE is the object name for which the name owner has +been changed. OLD-OWNER is the previous owner of SERVICE, or the +empty string if SERVICE was not owned yet. NEW-OWNER is the new +owner of SERVICE, or the empty string if SERVICE looses any name owner. + +usage: (dbus-name-owner-changed-handler service old-owner new-owner)" + (save-match-data + ;; Check the arguments. We should silently ignore it when they + ;; are wrong. + (if (and (= (length args) 3) + (stringp (car args)) + (stringp (cadr args)) + (stringp (caddr args))) + (let ((service (car args)) + (old-owner (cadr args)) + (new-owner (caddr args))) + ;; Check whether SERVICE is a known name. + (when (not (string-match "^:" service)) + (maphash + '(lambda (key value) + (dolist (elt value) + ;; key has the structure (BUS INTERFACE MEMBER). + ;; elt has the structure (UNAME SERVICE PATH HANDLER). + (when (string-equal old-owner (car elt)) + ;; Remove old key, and add new entry with changed name. + (dbus-unregister-object (list key (cdr elt))) + ;; Maybe we could arrange the lists a little bit better + ;; that we don't need to extract every single element? + (dbus-register-signal + ;; BUS SERVICE PATH + (nth 0 key) (nth 1 elt) (nth 2 elt) + ;; INTERFACE MEMBER HANDLER + (nth 1 key) (nth 2 key) (nth 3 elt))))) + (copy-hash-table dbus-registered-functions-table)))) + ;; The error is reported only in debug mode. + (when dbus-debug + (signal + 'dbus-error + (cons + (format "Wrong arguments of %s.NameOwnerChanged" dbus-interface-dbus) + args)))))) + +;; Register the handler. +(condition-case nil + (progn + (dbus-register-signal + :system dbus-service-dbus dbus-path-dbus dbus-interface-dbus + "NameOwnerChanged" 'dbus-name-owner-changed-handler) + (dbus-register-signal + :session dbus-service-dbus dbus-path-dbus dbus-interface-dbus + "NameOwnerChanged" 'dbus-name-owner-changed-handler)) + (dbus-error)) + + +;;; D-Bus events. + +(defun dbus-check-event (event) + "Checks whether EVENT is a well formed D-Bus event. +EVENT is a list which starts with symbol `dbus-event': + + (dbus-event BUS SERIAL SERVICE PATH INTERFACE MEMBER HANDLER &rest ARGS) + +BUS identifies the D-Bus the message is coming from. It is +either the symbol `:system' or the symbol `:session'. SERIAL is +the serial number of the received D-Bus message if it is a method +call, or nil. SERVICE and PATH are the unique name and the +object path of the D-Bus object emitting the message. INTERFACE +and MEMBER denote the message which has been sent. HANDLER is +the function which has been registered for this message. ARGS +are the arguments passed to HANDLER, when it is called during +event handling in `dbus-handle-event'. + +This function raises a `dbus-error' signal in case the event is +not well formed." + (when dbus-debug (message "DBus-Event %s" event)) + (unless (and (listp event) + (eq (car event) 'dbus-event) + ;; Bus symbol. + (symbolp (nth 1 event)) + ;; Serial. + (or (natnump (nth 2 event)) (null (nth 2 event))) + ;; Service. + (stringp (nth 3 event)) + ;; Object path. + (stringp (nth 4 event)) + ;; Interface. + (stringp (nth 5 event)) + ;; Member. + (stringp (nth 6 event)) + ;; Handler. + (functionp (nth 7 event))) + (signal 'dbus-error (list "Not a valid D-Bus event" event)))) + +;;;###autoload +(defun dbus-handle-event (event) + "Handle events from the D-Bus. +EVENT is a D-Bus event, see `dbus-check-event'. HANDLER, being +part of the event, is called with arguments ARGS." + (interactive "e") + ;; We don't want to raise an error, because this function is called + ;; in the event handling loop. + (condition-case err + (let (result) + (dbus-check-event event) + (setq result (apply (nth 7 event) (nthcdr 8 event))) + (unless (consp result) (setq result (cons result nil))) + ;; Return a message when serial is not nil. + (when (not (null (nth 2 event))) + (apply 'dbus-method-return + (nth 1 event) (nth 2 event) (nth 3 event) result))) + (dbus-error (when dbus-debug (signal (car err) (cdr err)))))) + +(defun dbus-event-bus-name (event) + "Return the bus name the event is coming from. +The result is either the symbol `:system' or the symbol `:session'. +EVENT is a D-Bus event, see `dbus-check-event'. This function +raises a `dbus-error' signal in case the event is not well +formed." + (dbus-check-event event) + (nth 1 event)) + +(defun dbus-event-serial-number (event) + "Return the serial number of the corresponding D-Bus message. +The result is a number in case the D-Bus message is a method +call, or nil for all other mesage types. The serial number is +needed for generating a reply message. EVENT is a D-Bus event, +see `dbus-check-event'. This function raises a `dbus-error' +signal in case the event is not well formed." + (dbus-check-event event) + (nth 2 event)) + +(defun dbus-event-service-name (event) + "Return the name of the D-Bus object the event is coming from. +The result is a string. EVENT is a D-Bus event, see `dbus-check-event'. +This function raises a `dbus-error' signal in case the event is +not well formed." + (dbus-check-event event) + (nth 3 event)) + +(defun dbus-event-path-name (event) + "Return the object path of the D-Bus object the event is coming from. +The result is a string. EVENT is a D-Bus event, see `dbus-check-event'. +This function raises a `dbus-error' signal in case the event is +not well formed." + (dbus-check-event event) + (nth 4 event)) + +(defun dbus-event-interface-name (event) + "Return the interface name of the D-Bus object the event is coming from. +The result is a string. EVENT is a D-Bus event, see `dbus-check-event'. +This function raises a `dbus-error' signal in case the event is +not well formed." + (dbus-check-event event) + (nth 5 event)) + +(defun dbus-event-member-name (event) + "Return the member name the event is coming from. +It is either a signal name or a method name. The result is is a +string. EVENT is a D-Bus event, see `dbus-check-event'. This +function raises a `dbus-error' signal in case the event is not +well formed." + (dbus-check-event event) + (nth 6 event)) + + +;;; D-Bus registered names. + +(defun dbus-list-activatable-names () + "Return the D-Bus service names which can be activated as list. +The result is a list of strings, which is nil when there are no +activatable service names at all." + (condition-case nil + (dbus-call-method + :system dbus-service-dbus + dbus-path-dbus dbus-interface-dbus "ListActivatableNames") + (dbus-error))) + +(defun dbus-list-names (bus) + "Return the service names registered at D-Bus BUS. +The result is a list of strings, which is nil when there are no +registered service names at all. Well known names are strings like +\"org.freedesktop.DBus\". Names starting with \":\" are unique names +for services." + (condition-case nil + (dbus-call-method + bus dbus-service-dbus dbus-path-dbus dbus-interface-dbus "ListNames") + (dbus-error))) + +(defun dbus-list-known-names (bus) + "Retrieve all services which correspond to a known name in BUS. +A service has a known name if it doesn't start with \":\"." + (let (result) + (dolist (name (dbus-list-names bus) result) + (unless (string-equal ":" (substring name 0 1)) + (add-to-list 'result name 'append))))) + +(defun dbus-list-queued-owners (bus service) +"Return the unique names registered at D-Bus BUS and queued for SERVICE. +The result is a list of strings, or nil when there are no queued name +owners service names at all." + (condition-case nil + (dbus-call-method + bus dbus-service-dbus dbus-path-dbus + dbus-interface-dbus "ListQueuedOwners" service) + (dbus-error))) + +(defun dbus-get-name-owner (bus service) + "Return the name owner of SERVICE registered at D-Bus BUS. +The result is either a string, or nil if there is no name owner." + (condition-case nil + (dbus-call-method + bus dbus-service-dbus dbus-path-dbus + dbus-interface-dbus "GetNameOwner" service) + (dbus-error))) + +(defun dbus-introspect (bus service path) + "Return the introspection data of SERVICE in D-Bus BUS at object path PATH. +The data are in XML format. + +Example: + +\(dbus-introspect + :system \"org.freedesktop.Hal\" + \"/org/freedesktop/Hal/devices/computer\")" + (condition-case nil + (dbus-call-method + bus service path dbus-interface-introspectable "Introspect") + (dbus-error))) + +(if nil ;; Must be reworked. Shall we offer D-Bus signatures at all? +(defun dbus-get-signatures (bus interface signal) + "Retrieve SIGNAL's type signatures from D-Bus. +The result is a list of SIGNAL's type signatures. Example: + + \(\"s\" \"b\" \"ai\"\) + +This list represents 3 parameters of SIGNAL. The first parameter +is of type string, the second parameter is of type boolean, and +the third parameter is of type array of integer. + +If INTERFACE or SIGNAL do not exist, or if they do not support +the D-Bus method org.freedesktop.DBus.Introspectable.Introspect, +the function returns nil." + (condition-case nil + (let ((introspect-xml + (with-temp-buffer + (insert (dbus-introspect bus interface)) + (xml-parse-region (point-min) (point-max)))) + node interfaces signals args result) + ;; Get the root node. + (setq node (xml-node-name introspect-xml)) + ;; Get all interfaces. + (setq interfaces (xml-get-children node 'interface)) + (while interfaces + (when (string-equal (xml-get-attribute (car interfaces) 'name) + interface) + ;; That's the requested interface. Check for signals. + (setq signals (xml-get-children (car interfaces) 'signal)) + (while signals + (when (string-equal (xml-get-attribute (car signals) 'name) + signal) + ;; The signal we are looking for. + (setq args (xml-get-children (car signals) 'arg)) + (while args + (unless (xml-get-attribute (car args) 'type) + ;; This shouldn't happen, let's escape. + (signal 'dbus-error "")) + ;; We append the signature. + (setq + result (append result + (list (xml-get-attribute (car args) 'type)))) + (setq args (cdr args))) + (setq signals nil)) + (setq signals (cdr signals))) + (setq interfaces nil)) + (setq interfaces (cdr interfaces))) + result) + ;; We ignore `dbus-error'. There might be no introspectable interface. + (dbus-error nil))) +) ;; (if nil ... + +(provide 'dbus) + +;; arch-tag: a47caf84-9162-4811-90cc-5d388e37b9bd +;;; dbus.el ends here diff --git a/lisp/net/dig.el b/lisp/net/dig.el new file mode 100644 index 00000000000..16755439bd8 --- /dev/null +++ b/lisp/net/dig.el @@ -0,0 +1,196 @@ +;;; dig.el --- Domain Name System dig interface + +;; Copyright (C) 2000, 2001, 2002, 2003, 2004, +;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc. + +;; Author: Simon Josefsson <simon@josefsson.org> +;; Keywords: DNS BIND dig + +;; 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, or (at your +;; option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This provide an interface for "dig". +;; +;; For interactive use, try M-x dig and type a hostname. Use `q' to quit +;; dig buffer. +;; +;; For use in elisp programs, call `dig-invoke' and use +;; `dig-extract-rr' to extract resource records. + +;;; Release history: + +;; 2000-10-28 posted on gnu.emacs.sources + +;;; Code: + +(eval-when-compile (require 'cl)) + +(defgroup dig nil + "Dig configuration." + :group 'comm) + +(defcustom dig-program "dig" + "Name of dig (domain information groper) binary." + :type 'file + :group 'dig) + +(defcustom dig-dns-server nil + "DNS server to query. +If nil, use system defaults." + :type '(choice (const :tag "System defaults") + string) + :group 'dig) + +(defcustom dig-font-lock-keywords + '(("^;; [A-Z]+ SECTION:" 0 font-lock-keyword-face) + ("^;;.*" 0 font-lock-comment-face) + ("^; <<>>.*" 0 font-lock-type-face) + ("^;.*" 0 font-lock-function-name-face)) + "Default expressions to highlight in dig mode." + :type 'sexp + :group 'dig) + +(defun dig-invoke (domain &optional + query-type query-class query-option + dig-option server) + "Call dig with given arguments and return buffer containing output. +DOMAIN is a string with a DNS domain. QUERY-TYPE is an optional string +with a DNS type. QUERY-CLASS is an optional string with a DNS class. +QUERY-OPTION is an optional string with dig \"query options\". +DIG-OPTIONS is an optional string with parameters for the dig program. +SERVER is an optional string with a domain name server to query. + +Dig is an external program found in the BIND name server distribution, +and is a commonly available debugging tool." + (let (buf cmdline) + (setq buf (generate-new-buffer "*dig output*")) + (if dig-option (push dig-option cmdline)) + (if query-option (push query-option cmdline)) + (if query-class (push query-class cmdline)) + (if query-type (push query-type cmdline)) + (push domain cmdline) + (if server (push (concat "@" server) cmdline) + (if dig-dns-server (push (concat "@" dig-dns-server) cmdline))) + (apply 'call-process dig-program nil buf nil cmdline) + buf)) + +(defun dig-extract-rr (domain &optional type class) + "Extract resource records for DOMAIN, TYPE and CLASS from buffer. +Buffer should contain output generated by `dig-invoke'." + (save-excursion + (goto-char (point-min)) + (if (re-search-forward + (concat domain "\\.?[\t ]+[0-9wWdDhHmMsS]+[\t ]+" + (upcase (or class "IN")) "[\t ]+" (upcase (or type "A"))) + nil t) + (let (b e) + (end-of-line) + (setq e (point)) + (beginning-of-line) + (setq b (point)) + (when (search-forward " (" e t) + (search-forward " )")) + (end-of-line) + (setq e (point)) + (buffer-substring b e)) + (and (re-search-forward (concat domain "\\.?[\t ]+[0-9wWdDhHmMsS]+[\t ]+" + (upcase (or class "IN")) + "[\t ]+CNAME[\t ]+\\(.*\\)$") nil t) + (dig-extract-rr (match-string 1) type class))))) + +(defun dig-rr-get-pkix-cert (rr) + (let (b e str) + (string-match "[^\t ]+[\t ]+[0-9wWdDhHmMsS]+[\t ]+IN[\t ]+CERT[\t ]+\\(1\\|PKIX\\)[\t ]+[0-9]+[\t ]+[0-9]+[\t ]+(?" rr) + (setq b (match-end 0)) + (string-match ")" rr) + (setq e (match-beginning 0)) + (setq str (substring rr b e)) + (while (string-match "[\t \n\r]" str) + (setq str (replace-match "" nil nil str))) + str)) + +;; XEmacs does it like this. For Emacs, we have to set the +;; `font-lock-defaults' buffer-local variable. +(put 'dig-mode 'font-lock-defaults '(dig-font-lock-keywords t)) + +(put 'dig-mode 'mode-class 'special) + +(defvar dig-mode-map nil) +(unless dig-mode-map + (setq dig-mode-map (make-sparse-keymap)) + (suppress-keymap dig-mode-map) + + (define-key dig-mode-map "q" 'dig-exit)) + +(defun dig-mode () + "Major mode for displaying dig output." + (interactive) + (kill-all-local-variables) + (setq mode-name "dig") + (setq major-mode 'dig-mode) + (use-local-map dig-mode-map) + (buffer-disable-undo) + (unless (featurep 'xemacs) + (set (make-local-variable 'font-lock-defaults) + '(dig-font-lock-keywords t))) + (when (featurep 'font-lock) + (font-lock-set-defaults)) + (save-current-buffer + (if (fboundp 'run-mode-hooks) + (run-mode-hooks 'dig-mode-hook) + (run-hooks 'dig-mode-hook)))) + +(defun dig-exit () + "Quit dig output buffer." + (interactive) + (kill-buffer (current-buffer))) + +(defun dig (domain &optional + query-type query-class query-option dig-option server) + "Query addresses of a DOMAIN using dig, by calling `dig-invoke'. +Optional arguments are passed to `dig-invoke'." + (interactive "sHost: ") + (switch-to-buffer + (dig-invoke domain query-type query-class query-option dig-option server)) + (goto-char (point-min)) + (and (search-forward ";; ANSWER SECTION:" nil t) + (forward-line)) + (dig-mode) + (setq buffer-read-only t) + (set-buffer-modified-p nil)) + +;; named for consistency with query-dns in dns.el +(defun query-dig (domain &optional + query-type query-class query-option dig-option server) + "Query addresses of a DOMAIN using dig. +It works by calling `dig-invoke' and `dig-extract-rr'. Optional +arguments are passed to `dig-invoke' and `dig-extract-rr'. Returns +nil for domain/class/type queries that results in no data." +(let ((buffer (dig-invoke domain query-type query-class + query-option dig-option server))) + (when buffer + (switch-to-buffer buffer) + (let ((digger (dig-extract-rr domain query-type query-class))) + (kill-buffer buffer) + digger)))) + +(provide 'dig) + +;;; arch-tag: 1d61726e-9400-4013-9ae7-4035e0c7f7d6 +;;; dig.el ends here diff --git a/lisp/net/dns.el b/lisp/net/dns.el new file mode 100644 index 00000000000..f8f46173fe4 --- /dev/null +++ b/lisp/net/dns.el @@ -0,0 +1,430 @@ +;;; dns.el --- Domain Name Service lookups + +;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 +;; Free Software Foundation, Inc. + +;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org> +;; Keywords: network + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;;; Code: + +(defvar dns-timeout 5 + "How many seconds to wait when doing DNS queries.") + +(defvar dns-servers nil + "Which DNS servers to query. +If nil, /etc/resolv.conf will be consulted.") + +;;; Internal code: + +(defvar dns-query-types + '((A 1) + (NS 2) + (MD 3) + (MF 4) + (CNAME 5) + (SOA 6) + (MB 7) + (MG 8) + (MR 9) + (NULL 10) + (WKS 11) + (PTR 12) + (HINFO 13) + (MINFO 14) + (MX 15) + (TXT 16) + (AAAA 28) ; RFC3596 + (SRV 33) ; RFC2782 + (AXFR 252) + (MAILB 253) + (MAILA 254) + (* 255)) + "Names of query types and their values.") + +(defvar dns-classes + '((IN 1) + (CS 2) + (CH 3) + (HS 4)) + "Classes of queries.") + +(defun dns-write-bytes (value &optional length) + (let (bytes) + (dotimes (i (or length 1)) + (push (% value 256) bytes) + (setq value (/ value 256))) + (dolist (byte bytes) + (insert byte)))) + +(defun dns-read-bytes (length) + (let ((value 0)) + (dotimes (i length) + (setq value (logior (* value 256) (following-char))) + (forward-char 1)) + value)) + +(defun dns-get (type spec) + (cadr (assq type spec))) + +(defun dns-inverse-get (value spec) + (let ((found nil)) + (while (and (not found) + spec) + (if (eq value (cadr (car spec))) + (setq found (caar spec)) + (pop spec))) + found)) + +(defun dns-write-name (name) + (dolist (part (split-string name "\\.")) + (dns-write-bytes (length part)) + (insert part)) + (dns-write-bytes 0)) + +(defun dns-read-string-name (string buffer) + (let (default-enable-multibyte-characters) + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (dns-read-name buffer)))) + +(defun dns-read-name (&optional buffer) + (let ((ended nil) + (name nil) + length) + (while (not ended) + (setq length (dns-read-bytes 1)) + (if (= 192 (logand length (lsh 3 6))) + (let ((offset (+ (* (logand 63 length) 256) + (dns-read-bytes 1)))) + (save-excursion + (when buffer + (set-buffer buffer)) + (goto-char (1+ offset)) + (setq ended (dns-read-name buffer)))) + (if (zerop length) + (setq ended t) + (push (buffer-substring (point) + (progn (forward-char length) (point))) + name)))) + (if (stringp ended) + (if (null name) + ended + (concat (mapconcat 'identity (nreverse name) ".") "." ended)) + (mapconcat 'identity (nreverse name) ".")))) + +(defun dns-write (spec &optional tcp-p) + "Write a DNS packet according to SPEC. +If TCP-P, the first two bytes of the package with be the length field." + (with-temp-buffer + (dns-write-bytes (dns-get 'id spec) 2) + (dns-write-bytes + (logior + (lsh (if (dns-get 'response-p spec) 1 0) -7) + (lsh + (cond + ((eq (dns-get 'opcode spec) 'query) 0) + ((eq (dns-get 'opcode spec) 'inverse-query) 1) + ((eq (dns-get 'opcode spec) 'status) 2) + (t (error "No such opcode: %s" (dns-get 'opcode spec)))) + -3) + (lsh (if (dns-get 'authoritative-p spec) 1 0) -2) + (lsh (if (dns-get 'truncated-p spec) 1 0) -1) + (lsh (if (dns-get 'recursion-desired-p spec) 1 0) 0))) + (dns-write-bytes + (cond + ((eq (dns-get 'response-code spec) 'no-error) 0) + ((eq (dns-get 'response-code spec) 'format-error) 1) + ((eq (dns-get 'response-code spec) 'server-failure) 2) + ((eq (dns-get 'response-code spec) 'name-error) 3) + ((eq (dns-get 'response-code spec) 'not-implemented) 4) + ((eq (dns-get 'response-code spec) 'refused) 5) + (t 0))) + (dns-write-bytes (length (dns-get 'queries spec)) 2) + (dns-write-bytes (length (dns-get 'answers spec)) 2) + (dns-write-bytes (length (dns-get 'authorities spec)) 2) + (dns-write-bytes (length (dns-get 'additionals spec)) 2) + (dolist (query (dns-get 'queries spec)) + (dns-write-name (car query)) + (dns-write-bytes (cadr (assq (or (dns-get 'type query) 'A) + dns-query-types)) 2) + (dns-write-bytes (cadr (assq (or (dns-get 'class query) 'IN) + dns-classes)) 2)) + (dolist (slot '(answers authorities additionals)) + (dolist (resource (dns-get slot spec)) + (dns-write-name (car resource)) + (dns-write-bytes (cadr (assq (dns-get 'type resource) dns-query-types)) + 2) + (dns-write-bytes (cadr (assq (dns-get 'class resource) dns-classes)) + 2) + (dns-write-bytes (dns-get 'ttl resource) 4) + (dns-write-bytes (length (dns-get 'data resource)) 2) + (insert (dns-get 'data resource)))) + (when tcp-p + (goto-char (point-min)) + (dns-write-bytes (buffer-size) 2)) + (buffer-string))) + +(defun dns-read (packet) + (let (default-enable-multibyte-characters) + (with-temp-buffer + (let ((spec nil) + queries answers authorities additionals) + (insert packet) + (goto-char (point-min)) + (push (list 'id (dns-read-bytes 2)) spec) + (let ((byte (dns-read-bytes 1))) + (push (list 'response-p (if (zerop (logand byte (lsh 1 7))) nil t)) + spec) + (let ((opcode (logand byte (lsh 7 3)))) + (push (list 'opcode + (cond ((eq opcode 0) 'query) + ((eq opcode 1) 'inverse-query) + ((eq opcode 2) 'status))) + spec)) + (push (list 'authoritative-p (if (zerop (logand byte (lsh 1 2))) + nil t)) spec) + (push (list 'truncated-p (if (zerop (logand byte (lsh 1 2))) nil t)) + spec) + (push (list 'recursion-desired-p + (if (zerop (logand byte (lsh 1 0))) nil t)) spec)) + (let ((rc (logand (dns-read-bytes 1) 15))) + (push (list 'response-code + (cond + ((eq rc 0) 'no-error) + ((eq rc 1) 'format-error) + ((eq rc 2) 'server-failure) + ((eq rc 3) 'name-error) + ((eq rc 4) 'not-implemented) + ((eq rc 5) 'refused))) + spec)) + (setq queries (dns-read-bytes 2)) + (setq answers (dns-read-bytes 2)) + (setq authorities (dns-read-bytes 2)) + (setq additionals (dns-read-bytes 2)) + (let ((qs nil)) + (dotimes (i queries) + (push (list (dns-read-name) + (list 'type (dns-inverse-get (dns-read-bytes 2) + dns-query-types)) + (list 'class (dns-inverse-get (dns-read-bytes 2) + dns-classes))) + qs)) + (push (list 'queries qs) spec)) + (dolist (slot '(answers authorities additionals)) + (let ((qs nil) + type) + (dotimes (i (symbol-value slot)) + (push (list (dns-read-name) + (list 'type + (setq type (dns-inverse-get (dns-read-bytes 2) + dns-query-types))) + (list 'class (dns-inverse-get (dns-read-bytes 2) + dns-classes)) + (list 'ttl (dns-read-bytes 4)) + (let ((length (dns-read-bytes 2))) + (list 'data + (dns-read-type + (buffer-substring + (point) + (progn (forward-char length) (point))) + type)))) + qs)) + (push (list slot qs) spec))) + (nreverse spec))))) + +(defun dns-read-int32 () + ;; Full 32 bit Integers can't be handled by Emacs. If we use + ;; floats, it works. + (format "%.0f" (+ (* (dns-read-bytes 1) 16777216.0) + (dns-read-bytes 3)))) + +(defun dns-read-type (string type) + (let ((buffer (current-buffer)) + (point (point))) + (prog1 + (let (default-enable-multibyte-characters) + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (cond + ((eq type 'A) + (let ((bytes nil)) + (dotimes (i 4) + (push (dns-read-bytes 1) bytes)) + (mapconcat 'number-to-string (nreverse bytes) "."))) + ((eq type 'AAAA) + (let (hextets) + (dotimes (i 8) + (push (dns-read-bytes 2) hextets)) + (mapconcat (lambda (n) (format "%x" n)) + (nreverse hextets) ":"))) + ((eq type 'SOA) + (list (list 'mname (dns-read-name buffer)) + (list 'rname (dns-read-name buffer)) + (list 'serial (dns-read-int32)) + (list 'refresh (dns-read-int32)) + (list 'retry (dns-read-int32)) + (list 'expire (dns-read-int32)) + (list 'minimum (dns-read-int32)))) + ((eq type 'SRV) + (list (list 'priority (dns-read-bytes 2)) + (list 'weight (dns-read-bytes 2)) + (list 'port (dns-read-bytes 2)) + (list 'target (dns-read-name buffer)))) + ((eq type 'MX) + (cons (dns-read-bytes 2) (dns-read-name buffer))) + ((or (eq type 'CNAME) (eq type 'NS) (eq type 'PTR)) + (dns-read-string-name string buffer)) + (t string)))) + (goto-char point)))) + +(defun dns-parse-resolv-conf () + (when (file-exists-p "/etc/resolv.conf") + (with-temp-buffer + (insert-file-contents "/etc/resolv.conf") + (goto-char (point-min)) + (while (re-search-forward "^nameserver[\t ]+\\([^ \t\n]+\\)" nil t) + (push (match-string 1) dns-servers)) + (setq dns-servers (nreverse dns-servers))))) + +(defun dns-read-txt (string) + (if (> (length string) 1) + (substring string 1) + string)) + +(defun dns-get-txt-answer (answers) + (let ((result "") + (do-next nil)) + (dolist (answer answers) + (dolist (elem answer) + (when (consp elem) + (cond + ((eq (car elem) 'type) + (setq do-next (eq (cadr elem) 'TXT))) + ((eq (car elem) 'data) + (when do-next + (setq result (concat result (dns-read-txt (cadr elem)))))))))) + result)) + +;;; Interface functions. +(defmacro dns-make-network-process (server) + (if (featurep 'xemacs) + `(let ((coding-system-for-read 'binary) + (coding-system-for-write 'binary)) + (open-network-stream "dns" (current-buffer) + ,server "domain" 'udp)) + `(let ((server ,server) + (coding-system-for-read 'binary) + (coding-system-for-write 'binary)) + (if (fboundp 'make-network-process) + (make-network-process + :name "dns" + :coding 'binary + :buffer (current-buffer) + :host server + :service "domain" + :type 'datagram) + ;; Older versions of Emacs doesn't have + ;; `make-network-process', so we fall back on opening a TCP + ;; connection to the DNS server. + (open-network-stream "dns" (current-buffer) server "domain"))))) + +(defvar dns-cache (make-vector 4096 0)) + +(defun query-dns-cached (name &optional type fullp reversep) + (let* ((key (format "%s:%s:%s:%s" name type fullp reversep)) + (sym (intern-soft key dns-cache))) + (if (and sym + (boundp sym)) + (symbol-value sym) + (let ((result (query-dns name type fullp reversep))) + (set (intern key dns-cache) result) + result)))) + +(defun query-dns (name &optional type fullp reversep) + "Query a DNS server for NAME of TYPE. +If FULLP, return the entire record returned. +If REVERSEP, look up an IP address." + (setq type (or type 'A)) + (unless dns-servers + (dns-parse-resolv-conf)) + + (when reversep + (setq name (concat + (mapconcat 'identity (nreverse (split-string name "\\.")) ".") + ".in-addr.arpa") + type 'PTR)) + + (if (not dns-servers) + (message "No DNS server configuration found") + (let (default-enable-multibyte-characters) + (with-temp-buffer + (let ((process (condition-case () + (dns-make-network-process (car dns-servers)) + (error + (message + "dns: Got an error while trying to talk to %s" + (car dns-servers)) + nil))) + (tcp-p (and (not (fboundp 'make-network-process)) + (not (featurep 'xemacs)))) + (step 100) + (times (* dns-timeout 1000)) + (id (random 65000))) + (when process + (process-send-string + process + (dns-write `((id ,id) + (opcode query) + (queries ((,name (type ,type)))) + (recursion-desired-p t)) + tcp-p)) + (while (and (zerop (buffer-size)) + (> times 0)) + (sit-for (/ step 1000.0)) + (accept-process-output process 0 step) + (setq times (- times step))) + (condition-case nil + (delete-process process) + (error nil)) + (when (and tcp-p + (>= (buffer-size) 2)) + (goto-char (point-min)) + (delete-region (point) (+ (point) 2))) + (when (and (>= (buffer-size) 2) + ;; We had a time-out. + (> times 0)) + (let ((result (dns-read (buffer-string)))) + (if fullp + result + (let ((answer (car (dns-get 'answers result)))) + (when (eq type (dns-get 'type answer)) + (if (eq type 'TXT) + (dns-get-txt-answer (dns-get 'answers result)) + (dns-get 'data answer))))))))))))) + +(provide 'dns) + +;;; arch-tag: d0edd0c4-4cce-4538-ae92-06c3356ee80a +;;; dns.el ends here diff --git a/lisp/net/eudc-bob.el b/lisp/net/eudc-bob.el index 9cedc989c83..0ba4d14ce02 100644 --- a/lisp/net/eudc-bob.el +++ b/lisp/net/eudc-bob.el @@ -149,7 +149,7 @@ display a button." "Toggle inline display of an image." (interactive) (when (eudc-bob-can-display-inline-images) - (cond (eudc-xemacs-p + (cond ((featurep 'xemacs) (let ((overlays (append (overlays-at (1- (point))) (overlays-at (point)))) overlay glyph) @@ -266,7 +266,7 @@ display a button." (interactive "@e") (run-hooks 'activate-menubar-hook) (eudc-jump-to-event event) - (if eudc-xemacs-p + (if (featurep 'xemacs) (progn (run-hooks 'activate-popup-menu-hook) (popup-menu (eudc-bob-menu))) @@ -282,7 +282,7 @@ display a button." (let ((map (make-sparse-keymap))) (define-key map "s" 'eudc-bob-save-object) (define-key map "!" 'eudc-bob-pipe-object-to-external-program) - (define-key map (if eudc-xemacs-p + (define-key map (if (featurep 'xemacs) [button3] [down-mouse-3]) 'eudc-bob-popup-menu) map)) @@ -295,7 +295,7 @@ display a button." (setq eudc-bob-sound-keymap (let ((map (make-sparse-keymap))) (define-key map [return] 'eudc-bob-play-sound-at-point) - (define-key map (if eudc-xemacs-p + (define-key map (if (featurep 'xemacs) [button2] [down-mouse-2]) 'eudc-bob-play-sound-at-mouse) map)) @@ -303,7 +303,7 @@ display a button." (setq eudc-bob-url-keymap (let ((map (make-sparse-keymap))) (define-key map [return] 'browse-url-at-point) - (define-key map (if eudc-xemacs-p + (define-key map (if (featurep 'xemacs) [button2] [down-mouse-2]) 'browse-url-at-mouse) map)) @@ -311,7 +311,7 @@ display a button." (setq eudc-bob-mail-keymap (let ((map (make-sparse-keymap))) (define-key map [return] 'goto-address-at-point) - (define-key map (if eudc-xemacs-p + (define-key map (if (featurep 'xemacs) [button2] [down-mouse-2]) 'goto-address-at-mouse) map)) @@ -319,20 +319,19 @@ display a button." (set-keymap-parent eudc-bob-image-keymap eudc-bob-generic-keymap) (set-keymap-parent eudc-bob-sound-keymap eudc-bob-generic-keymap) -(if eudc-emacs-p - (progn - (easy-menu-define eudc-bob-generic-menu - eudc-bob-generic-keymap - "" - eudc-bob-generic-menu) - (easy-menu-define eudc-bob-image-menu - eudc-bob-image-keymap - "" - eudc-bob-image-menu) - (easy-menu-define eudc-bob-sound-menu - eudc-bob-sound-keymap - "" - eudc-bob-sound-menu))) +(when (not (featurep 'xemacs)) + (easy-menu-define eudc-bob-generic-menu + eudc-bob-generic-keymap + "" + eudc-bob-generic-menu) + (easy-menu-define eudc-bob-image-menu + eudc-bob-image-keymap + "" + eudc-bob-image-menu) + (easy-menu-define eudc-bob-sound-menu + eudc-bob-sound-keymap + "" + eudc-bob-sound-menu)) ;;;###autoload (defun eudc-display-generic-binary (data) diff --git a/lisp/net/eudc-export.el b/lisp/net/eudc-export.el index 8bc4987b457..146e4d19626 100644 --- a/lisp/net/eudc-export.el +++ b/lisp/net/eudc-export.el @@ -158,6 +158,11 @@ LOCATION is used as the address location for bbdb." (or state "") zip))) +;; External. +(declare-function bbdb-parse-phone-number "ext:bbdb-com" + (string &optional number-type)) +(declare-function bbdb-string-trim "ext:bbdb" (string)) + (defun eudc-bbdbify-phone (phone location) "Parse PHONE into a vector compatible with BBDB. PHONE is either a string supposedly containing a phone number or diff --git a/lisp/net/eudc-hotlist.el b/lisp/net/eudc-hotlist.el index 72c7ed54a34..72be4c49e4f 100644 --- a/lisp/net/eudc-hotlist.el +++ b/lisp/net/eudc-hotlist.el @@ -69,10 +69,10 @@ These are the special commands of this mode: (switch-to-buffer (get-buffer-create "*EUDC Servers*")) (setq buffer-read-only nil) (erase-buffer) - (mapcar (function - (lambda (entry) - (setq proto-col (max (length (car entry)) proto-col)))) - eudc-server-hotlist) + (mapc (function + (lambda (entry) + (setq proto-col (max (length (car entry)) proto-col)))) + eudc-server-hotlist) (setq proto-col (+ 3 proto-col)) (setq gap (make-string (- proto-col 6) ?\ )) (insert " EUDC Servers\n" @@ -82,7 +82,7 @@ These are the special commands of this mode: "------" gap "--------\n" "\n") (setq eudc-hotlist-list-beginning (point)) - (mapcar '(lambda (entry) + (mapc '(lambda (entry) (insert (car entry)) (indent-to proto-col) (insert (symbol-name (cdr entry)) "\n")) @@ -190,11 +190,11 @@ These are the special commands of this mode: ["Save and Quit" eudc-hotlist-quit-edit t] ["Exit without Saving" kill-this-buffer t])) -(if eudc-emacs-p - (easy-menu-define eudc-hotlist-emacs-menu - eudc-hotlist-mode-map - "" - eudc-hotlist-menu)) +(when (not (featurep 'xemacs)) + (easy-menu-define eudc-hotlist-emacs-menu + eudc-hotlist-mode-map + "" + eudc-hotlist-menu)) ;;; arch-tag: 9b633ab3-6a6e-4b46-b12e-d96739a7e0e8 ;;; eudc-hotlist.el ends here diff --git a/lisp/net/eudc.el b/lisp/net/eudc.el index 834614d6cfe..7e51e1d7e89 100644 --- a/lisp/net/eudc.el +++ b/lisp/net/eudc.el @@ -66,13 +66,6 @@ ;;{{{ Internal variables and compatibility tricks -(defconst eudc-xemacs-p (string-match "XEmacs" emacs-version)) -(defconst eudc-emacs-p (not eudc-xemacs-p)) -(defconst eudc-xemacs-mule-p (and eudc-xemacs-p - (featurep 'mule))) -(defconst eudc-emacs-mule-p (and eudc-emacs-p - (featurep 'mule))) - (defvar eudc-form-widget-list nil) (defvar eudc-mode-map nil) @@ -502,15 +495,15 @@ otherwise they are formatted according to `eudc-user-attribute-names-alist'." records)) ;; Display the records (setq first-record (point)) - (mapcar + (mapc (function (lambda (record) (setq beg (point)) ;; Map over the record fields to print the attribute/value pairs - (mapcar (function - (lambda (field) - (eudc-print-record-field field width))) - record) + (mapc (function + (lambda (field) + (eudc-print-record-field field width))) + record) ;; Store the record internal format in some convenient place (overlay-put (make-overlay beg (point)) 'eudc-record @@ -540,13 +533,13 @@ otherwise they are formatted according to `eudc-user-attribute-names-alist'." (if (not (and (boundp 'eudc-form-widget-list) eudc-form-widget-list)) (error "Not in a directory query form buffer") - (mapcar (function - (lambda (wid-field) - (setq value (widget-value (cdr wid-field))) - (if (not (string= value "")) - (setq query-alist (cons (cons (car wid-field) value) - query-alist))))) - eudc-form-widget-list) + (mapc (function + (lambda (wid-field) + (setq value (widget-value (cdr wid-field))) + (if (not (string= value "")) + (setq query-alist (cons (cons (car wid-field) value) + query-alist))))) + eudc-form-widget-list) (kill-buffer (current-buffer)) (eudc-display-records (eudc-query query-alist) eudc-use-raw-directory-names)))) @@ -565,15 +558,15 @@ otherwise they are formatted according to `eudc-user-attribute-names-alist'." (if (null (eudc-cdar rec)) (list record) ; No duplicate attrs in this record - (mapcar (function - (lambda (field) - (if (listp (cdr field)) - (setq duplicates (cons field duplicates)) - (setq unique (cons field unique))))) - record) + (mapc (function + (lambda (field) + (if (listp (cdr field)) + (setq duplicates (cons field duplicates)) + (setq unique (cons field unique))))) + record) (setq result (list unique)) ;; Map over the record fields that have multiple values - (mapcar + (mapc (function (lambda (field) (let ((method (if (consp eudc-duplicate-attribute-handling-method) @@ -641,7 +634,7 @@ Each copy is added a new field containing one of the values of FIELD." (while values (setcdr values (delete (car values) (cdr values))) (setq values (cdr values))) - (mapcar + (mapc (function (lambda (value) (let ((result-list (copy-sequence records))) @@ -670,7 +663,7 @@ These are the special commands of EUDC mode: (setq major-mode 'eudc-mode) (setq mode-name "EUDC") (use-local-map eudc-mode-map) - (if eudc-emacs-p + (if (not (featurep 'xemacs)) (easy-menu-define eudc-emacs-menu eudc-mode-map "" (eudc-menu)) (setq mode-popup-menu (eudc-menu))) (run-mode-hooks 'eudc-mode-hook)) @@ -974,11 +967,11 @@ queries the server for the existing fields and displays a corresponding form." (capitalize (symbol-name field))))) fields))) ;; Loop over prompt strings to find the longest one - (mapcar (function - (lambda (prompt) - (if (> (length prompt) width) - (setq width (length prompt))))) - prompts) + (mapc (function + (lambda (prompt) + (if (> (length prompt) width) + (setq width (length prompt))))) + prompts) ;; Insert the first widget out of the mapcar to leave the cursor ;; in the first field (widget-insert "\n\n" (format (concat "%" (int-to-string width) "s: ") (car prompts))) @@ -988,15 +981,15 @@ queries the server for the existing fields and displays a corresponding form." eudc-form-widget-list)) (setq fields (cdr fields)) (setq prompts (cdr prompts)) - (mapcar (function - (lambda (field) - (widget-insert "\n\n" (format (concat "%" (int-to-string width) "s: ") (car prompts))) - (setq widget (widget-create 'editable-field - :size 15)) - (setq eudc-form-widget-list (cons (cons field widget) - eudc-form-widget-list)) - (setq prompts (cdr prompts)))) - fields) + (mapc (function + (lambda (field) + (widget-insert "\n\n" (format (concat "%" (int-to-string width) "s: ") (car prompts))) + (setq widget (widget-create 'editable-field + :size 15)) + (setq eudc-form-widget-list (cons (cons field widget) + eudc-form-widget-list)) + (setq prompts (cdr prompts)))) + fields) (widget-insert "\n\n") (widget-create 'push-button :notify (lambda (&rest ignore) @@ -1186,9 +1179,9 @@ queries the server for the existing fields and displays a corresponding form." (defun eudc-install-menu () (cond - ((and eudc-xemacs-p (featurep 'menubar)) + ((and (featurep 'xemacs) (featurep 'menubar)) (add-submenu '("Tools") (eudc-menu))) - (eudc-emacs-p + ((not (featurep 'xemacs)) (cond ((fboundp 'easy-menu-create-menu) (define-key @@ -1236,7 +1229,7 @@ This does nothing except loading eudc by autoload side-effect." nil) ;;;###autoload -(cond ((not (string-match "XEmacs" emacs-version)) +(cond ((not (featurep 'xemacs)) (defvar eudc-tools-menu (make-sparse-keymap "Directory Search")) (fset 'eudc-tools-menu (symbol-value 'eudc-tools-menu)) (define-key eudc-tools-menu [phone] @@ -1267,7 +1260,7 @@ This does nothing except loading eudc by autoload side-effect." ["Get Email" eudc-get-email t] ["Get Phone" eudc-get-phone t]))) (if (not (featurep 'eudc-autoloads)) - (if eudc-xemacs-p + (if (featurep 'xemacs) (if (and (featurep 'menubar) (not (featurep 'infodock))) (add-submenu '("Tools") menu)) diff --git a/lisp/net/eudcb-bbdb.el b/lisp/net/eudcb-bbdb.el index 4b743a59ec4..23332e6a273 100644 --- a/lisp/net/eudcb-bbdb.el +++ b/lisp/net/eudcb-bbdb.el @@ -75,7 +75,7 @@ "Return RECORD if it matches `eudc-bbdb-current-query', nil otherwise." (catch 'unmatch (progn - (mapcar + (mapc (function (lambda (condition) (let ((attr (car condition)) @@ -103,6 +103,19 @@ eudc-bbdb-current-query) record))) +;; External. +(declare-function bbdb-phone-location "ext:bbdb" t) ; via bbdb-defstruct +(declare-function bbdb-phone-string "ext:bbdb" (phone)) +(declare-function bbdb-record-phones "ext:bbdb" t) ; via bbdb-defstruct +(declare-function bbdb-address-streets "ext:bbdb" t) ; via bbdb-defstruct +(declare-function bbdb-address-city "ext:bbdb" t) ; via bbdb-defstruct +(declare-function bbdb-address-state "ext:bbdb" t) ; via bbdb-defstruct +(declare-function bbdb-address-zip "ext:bbdb" t) ; via bbdb-defstruct +(declare-function bbdb-address-location "ext:bbdb" t) ; via bbdb-defstruct +(declare-function bbdb-record-addresses "ext:bbdb" t) ; via bbdb-defstruct +(declare-function bbdb-records "ext:bbdb" + (&optional dont-check-disk already-in-db-buffer)) + (defun eudc-bbdb-extract-phones (record) (mapcar (function (lambda (phone) @@ -116,25 +129,24 @@ (defun eudc-bbdb-extract-addresses (record) (let (s c val) - (mapcar (function - (lambda (address) - (setq val (concat (unless (= 0 (length (setq s (bbdb-address-street1 address)))) - (concat s "\n")) - (unless (= 0 (length (setq s (bbdb-address-street2 address)))) - (concat s "\n")) - (unless (= 0 (length (setq s (bbdb-address-street3 address)))) - (concat s "\n")) - (progn - (setq c (bbdb-address-city address)) - (setq s (bbdb-address-state address)) - (if (and (> (length c) 0) (> (length s) 0)) - (concat c ", " s " ") - (concat c " "))) - (bbdb-address-zip-string address))) - (if eudc-bbdb-use-locations-as-attribute-names - (cons (intern (bbdb-address-location address)) val) - (cons 'addresses (concat (bbdb-address-location address) "\n" val))))) - (bbdb-record-addresses record)))) + (mapcar (lambda (address) + (setq c (bbdb-address-streets address)) + (dotimes (n 3) + (unless (zerop (length (setq s (nth n c)))) + (setq val (concat val s "\n")))) + (setq c (bbdb-address-city address) + s (bbdb-address-state address)) + (setq val (concat val + (if (and (> (length c) 0) (> (length s) 0)) + (concat c ", " s) + c) + " " + (bbdb-address-zip address))) + (if eudc-bbdb-use-locations-as-attribute-names + (cons (intern (bbdb-address-location address)) val) + (cons 'addresses (concat (bbdb-address-location address) + "\n" val)))) + (bbdb-record-addresses record)))) (defun eudc-bbdb-format-record-as-result (record) "Format the BBDB RECORD as a EUDC query result record. @@ -197,22 +209,22 @@ RETURN-ATTRS is a list of attributes to return, defaulting to (if (car query-attrs) (setq records (eval `(bbdb-search ,(quote records) ,@bbdb-attrs)))) (setq query-attrs (cdr query-attrs))) - (mapcar (function - (lambda (record) - (setq filtered (eudc-filter-duplicate-attributes record)) - ;; If there were duplicate attributes reverse the order of the - ;; record so the unique attributes appear first - (if (> (length filtered) 1) - (setq filtered (mapcar (function - (lambda (rec) - (reverse rec))) - filtered))) - (setq result (append result filtered)))) - (delq nil - (mapcar 'eudc-bbdb-format-record-as-result - (delq nil - (mapcar 'eudc-bbdb-filter-non-matching-record - records))))) + (mapc (function + (lambda (record) + (setq filtered (eudc-filter-duplicate-attributes record)) + ;; If there were duplicate attributes reverse the order of the + ;; record so the unique attributes appear first + (if (> (length filtered) 1) + (setq filtered (mapcar (function + (lambda (rec) + (reverse rec))) + filtered))) + (setq result (append result filtered)))) + (delq nil + (mapcar 'eudc-bbdb-format-record-as-result + (delq nil + (mapcar 'eudc-bbdb-filter-non-matching-record + records))))) result)) ;;}}} diff --git a/lisp/net/eudcb-ldap.el b/lisp/net/eudcb-ldap.el index 7c2e952d39b..e2d393f711a 100644 --- a/lisp/net/eudcb-ldap.el +++ b/lisp/net/eudcb-ldap.el @@ -130,7 +130,7 @@ RETURN-ATTRS is a list of attributes to return, defaulting to (setq result (eudc-filter-partial-records result return-attrs))) ;; Apply eudc-duplicate-attribute-handling-method (if (not (eq 'list eudc-duplicate-attribute-handling-method)) - (mapcar + (mapc (function (lambda (record) (setq final-result (append (eudc-filter-duplicate-attributes record) diff --git a/lisp/net/eudcb-ph.el b/lisp/net/eudcb-ph.el index db0746282b2..184fdefafb0 100644 --- a/lisp/net/eudcb-ph.el +++ b/lisp/net/eudcb-ph.el @@ -179,7 +179,7 @@ SERVER is either a string naming the server or a list (NAME PORT)." (set-buffer eudc-ph-process-buffer) (erase-buffer) (setq eudc-ph-read-point (point)) - (and eudc-xemacs-mule-p + (and (featurep 'xemacs) (featurep 'mule) (set-buffer-file-coding-system 'binary t))) (setq process (open-network-stream "ph" eudc-ph-process-buffer host port)) (if (null process) diff --git a/lisp/net/hmac-def.el b/lisp/net/hmac-def.el new file mode 100644 index 00000000000..bfff7282adf --- /dev/null +++ b/lisp/net/hmac-def.el @@ -0,0 +1,86 @@ +;;; hmac-def.el --- A macro for defining HMAC functions. + +;; Copyright (C) 1999, 2001, 2007 Free Software Foundation, Inc. + +;; Author: Shuhei KOBAYASHI <shuhei@aqua.ocn.ne.jp> +;; Keywords: HMAC, RFC 2104 + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This program is implemented from RFC 2104, +;; "HMAC: Keyed-Hashing for Message Authentication". + +;;; Code: + +(defmacro define-hmac-function (name H B L &optional bit) + "Define a function NAME(TEXT KEY) which computes HMAC with function H. + +HMAC function is H(KEY XOR opad, H(KEY XOR ipad, TEXT)): + +H is a cryptographic hash function, such as SHA1 and MD5, which takes +a string and return a digest of it (in binary form). +B is a byte-length of a block size of H. (B=64 for both SHA1 and MD5.) +L is a byte-length of hash outputs. (L=16 for MD5, L=20 for SHA1.) +If BIT is non-nil, truncate output to specified bits." + `(defun ,name (text key) + ,(concat "Compute " + (upcase (symbol-name name)) + " over TEXT with KEY.") + (let ((key-xor-ipad (make-string ,B ?\x36)) + (key-xor-opad (make-string ,B ?\x5C)) + (len (length key)) + (pos 0)) + (unwind-protect + (progn + ;; if `key' is longer than the block size, apply hash function + ;; to `key' and use the result as a real `key'. + (if (> len ,B) + (setq key (,H key) + len ,L)) + (while (< pos len) + (aset key-xor-ipad pos (logxor (aref key pos) ?\x36)) + (aset key-xor-opad pos (logxor (aref key pos) ?\x5C)) + (setq pos (1+ pos))) + (setq key-xor-ipad (unwind-protect + (concat key-xor-ipad text) + (fillarray key-xor-ipad 0)) + key-xor-ipad (unwind-protect + (,H key-xor-ipad) + (fillarray key-xor-ipad 0)) + key-xor-opad (unwind-protect + (concat key-xor-opad key-xor-ipad) + (fillarray key-xor-opad 0)) + key-xor-opad (unwind-protect + (,H key-xor-opad) + (fillarray key-xor-opad 0))) + ;; now `key-xor-opad' contains + ;; H(KEY XOR opad, H(KEY XOR ipad, TEXT)). + ,(if (and bit (< (/ bit 8) L)) + `(substring key-xor-opad 0 ,(/ bit 8)) + ;; return a copy of `key-xor-opad'. + `(concat key-xor-opad))) + ;; cleanup. + (fillarray key-xor-ipad 0) + (fillarray key-xor-opad 0))))) + +(provide 'hmac-def) + +;;; arch-tag: 645adcef-b835-4900-a10a-11f636c982b9 +;;; hmac-def.el ends here diff --git a/lisp/net/hmac-md5.el b/lisp/net/hmac-md5.el new file mode 100644 index 00000000000..186708446f0 --- /dev/null +++ b/lisp/net/hmac-md5.el @@ -0,0 +1,85 @@ +;;; hmac-md5.el --- Compute HMAC-MD5. + +;; Copyright (C) 1999, 2001, 2007 Free Software Foundation, Inc. + +;; Author: Shuhei KOBAYASHI <shuhei@aqua.ocn.ne.jp> +;; Keywords: HMAC, RFC 2104, HMAC-MD5, MD5, KEYED-MD5, CRAM-MD5 + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; Test cases from RFC 2202, "Test Cases for HMAC-MD5 and HMAC-SHA-1". +;; +;; (encode-hex-string (hmac-md5 "Hi There" (make-string 16 ?\x0b))) +;; => "9294727a3638bb1c13f48ef8158bfc9d" +;; +;; (encode-hex-string (hmac-md5 "what do ya want for nothing?" "Jefe")) +;; => "750c783e6ab0b503eaa86e310a5db738" +;; +;; (encode-hex-string (hmac-md5 (make-string 50 ?\xdd) (make-string 16 ?\xaa))) +;; => "56be34521d144c88dbb8c733f0e8b3f6" +;; +;; (encode-hex-string +;; (hmac-md5 +;; (make-string 50 ?\xcd) +;; (decode-hex-string "0102030405060708090a0b0c0d0e0f10111213141516171819"))) +;; => "697eaf0aca3a3aea3a75164746ffaa79" +;; +;; (encode-hex-string +;; (hmac-md5 "Test With Truncation" (make-string 16 ?\x0c))) +;; => "56461ef2342edc00f9bab995690efd4c" +;; +;; (encode-hex-string +;; (hmac-md5-96 "Test With Truncation" (make-string 16 ?\x0c))) +;; => "56461ef2342edc00f9bab995" +;; +;; (encode-hex-string +;; (hmac-md5 +;; "Test Using Larger Than Block-Size Key - Hash Key First" +;; (make-string 80 ?\xaa))) +;; => "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd" +;; +;; (encode-hex-string +;; (hmac-md5 +;; "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data" +;; (make-string 80 ?\xaa))) +;; => "6f630fad67cda0ee1fb1f562db3aa53e" + +;;; Code: + +(eval-when-compile (require 'hmac-def)) +(require 'hex-util) ; (decode-hex-string STRING) +(require 'md5) ; expects (md5 STRING) + +(defun md5-binary (string) + "Return the MD5 of STRING in binary form." + (if (condition-case nil + ;; `md5' of v21 takes 4th arg CODING (and 5th arg NOERROR). + (md5 "" nil nil 'binary) ; => "d41d8cd98f00b204e9800998ecf8427e" + (wrong-number-of-arguments nil)) + (decode-hex-string (md5 string nil nil 'binary)) + (decode-hex-string (md5 string)))) + +(define-hmac-function hmac-md5 md5-binary 64 16) ; => (hmac-md5 TEXT KEY) +(define-hmac-function hmac-md5-96 md5-binary 64 16 96) + +(provide 'hmac-md5) + +;;; arch-tag: 0ab3f4f6-3d4b-4167-a9fa-635b7fed7f27 +;;; hmac-md5.el ends here diff --git a/lisp/net/imap.el b/lisp/net/imap.el new file mode 100644 index 00000000000..0ee4de6fee8 --- /dev/null +++ b/lisp/net/imap.el @@ -0,0 +1,2982 @@ +;;; imap.el --- imap library + +;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, +;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc. + +;; Author: Simon Josefsson <jas@pdc.kth.se> +;; Keywords: mail + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; imap.el is a elisp library providing an interface for talking to +;; IMAP servers. +;; +;; imap.el is roughly divided in two parts, one that parses IMAP +;; responses from the server and storing data into buffer-local +;; variables, and one for utility functions which send commands to +;; server, waits for an answer, and return information. The latter +;; part is layered on top of the previous. +;; +;; The imap.el API consist of the following functions, other functions +;; in this file should not be called directly and the result of doing +;; so are at best undefined. +;; +;; Global commands: +;; +;; imap-open, imap-opened, imap-authenticate, imap-close, +;; imap-capability, imap-namespace, imap-error-text +;; +;; Mailbox commands: +;; +;; imap-mailbox-get, imap-mailbox-map, imap-current-mailbox, +;; imap-current-mailbox-p, imap-search, imap-mailbox-select, +;; imap-mailbox-examine, imap-mailbox-unselect, imap-mailbox-expunge +;; imap-mailbox-close, imap-mailbox-create, imap-mailbox-delete +;; imap-mailbox-rename, imap-mailbox-lsub, imap-mailbox-list +;; imap-mailbox-subscribe, imap-mailbox-unsubscribe, imap-mailbox-status +;; imap-mailbox-acl-get, imap-mailbox-acl-set, imap-mailbox-acl-delete +;; +;; Message commands: +;; +;; imap-fetch-asynch, imap-fetch, +;; imap-current-message, imap-list-to-message-set, +;; imap-message-get, imap-message-map +;; imap-message-envelope-date, imap-message-envelope-subject, +;; imap-message-envelope-from, imap-message-envelope-sender, +;; imap-message-envelope-reply-to, imap-message-envelope-to, +;; imap-message-envelope-cc, imap-message-envelope-bcc +;; imap-message-envelope-in-reply-to, imap-message-envelope-message-id +;; imap-message-body, imap-message-flag-permanent-p +;; imap-message-flags-set, imap-message-flags-del +;; imap-message-flags-add, imap-message-copyuid +;; imap-message-copy, imap-message-appenduid +;; imap-message-append, imap-envelope-from +;; imap-body-lines +;; +;; It is my hope that these commands should be pretty self +;; explanatory for someone that know IMAP. All functions have +;; additional documentation on how to invoke them. +;; +;; imap.el support RFC1730/2060/RFC3501 (IMAP4/IMAP4rev1), implemented +;; IMAP extensions are RFC2195 (CRAM-MD5), RFC2086 (ACL), RFC2342 +;; (NAMESPACE), RFC2359 (UIDPLUS), the IMAP-part of RFC2595 (STARTTLS, +;; LOGINDISABLED) (with use of external library starttls.el and +;; program starttls), and the GSSAPI / kerberos V4 sections of RFC1731 +;; (with use of external program `imtest'), RFC2971 (ID). It also +;; takes advantage of the UNSELECT extension in Cyrus IMAPD. +;; +;; Without the work of John McClary Prevost and Jim Radford this library +;; would not have seen the light of day. Many thanks. +;; +;; This is a transcript of short interactive session for demonstration +;; purposes. +;; +;; (imap-open "my.mail.server") +;; => " *imap* my.mail.server:0" +;; +;; The rest are invoked with current buffer as the buffer returned by +;; `imap-open'. It is possible to do all without this, but it would +;; look ugly here since `buffer' is always the last argument for all +;; imap.el API functions. +;; +;; (imap-authenticate "myusername" "mypassword") +;; => auth +;; +;; (imap-mailbox-lsub "*") +;; => ("INBOX.sentmail" "INBOX.private" "INBOX.draft" "INBOX.spam") +;; +;; (imap-mailbox-list "INBOX.n%") +;; => ("INBOX.namedroppers" "INBOX.nnimap" "INBOX.ntbugtraq") +;; +;; (imap-mailbox-select "INBOX.nnimap") +;; => "INBOX.nnimap" +;; +;; (imap-mailbox-get 'exists) +;; => 166 +;; +;; (imap-mailbox-get 'uidvalidity) +;; => "908992622" +;; +;; (imap-search "FLAGGED SINCE 18-DEC-98") +;; => (235 236) +;; +;; (imap-fetch 235 "RFC822.PEEK" 'RFC822) +;; => "X-Sieve: cmu-sieve 1.3^M\nX-Username: <jas@pdc.kth.se>^M\r...." +;; +;; Todo: +;; +;; o Parse UIDs as strings? We need to overcome the 28 bit limit somehow. +;; o Don't use `read' at all (important places already fixed) +;; o Accept list of articles instead of message set string in most +;; imap-message-* functions. +;; o Send strings as literal if they contain, e.g., ". +;; +;; Revision history: +;; +;; - 19991218 added starttls/digest-md5 patch, +;; by Daiki Ueno <ueno@ueda.info.waseda.ac.jp> +;; NB! you need SLIM for starttls.el and digest-md5.el +;; - 19991023 commited to pgnus +;; + +;;; Code: + +(eval-when-compile (require 'cl)) +(eval-and-compile + (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))) + (autoload 'starttls-open-stream "starttls") + (autoload 'starttls-negotiate "starttls") + (autoload 'sasl-find-mechanism "sasl") + (autoload 'digest-md5-parse-digest-challenge "digest-md5") + (autoload 'digest-md5-digest-response "digest-md5") + (autoload 'digest-md5-digest-uri "digest-md5") + (autoload 'digest-md5-challenge "digest-md5") + (autoload 'rfc2104-hash "rfc2104") + (autoload 'utf7-encode "utf7") + (autoload 'utf7-decode "utf7") + (autoload 'format-spec "format-spec") + (autoload 'format-spec-make "format-spec") + (autoload 'open-tls-stream "tls")) + +;; User variables. + +(defgroup imap nil + "Low-level IMAP issues." + :version "21.1" + :group 'mail) + +(defcustom imap-kerberos4-program '("imtest -m kerberos_v4 -u %l -p %p %s" + "imtest -kp %s %p") + "List of strings containing commands for Kerberos 4 authentication. +%s is replaced with server hostname, %p with port to connect to, and +%l with the value of `imap-default-user'. The program should accept +IMAP commands on stdin and return responses to stdout. Each entry in +the list is tried until a successful connection is made." + :group 'imap + :type '(repeat string)) + +(defcustom imap-gssapi-program (list + (concat "gsasl %s %p " + "--mechanism GSSAPI " + "--authentication-id %l") + "imtest -m gssapi -u %l -p %p %s") + "List of strings containing commands for GSSAPI (krb5) authentication. +%s is replaced with server hostname, %p with port to connect to, and +%l with the value of `imap-default-user'. The program should accept +IMAP commands on stdin and return responses to stdout. Each entry in +the list is tried until a successful connection is made." + :group 'imap + :type '(repeat string)) + +(defcustom imap-ssl-program '("openssl s_client -quiet -ssl3 -connect %s:%p" + "openssl s_client -quiet -ssl2 -connect %s:%p" + "s_client -quiet -ssl3 -connect %s:%p" + "s_client -quiet -ssl2 -connect %s:%p") + "A string, or list of strings, containing commands for SSL connections. +Within a string, %s is replaced with the server address and %p with +port number on server. The program should accept IMAP commands on +stdin and return responses to stdout. Each entry in the list is tried +until a successful connection is made." + :group 'imap + :type '(choice string + (repeat string))) + +(defcustom imap-shell-program '("ssh %s imapd" + "rsh %s imapd" + "ssh %g ssh %s imapd" + "rsh %g rsh %s imapd") + "A list of strings, containing commands for IMAP connection. +Within a string, %s is replaced with the server address, %p with port +number on server, %g with `imap-shell-host', and %l with +`imap-default-user'. The program should read IMAP commands from stdin +and write IMAP response to stdout. Each entry in the list is tried +until a successful connection is made." + :group 'imap + :type '(repeat string)) + +(defcustom imap-process-connection-type nil + "*Value for `process-connection-type' to use for Kerberos4, GSSAPI and SSL. +The `process-connection-type' variable control type of device +used to communicate with subprocesses. Values are nil to use a +pipe, or t or `pty' to use a pty. The value has no effect if the +system has no ptys or if all ptys are busy: then a pipe is used +in any case. The value takes effect when a IMAP server is +opened, changing it after that has no effect." + :version "22.1" + :group 'imap + :type 'boolean) + +(defcustom imap-use-utf7 t + "If non-nil, do utf7 encoding/decoding of mailbox names. +Since the UTF7 decoding currently only decodes into ISO-8859-1 +characters, you may disable this decoding if you need to access UTF7 +encoded mailboxes which doesn't translate into ISO-8859-1." + :group 'imap + :type 'boolean) + +(defcustom imap-log nil + "If non-nil, a imap session trace is placed in *imap-log* buffer. +Note that username, passwords and other privacy sensitive +information (such as e-mail) may be stored in the *imap-log* +buffer. It is not written to disk, however. Do not enable this +variable unless you are comfortable with that." + :group 'imap + :type 'boolean) + +(defcustom imap-debug nil + "If non-nil, random debug spews are placed in *imap-debug* buffer. +Note that username, passwords and other privacy sensitive +information (such as e-mail) may be stored in the *imap-debug* +buffer. It is not written to disk, however. Do not enable this +variable unless you are comfortable with that." + :group 'imap + :type 'boolean) + +(defcustom imap-shell-host "gateway" + "Hostname of rlogin proxy." + :group 'imap + :type 'string) + +(defcustom imap-default-user (user-login-name) + "Default username to use." + :group 'imap + :type 'string) + +(defcustom imap-read-timeout (if (string-match + "windows-nt\\|os/2\\|emx\\|cygwin" + (symbol-name system-type)) + 1.0 + 0.1) + "*How long to wait between checking for the end of output. +Shorter values mean quicker response, but is more CPU intensive." + :type 'number + :group 'imap) + +(defcustom imap-store-password nil + "If non-nil, store session password without promting." + :group 'imap + :type 'boolean) + +;; Various variables. + +(defvar imap-fetch-data-hook nil + "Hooks called after receiving each FETCH response.") + +(defvar imap-streams '(gssapi kerberos4 starttls tls ssl network shell) + "Priority of streams to consider when opening connection to server.") + +(defvar imap-stream-alist + '((gssapi imap-gssapi-stream-p imap-gssapi-open) + (kerberos4 imap-kerberos4-stream-p imap-kerberos4-open) + (tls imap-tls-p imap-tls-open) + (ssl imap-ssl-p imap-ssl-open) + (network imap-network-p imap-network-open) + (shell imap-shell-p imap-shell-open) + (starttls imap-starttls-p imap-starttls-open)) + "Definition of network streams. + +\(NAME CHECK OPEN) + +NAME names the stream, CHECK is a function returning non-nil if the +server support the stream and OPEN is a function for opening the +stream.") + +(defvar imap-authenticators '(gssapi + kerberos4 + digest-md5 + cram-md5 + ;;sasl + login + anonymous) + "Priority of authenticators to consider when authenticating to server.") + +(defvar imap-authenticator-alist + '((gssapi imap-gssapi-auth-p imap-gssapi-auth) + (kerberos4 imap-kerberos4-auth-p imap-kerberos4-auth) + (sasl imap-sasl-auth-p imap-sasl-auth) + (cram-md5 imap-cram-md5-p imap-cram-md5-auth) + (login imap-login-p imap-login-auth) + (anonymous imap-anonymous-p imap-anonymous-auth) + (digest-md5 imap-digest-md5-p imap-digest-md5-auth)) + "Definition of authenticators. + +\(NAME CHECK AUTHENTICATE) + +NAME names the authenticator. CHECK is a function returning non-nil if +the server support the authenticator and AUTHENTICATE is a function +for doing the actual authentication.") + +(defvar imap-error nil + "Error codes from the last command.") + +(defvar imap-logout-timeout nil + "Close server immediately if it can't logout in this number of seconds. +If it is nil, never close server until logout completes. Normally, +the value of this variable will be bound to a certain value to which +an application program that uses this module specifies on a per-server +basis.") + +;; Internal constants. Change these and die. + +(defconst imap-default-port 143) +(defconst imap-default-ssl-port 993) +(defconst imap-default-tls-port 993) +(defconst imap-default-stream 'network) +(defconst imap-coding-system-for-read 'binary) +(defconst imap-coding-system-for-write 'binary) +(defconst imap-local-variables '(imap-server + imap-port + imap-client-eol + imap-server-eol + imap-auth + imap-stream + imap-username + imap-password + imap-current-mailbox + imap-current-target-mailbox + imap-message-data + imap-capability + imap-id + imap-namespace + imap-state + imap-reached-tag + imap-failed-tags + imap-tag + imap-process + imap-calculate-literal-size-first + imap-mailbox-data)) +(defconst imap-log-buffer "*imap-log*") +(defconst imap-debug-buffer "*imap-debug*") + +;; Internal variables. + +(defvar imap-stream nil) +(defvar imap-auth nil) +(defvar imap-server nil) +(defvar imap-port nil) +(defvar imap-username nil) +(defvar imap-password nil) +(defvar imap-calculate-literal-size-first nil) +(defvar imap-state 'closed + "IMAP state. +Valid states are `closed', `initial', `nonauth', `auth', `selected' +and `examine'.") + +(defvar imap-server-eol "\r\n" + "The EOL string sent from the server.") + +(defvar imap-client-eol "\r\n" + "The EOL string we send to the server.") + +(defvar imap-current-mailbox nil + "Current mailbox name.") + +(defvar imap-current-target-mailbox nil + "Current target mailbox for COPY and APPEND commands.") + +(defvar imap-mailbox-data nil + "Obarray with mailbox data.") + +(defvar imap-mailbox-prime 997 + "Length of imap-mailbox-data.") + +(defvar imap-current-message nil + "Current message number.") + +(defvar imap-message-data nil + "Obarray with message data.") + +(defvar imap-message-prime 997 + "Length of imap-message-data.") + +(defvar imap-capability nil + "Capability for server.") + +(defvar imap-id nil + "Identity of server. +See RFC 2971.") + +(defvar imap-namespace nil + "Namespace for current server.") + +(defvar imap-reached-tag 0 + "Lower limit on command tags that have been parsed.") + +(defvar imap-failed-tags nil + "Alist of tags that failed. +Each element is a list with four elements; tag (a integer), response +state (a symbol, `OK', `NO' or `BAD'), response code (a string), and +human readable response text (a string).") + +(defvar imap-tag 0 + "Command tag number.") + +(defvar imap-process nil + "Process.") + +(defvar imap-continuation nil + "Non-nil indicates that the server emitted a continuation request. +The actual value is really the text on the continuation line.") + +(defvar imap-callbacks nil + "List of response tags and callbacks, on the form `(number . function)'. +The function should take two arguments, the first the IMAP tag and the +second the status (OK, NO, BAD etc) of the command.") + + +;; Utility functions: + +(defun imap-remassoc (key alist) + "Delete by side effect any elements of LIST whose car is `equal' to KEY. +The modified LIST is returned. If the first member +of LIST has a car that is `equal' to KEY, there is no way to remove it +by side effect; therefore, write `(setq foo (remassoc key foo))' to be +sure of changing the value of `foo'." + (when alist + (if (equal key (caar alist)) + (cdr alist) + (setcdr alist (imap-remassoc key (cdr alist))) + alist))) + +(defsubst imap-disable-multibyte () + "Enable multibyte in the current buffer." + (when (fboundp 'set-buffer-multibyte) + (set-buffer-multibyte nil))) + +(defsubst imap-utf7-encode (string) + (if imap-use-utf7 + (and string + (condition-case () + (utf7-encode string t) + (error (message + "imap: Could not UTF7 encode `%s', using it unencoded..." + string) + string))) + string)) + +(defsubst imap-utf7-decode (string) + (if imap-use-utf7 + (and string + (condition-case () + (utf7-decode string t) + (error (message + "imap: Could not UTF7 decode `%s', using it undecoded..." + string) + string))) + string)) + +(defsubst imap-ok-p (status) + (if (eq status 'OK) + t + (setq imap-error status) + nil)) + +(defun imap-error-text (&optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (nth 3 (car imap-failed-tags)))) + + +;; Server functions; stream stuff: + +(defun imap-kerberos4-stream-p (buffer) + (imap-capability 'AUTH=KERBEROS_V4 buffer)) + +(defun imap-kerberos4-open (name buffer server port) + (let ((cmds imap-kerberos4-program) + cmd done) + (while (and (not done) (setq cmd (pop cmds))) + (message "Opening Kerberos 4 IMAP connection with `%s'..." cmd) + (erase-buffer) + (let* ((port (or port imap-default-port)) + (coding-system-for-read imap-coding-system-for-read) + (coding-system-for-write imap-coding-system-for-write) + (process-connection-type imap-process-connection-type) + (process (start-process + name buffer shell-file-name shell-command-switch + (format-spec + cmd + (format-spec-make + ?s server + ?p (number-to-string port) + ?l imap-default-user)))) + response) + (when process + (with-current-buffer buffer + (setq imap-client-eol "\n" + imap-calculate-literal-size-first t) + (while (and (memq (process-status process) '(open run)) + (set-buffer buffer) ;; XXX "blue moon" nntp.el bug + (goto-char (point-min)) + ;; Athena IMTEST can output SSL verify errors + (or (while (looking-at "^verify error:num=") + (forward-line)) + t) + (or (while (looking-at "^TLS connection established") + (forward-line)) + t) + ;; cyrus 1.6.x (13? < x <= 22) queries capabilities + (or (while (looking-at "^C:") + (forward-line)) + t) + ;; cyrus 1.6 imtest print "S: " before server greeting + (or (not (looking-at "S: ")) + (forward-char 3) + t) + (not (and (imap-parse-greeting) + ;; success in imtest < 1.6: + (or (re-search-forward + "^__\\(.*\\)__\n" nil t) + ;; success in imtest 1.6: + (re-search-forward + "^\\(Authenticat.*\\)" nil t)) + (setq response (match-string 1))))) + (accept-process-output process 1) + (sit-for 1)) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert-buffer-substring buffer))) + (erase-buffer) + (message "Opening Kerberos 4 IMAP connection with `%s'...%s" cmd + (if response (concat "done, " response) "failed")) + (if (and response (let ((case-fold-search nil)) + (not (string-match "failed" response)))) + (setq done process) + (if (memq (process-status process) '(open run)) + (imap-logout)) + (delete-process process) + nil))))) + done)) + +(defun imap-gssapi-stream-p (buffer) + (imap-capability 'AUTH=GSSAPI buffer)) + +(defun imap-gssapi-open (name buffer server port) + (let ((cmds imap-gssapi-program) + cmd done) + (while (and (not done) (setq cmd (pop cmds))) + (message "Opening GSSAPI IMAP connection with `%s'..." cmd) + (erase-buffer) + (let* ((port (or port imap-default-port)) + (coding-system-for-read imap-coding-system-for-read) + (coding-system-for-write imap-coding-system-for-write) + (process-connection-type imap-process-connection-type) + (process (start-process + name buffer shell-file-name shell-command-switch + (format-spec + cmd + (format-spec-make + ?s server + ?p (number-to-string port) + ?l imap-default-user)))) + response) + (when process + (with-current-buffer buffer + (setq imap-client-eol "\n" + imap-calculate-literal-size-first t) + (while (and (memq (process-status process) '(open run)) + (set-buffer buffer) ;; XXX "blue moon" nntp.el bug + (goto-char (point-min)) + ;; Athena IMTEST can output SSL verify errors + (or (while (looking-at "^verify error:num=") + (forward-line)) + t) + (or (while (looking-at "^TLS connection established") + (forward-line)) + t) + ;; cyrus 1.6.x (13? < x <= 22) queries capabilities + (or (while (looking-at "^C:") + (forward-line)) + t) + ;; cyrus 1.6 imtest print "S: " before server greeting + (or (not (looking-at "S: ")) + (forward-char 3) + t) + ;; GNU SASL may print 'Trying ...' first. + (or (not (looking-at "Trying ")) + (forward-line) + t) + (not (and (imap-parse-greeting) + ;; success in imtest 1.6: + (re-search-forward + (concat "^\\(\\(Authenticat.*\\)\\|\\(" + "Client authentication " + "finished.*\\)\\)") + nil t) + (setq response (match-string 1))))) + (accept-process-output process 1) + (sit-for 1)) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert-buffer-substring buffer))) + (erase-buffer) + (message "GSSAPI IMAP connection: %s" (or response "failed")) + (if (and response (let ((case-fold-search nil)) + (not (string-match "failed" response)))) + (setq done process) + (if (memq (process-status process) '(open run)) + (imap-logout)) + (delete-process process) + nil))))) + done)) + +(defun imap-ssl-p (buffer) + nil) + +(defun imap-ssl-open (name buffer server port) + "Open a SSL connection to server." + (let ((cmds (if (listp imap-ssl-program) imap-ssl-program + (list imap-ssl-program))) + cmd done) + (while (and (not done) (setq cmd (pop cmds))) + (message "imap: Opening SSL connection with `%s'..." cmd) + (erase-buffer) + (let* ((port (or port imap-default-ssl-port)) + (coding-system-for-read imap-coding-system-for-read) + (coding-system-for-write imap-coding-system-for-write) + (process-connection-type imap-process-connection-type) + (set-process-query-on-exit-flag + (if (fboundp 'set-process-query-on-exit-flag) + 'set-process-query-on-exit-flag + 'process-kill-without-query)) + process) + (when (progn + (setq process (start-process + name buffer shell-file-name + shell-command-switch + (format-spec cmd + (format-spec-make + ?s server + ?p (number-to-string port))))) + (funcall set-process-query-on-exit-flag process nil) + process) + (with-current-buffer buffer + (goto-char (point-min)) + (while (and (memq (process-status process) '(open run)) + (set-buffer buffer) ;; XXX "blue moon" nntp.el bug + (goto-char (point-max)) + (forward-line -1) + (not (imap-parse-greeting))) + (accept-process-output process 1) + (sit-for 1)) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert-buffer-substring buffer))) + (erase-buffer) + (when (memq (process-status process) '(open run)) + (setq done process)))))) + (if done + (progn + (message "imap: Opening SSL connection with `%s'...done" cmd) + done) + (message "imap: Opening SSL connection with `%s'...failed" cmd) + nil))) + +(defun imap-tls-p (buffer) + nil) + +(defun imap-tls-open (name buffer server port) + (let* ((port (or port imap-default-tls-port)) + (coding-system-for-read imap-coding-system-for-read) + (coding-system-for-write imap-coding-system-for-write) + (process (open-tls-stream name buffer server port))) + (when process + (while (and (memq (process-status process) '(open run)) + (set-buffer buffer) ;; XXX "blue moon" nntp.el bug + (goto-char (point-max)) + (forward-line -1) + (not (imap-parse-greeting))) + (accept-process-output process 1) + (sit-for 1)) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert-buffer-substring buffer))) + (when (memq (process-status process) '(open run)) + process)))) + +(defun imap-network-p (buffer) + t) + +(defun imap-network-open (name buffer server port) + (let* ((port (or port imap-default-port)) + (coding-system-for-read imap-coding-system-for-read) + (coding-system-for-write imap-coding-system-for-write) + (process (open-network-stream name buffer server port))) + (when process + (while (and (memq (process-status process) '(open run)) + (set-buffer buffer) ;; XXX "blue moon" nntp.el bug + (goto-char (point-min)) + (not (imap-parse-greeting))) + (accept-process-output process 1) + (sit-for 1)) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert-buffer-substring buffer))) + (when (memq (process-status process) '(open run)) + process)))) + +(defun imap-shell-p (buffer) + nil) + +(defun imap-shell-open (name buffer server port) + (let ((cmds (if (listp imap-shell-program) imap-shell-program + (list imap-shell-program))) + cmd done) + (while (and (not done) (setq cmd (pop cmds))) + (message "imap: Opening IMAP connection with `%s'..." cmd) + (setq imap-client-eol "\n") + (let* ((port (or port imap-default-port)) + (coding-system-for-read imap-coding-system-for-read) + (coding-system-for-write imap-coding-system-for-write) + (process (start-process + name buffer shell-file-name shell-command-switch + (format-spec + cmd + (format-spec-make + ?s server + ?g imap-shell-host + ?p (number-to-string port) + ?l imap-default-user))))) + (when process + (while (and (memq (process-status process) '(open run)) + (set-buffer buffer) ;; XXX "blue moon" nntp.el bug + (goto-char (point-max)) + (forward-line -1) + (not (imap-parse-greeting))) + (accept-process-output process 1) + (sit-for 1)) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert-buffer-substring buffer))) + (erase-buffer) + (when (memq (process-status process) '(open run)) + (setq done process))))) + (if done + (progn + (message "imap: Opening IMAP connection with `%s'...done" cmd) + done) + (message "imap: Opening IMAP connection with `%s'...failed" cmd) + nil))) + +(defun imap-starttls-p (buffer) + (imap-capability 'STARTTLS buffer)) + +(defun imap-starttls-open (name buffer server port) + (let* ((port (or port imap-default-port)) + (coding-system-for-read imap-coding-system-for-read) + (coding-system-for-write imap-coding-system-for-write) + (process (starttls-open-stream name buffer server port)) + done tls-info) + (message "imap: Connecting with STARTTLS...") + (when process + (while (and (memq (process-status process) '(open run)) + (set-buffer buffer) ;; XXX "blue moon" nntp.el bug + (goto-char (point-max)) + (forward-line -1) + (not (imap-parse-greeting))) + (accept-process-output process 1) + (sit-for 1)) + (imap-send-command "STARTTLS") + (while (and (memq (process-status process) '(open run)) + (set-buffer buffer) ;; XXX "blue moon" nntp.el bug + (goto-char (point-max)) + (forward-line -1) + (not (re-search-forward "[0-9]+ OK.*\r?\n" nil t))) + (accept-process-output process 1) + (sit-for 1)) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (buffer-disable-undo) + (goto-char (point-max)) + (insert-buffer-substring buffer))) + (when (and (setq tls-info (starttls-negotiate process)) + (memq (process-status process) '(open run))) + (setq done process))) + (if (stringp tls-info) + (message "imap: STARTTLS info: %s" tls-info)) + (message "imap: Connecting with STARTTLS...%s" (if done "done" "failed")) + done)) + +;; Server functions; authenticator stuff: + +(defun imap-interactive-login (buffer loginfunc) + "Login to server in BUFFER. +LOGINFUNC is passed a username and a password, it should return t if +it where successful authenticating itself to the server, nil otherwise. +Returns t if login was successful, nil otherwise." + (with-current-buffer buffer + (make-local-variable 'imap-username) + (make-local-variable 'imap-password) + (let (user passwd ret) + ;; (condition-case () + (while (or (not user) (not passwd)) + (setq user (or imap-username + (read-from-minibuffer + (concat "IMAP username for " imap-server + " (using stream `" (symbol-name imap-stream) + "'): ") + (or user imap-default-user)))) + (setq passwd (or imap-password + (read-passwd + (concat "IMAP password for " user "@" + imap-server " (using authenticator `" + (symbol-name imap-auth) "'): ")))) + (when (and user passwd) + (if (funcall loginfunc user passwd) + (progn + (setq ret t + imap-username user) + (when (and (not imap-password) + (or imap-store-password + (y-or-n-p "Store password for this session? "))) + (setq imap-password passwd))) + (message "Login failed...") + (setq passwd nil) + (setq imap-password nil) + (sit-for 1)))) + ;; (quit (with-current-buffer buffer + ;; (setq user nil + ;; passwd nil))) + ;; (error (with-current-buffer buffer + ;; (setq user nil + ;; passwd nil)))) + ret))) + +(defun imap-gssapi-auth-p (buffer) + (eq imap-stream 'gssapi)) + +(defun imap-gssapi-auth (buffer) + (message "imap: Authenticating using GSSAPI...%s" + (if (eq imap-stream 'gssapi) "done" "failed")) + (eq imap-stream 'gssapi)) + +(defun imap-kerberos4-auth-p (buffer) + (and (imap-capability 'AUTH=KERBEROS_V4 buffer) + (eq imap-stream 'kerberos4))) + +(defun imap-kerberos4-auth (buffer) + (message "imap: Authenticating using Kerberos 4...%s" + (if (eq imap-stream 'kerberos4) "done" "failed")) + (eq imap-stream 'kerberos4)) + +(defun imap-cram-md5-p (buffer) + (imap-capability 'AUTH=CRAM-MD5 buffer)) + +(defun imap-cram-md5-auth (buffer) + "Login to server using the AUTH CRAM-MD5 method." + (message "imap: Authenticating using CRAM-MD5...") + (let ((done (imap-interactive-login + buffer + (lambda (user passwd) + (imap-ok-p + (imap-send-command-wait + (list + "AUTHENTICATE CRAM-MD5" + (lambda (challenge) + (let* ((decoded (base64-decode-string challenge)) + (hash (rfc2104-hash 'md5 64 16 passwd decoded)) + (response (concat user " " hash)) + (encoded (base64-encode-string response))) + encoded))))))))) + (if done + (message "imap: Authenticating using CRAM-MD5...done") + (message "imap: Authenticating using CRAM-MD5...failed")))) + +(defun imap-login-p (buffer) + (and (not (imap-capability 'LOGINDISABLED buffer)) + (not (imap-capability 'X-LOGIN-CMD-DISABLED buffer)))) + +(defun imap-quote-specials (string) + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (while (re-search-forward "[\\\"]" nil t) + (forward-char -1) + (insert "\\") + (forward-char 1)) + (buffer-string))) + +(defun imap-login-auth (buffer) + "Login to server using the LOGIN command." + (message "imap: Plaintext authentication...") + (imap-interactive-login buffer + (lambda (user passwd) + (imap-ok-p (imap-send-command-wait + (concat "LOGIN \"" + (imap-quote-specials user) + "\" \"" + (imap-quote-specials passwd) + "\"")))))) + +(defun imap-anonymous-p (buffer) + t) + +(defun imap-anonymous-auth (buffer) + (message "imap: Logging in anonymously...") + (with-current-buffer buffer + (imap-ok-p (imap-send-command-wait + (concat "LOGIN anonymous \"" (concat (user-login-name) "@" + (system-name)) "\""))))) + +;;; Compiler directives. + +(defvar imap-sasl-client) +(defvar imap-sasl-step) + +(defun imap-sasl-make-mechanisms (buffer) + (let ((mecs '())) + (mapc (lambda (sym) + (let ((name (symbol-name sym))) + (if (and (> (length name) 5) + (string-equal "AUTH=" (substring name 0 5 ))) + (setq mecs (cons (substring name 5) mecs))))) + (imap-capability nil buffer)) + mecs)) + +(declare-function sasl-find-mechanism "sasl" (mechanism)) +(declare-function sasl-mechanism-name "sasl" (mechanism)) +(declare-function sasl-make-client "sasl" (mechanism name service server)) +(declare-function sasl-next-step "sasl" (client step)) +(declare-function sasl-step-data "sasl" (step)) +(declare-function sasl-step-set-data "sasl" (step data)) + +(defun imap-sasl-auth-p (buffer) + (and (condition-case () + (require 'sasl) + (error nil)) + (sasl-find-mechanism (imap-sasl-make-mechanisms buffer)))) + +(defun imap-sasl-auth (buffer) + "Login to server using the SASL method." + (message "imap: Authenticating using SASL...") + (with-current-buffer buffer + (make-local-variable 'imap-username) + (make-local-variable 'imap-sasl-client) + (make-local-variable 'imap-sasl-step) + (let ((mechanism (sasl-find-mechanism (imap-sasl-make-mechanisms buffer))) + logged user) + (while (not logged) + (setq user (or imap-username + (read-from-minibuffer + (concat "IMAP username for " imap-server " using SASL " + (sasl-mechanism-name mechanism) ": ") + (or user imap-default-user)))) + (when user + (setq imap-sasl-client (sasl-make-client mechanism user "imap2" imap-server) + imap-sasl-step (sasl-next-step imap-sasl-client nil)) + (let ((tag (imap-send-command + (if (sasl-step-data imap-sasl-step) + (format "AUTHENTICATE %s %s" + (sasl-mechanism-name mechanism) + (sasl-step-data imap-sasl-step)) + (format "AUTHENTICATE %s" (sasl-mechanism-name mechanism))) + buffer))) + (while (eq (imap-wait-for-tag tag) 'INCOMPLETE) + (sasl-step-set-data imap-sasl-step (base64-decode-string imap-continuation)) + (setq imap-continuation nil + imap-sasl-step (sasl-next-step imap-sasl-client imap-sasl-step)) + (imap-send-command-1 (if (sasl-step-data imap-sasl-step) + (base64-encode-string (sasl-step-data imap-sasl-step) t) + ""))) + (if (imap-ok-p (imap-wait-for-tag tag)) + (setq imap-username user + logged t) + (message "Login failed...") + (sit-for 1))))) + logged))) + +(defun imap-digest-md5-p (buffer) + (and (imap-capability 'AUTH=DIGEST-MD5 buffer) + (condition-case () + (require 'digest-md5) + (error nil)))) + +(defun imap-digest-md5-auth (buffer) + "Login to server using the AUTH DIGEST-MD5 method." + (message "imap: Authenticating using DIGEST-MD5...") + (imap-interactive-login + buffer + (lambda (user passwd) + (let ((tag + (imap-send-command + (list + "AUTHENTICATE DIGEST-MD5" + (lambda (challenge) + (digest-md5-parse-digest-challenge + (base64-decode-string challenge)) + (let* ((digest-uri + (digest-md5-digest-uri + "imap" (digest-md5-challenge 'realm))) + (response + (digest-md5-digest-response + user passwd digest-uri))) + (base64-encode-string response 'no-line-break)))) + ))) + (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE)) + nil + (setq imap-continuation nil) + (imap-send-command-1 "") + (imap-ok-p (imap-wait-for-tag tag))))))) + +;; Server functions: + +(defun imap-open-1 (buffer) + (with-current-buffer buffer + (erase-buffer) + (setq imap-current-mailbox nil + imap-current-message nil + imap-state 'initial + imap-process (condition-case () + (funcall (nth 2 (assq imap-stream + imap-stream-alist)) + "imap" buffer imap-server imap-port) + ((error quit) nil))) + (when imap-process + (set-process-filter imap-process 'imap-arrival-filter) + (set-process-sentinel imap-process 'imap-sentinel) + (while (and (eq imap-state 'initial) + (memq (process-status imap-process) '(open run))) + (message "Waiting for response from %s..." imap-server) + (accept-process-output imap-process 1)) + (message "Waiting for response from %s...done" imap-server) + (and (memq (process-status imap-process) '(open run)) + imap-process)))) + +(defun imap-open (server &optional port stream auth buffer) + "Open a IMAP connection to host SERVER at PORT returning a buffer. +If PORT is unspecified, a default value is used (143 except +for SSL which use 993). +STREAM indicates the stream to use, see `imap-streams' for available +streams. If nil, it choices the best stream the server is capable of. +AUTH indicates authenticator to use, see `imap-authenticators' for +available authenticators. If nil, it choices the best stream the +server is capable of. +BUFFER can be a buffer or a name of a buffer, which is created if +necessary. If nil, the buffer name is generated." + (setq buffer (or buffer (format " *imap* %s:%d" server (or port 0)))) + (with-current-buffer (get-buffer-create buffer) + (if (imap-opened buffer) + (imap-close buffer)) + (mapc 'make-local-variable imap-local-variables) + (imap-disable-multibyte) + (buffer-disable-undo) + (setq imap-server (or server imap-server)) + (setq imap-port (or port imap-port)) + (setq imap-auth (or auth imap-auth)) + (setq imap-stream (or stream imap-stream)) + (message "imap: Connecting to %s..." imap-server) + (if (null (let ((imap-stream (or imap-stream imap-default-stream))) + (imap-open-1 buffer))) + (progn + (message "imap: Connecting to %s...failed" imap-server) + nil) + (when (null imap-stream) + ;; Need to choose stream. + (let ((streams imap-streams)) + (while (setq stream (pop streams)) + ;; OK to use this stream? + (when (funcall (nth 1 (assq stream imap-stream-alist)) buffer) + ;; Stream changed? + (if (not (eq imap-default-stream stream)) + (with-current-buffer (get-buffer-create + (generate-new-buffer-name " *temp*")) + (mapc 'make-local-variable imap-local-variables) + (imap-disable-multibyte) + (buffer-disable-undo) + (setq imap-server (or server imap-server)) + (setq imap-port (or port imap-port)) + (setq imap-auth (or auth imap-auth)) + (message "imap: Reconnecting with stream `%s'..." stream) + (if (null (let ((imap-stream stream)) + (imap-open-1 (current-buffer)))) + (progn + (kill-buffer (current-buffer)) + (message + "imap: Reconnecting with stream `%s'...failed" + stream)) + ;; We're done, kill the first connection + (imap-close buffer) + (let ((name (if (stringp buffer) + buffer + (buffer-name buffer)))) + (kill-buffer buffer) + (rename-buffer name)) + (message "imap: Reconnecting with stream `%s'...done" + stream) + (setq imap-stream stream) + (setq imap-capability nil) + (setq streams nil))) + ;; We're done + (message "imap: Connecting to %s...done" imap-server) + (setq imap-stream stream) + (setq imap-capability nil) + (setq streams nil)))))) + (when (imap-opened buffer) + (setq imap-mailbox-data (make-vector imap-mailbox-prime 0))) + (when imap-stream + buffer)))) + +(defun imap-opened (&optional buffer) + "Return non-nil if connection to imap server in BUFFER is open. +If BUFFER is nil then the current buffer is used." + (and (setq buffer (get-buffer (or buffer (current-buffer)))) + (buffer-live-p buffer) + (with-current-buffer buffer + (and imap-process + (memq (process-status imap-process) '(open run)))))) + +(defun imap-authenticate (&optional user passwd buffer) + "Authenticate to server in BUFFER, using current buffer if nil. +It uses the authenticator specified when opening the server. If the +authenticator requires username/passwords, they are queried from the +user and optionally stored in the buffer. If USER and/or PASSWD is +specified, the user will not be questioned and the username and/or +password is remembered in the buffer." + (with-current-buffer (or buffer (current-buffer)) + (if (not (eq imap-state 'nonauth)) + (or (eq imap-state 'auth) + (eq imap-state 'selected) + (eq imap-state 'examine)) + (make-local-variable 'imap-username) + (make-local-variable 'imap-password) + (if user (setq imap-username user)) + (if passwd (setq imap-password passwd)) + (if imap-auth + (and (funcall (nth 2 (assq imap-auth + imap-authenticator-alist)) (current-buffer)) + (setq imap-state 'auth)) + ;; Choose authenticator. + (let ((auths imap-authenticators) + auth) + (while (setq auth (pop auths)) + ;; OK to use authenticator? + (when (funcall (nth 1 (assq auth imap-authenticator-alist)) (current-buffer)) + (message "imap: Authenticating to `%s' using `%s'..." + imap-server auth) + (setq imap-auth auth) + (if (funcall (nth 2 (assq auth imap-authenticator-alist)) (current-buffer)) + (progn + (message "imap: Authenticating to `%s' using `%s'...done" + imap-server auth) + (setq auths nil)) + (message "imap: Authenticating to `%s' using `%s'...failed" + imap-server auth))))) + imap-state)))) + +(defun imap-close (&optional buffer) + "Close connection to server in BUFFER. +If BUFFER is nil, the current buffer is used." + (with-current-buffer (or buffer (current-buffer)) + (when (imap-opened) + (condition-case nil + (imap-logout-wait) + (quit nil))) + (when (and imap-process + (memq (process-status imap-process) '(open run))) + (delete-process imap-process)) + (setq imap-current-mailbox nil + imap-current-message nil + imap-process nil) + (erase-buffer) + t)) + +(defun imap-capability (&optional identifier buffer) + "Return a list of identifiers which server in BUFFER support. +If IDENTIFIER, return non-nil if it's among the servers capabilities. +If BUFFER is nil, the current buffer is assumed." + (with-current-buffer (or buffer (current-buffer)) + (unless imap-capability + (unless (imap-ok-p (imap-send-command-wait "CAPABILITY")) + (setq imap-capability '(IMAP2)))) + (if identifier + (memq (intern (upcase (symbol-name identifier))) imap-capability) + imap-capability))) + +(defun imap-id (&optional list-of-values buffer) + "Identify client to server in BUFFER, and return server identity. +LIST-OF-VALUES is nil, or a plist with identifier and value +strings to send to the server to identify the client. + +Return a list of identifiers which server in BUFFER support, or +nil if it doesn't support ID or returns no information. + +If BUFFER is nil, the current buffer is assumed." + (with-current-buffer (or buffer (current-buffer)) + (when (and (imap-capability 'ID) + (imap-ok-p (imap-send-command-wait + (if (null list-of-values) + "ID NIL" + (concat "ID (" (mapconcat (lambda (el) + (concat "\"" el "\"")) + list-of-values + " ") ")"))))) + imap-id))) + +(defun imap-namespace (&optional buffer) + "Return a namespace hierarchy at server in BUFFER. +If BUFFER is nil, the current buffer is assumed." + (with-current-buffer (or buffer (current-buffer)) + (unless imap-namespace + (when (imap-capability 'NAMESPACE) + (imap-send-command-wait "NAMESPACE"))) + imap-namespace)) + +(defun imap-send-command-wait (command &optional buffer) + (imap-wait-for-tag (imap-send-command command buffer) buffer)) + +(defun imap-logout (&optional buffer) + (or buffer (setq buffer (current-buffer))) + (if imap-logout-timeout + (with-timeout (imap-logout-timeout + (condition-case nil + (with-current-buffer buffer + (delete-process imap-process)) + (error))) + (imap-send-command "LOGOUT" buffer)) + (imap-send-command "LOGOUT" buffer))) + +(defun imap-logout-wait (&optional buffer) + (or buffer (setq buffer (current-buffer))) + (if imap-logout-timeout + (with-timeout (imap-logout-timeout + (condition-case nil + (with-current-buffer buffer + (delete-process imap-process)) + (error))) + (imap-send-command-wait "LOGOUT" buffer)) + (imap-send-command-wait "LOGOUT" buffer))) + + +;; Mailbox functions: + +(defun imap-mailbox-put (propname value &optional mailbox buffer) + (with-current-buffer (or buffer (current-buffer)) + (if imap-mailbox-data + (put (intern (or mailbox imap-current-mailbox) imap-mailbox-data) + propname value) + (error "Imap-mailbox-data is nil, prop %s value %s mailbox %s buffer %s" + propname value mailbox (current-buffer))) + t)) + +(defsubst imap-mailbox-get-1 (propname &optional mailbox) + (get (intern-soft (or mailbox imap-current-mailbox) imap-mailbox-data) + propname)) + +(defun imap-mailbox-get (propname &optional mailbox buffer) + (let ((mailbox (imap-utf7-encode mailbox))) + (with-current-buffer (or buffer (current-buffer)) + (imap-mailbox-get-1 propname (or mailbox imap-current-mailbox))))) + +(defun imap-mailbox-map-1 (func &optional mailbox-decoder buffer) + (with-current-buffer (or buffer (current-buffer)) + (let (result) + (mapatoms + (lambda (s) + (push (funcall func (if mailbox-decoder + (funcall mailbox-decoder (symbol-name s)) + (symbol-name s))) result)) + imap-mailbox-data) + result))) + +(defun imap-mailbox-map (func &optional buffer) + "Map a function across each mailbox in `imap-mailbox-data', returning a list. +Function should take a mailbox name (a string) as +the only argument." + (imap-mailbox-map-1 func 'imap-utf7-decode buffer)) + +(defun imap-current-mailbox (&optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (imap-utf7-decode imap-current-mailbox))) + +(defun imap-current-mailbox-p-1 (mailbox &optional examine) + (and (string= mailbox imap-current-mailbox) + (or (and examine + (eq imap-state 'examine)) + (and (not examine) + (eq imap-state 'selected))))) + +(defun imap-current-mailbox-p (mailbox &optional examine buffer) + (with-current-buffer (or buffer (current-buffer)) + (imap-current-mailbox-p-1 (imap-utf7-encode mailbox) examine))) + +(defun imap-mailbox-select-1 (mailbox &optional examine) + "Select MAILBOX on server in BUFFER. +If EXAMINE is non-nil, do a read-only select." + (if (imap-current-mailbox-p-1 mailbox examine) + imap-current-mailbox + (setq imap-current-mailbox mailbox) + (if (imap-ok-p (imap-send-command-wait + (concat (if examine "EXAMINE" "SELECT") " \"" + mailbox "\""))) + (progn + (setq imap-message-data (make-vector imap-message-prime 0) + imap-state (if examine 'examine 'selected)) + imap-current-mailbox) + ;; Failed SELECT/EXAMINE unselects current mailbox + (setq imap-current-mailbox nil)))) + +(defun imap-mailbox-select (mailbox &optional examine buffer) + (with-current-buffer (or buffer (current-buffer)) + (imap-utf7-decode + (imap-mailbox-select-1 (imap-utf7-encode mailbox) examine)))) + +(defun imap-mailbox-examine-1 (mailbox &optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (imap-mailbox-select-1 mailbox 'examine))) + +(defun imap-mailbox-examine (mailbox &optional buffer) + "Examine MAILBOX on server in BUFFER." + (imap-mailbox-select mailbox 'examine buffer)) + +(defun imap-mailbox-unselect (&optional buffer) + "Close current folder in BUFFER, without expunging articles." + (with-current-buffer (or buffer (current-buffer)) + (when (or (eq imap-state 'auth) + (and (imap-capability 'UNSELECT) + (imap-ok-p (imap-send-command-wait "UNSELECT"))) + (and (imap-ok-p + (imap-send-command-wait (concat "EXAMINE \"" + imap-current-mailbox + "\""))) + (imap-ok-p (imap-send-command-wait "CLOSE")))) + (setq imap-current-mailbox nil + imap-message-data nil + imap-state 'auth) + t))) + +(defun imap-mailbox-expunge (&optional asynch buffer) + "Expunge articles in current folder in BUFFER. +If ASYNCH, do not wait for succesful completion of the command. +If BUFFER is nil the current buffer is assumed." + (with-current-buffer (or buffer (current-buffer)) + (when (and imap-current-mailbox (not (eq imap-state 'examine))) + (if asynch + (imap-send-command "EXPUNGE") + (imap-ok-p (imap-send-command-wait "EXPUNGE")))))) + +(defun imap-mailbox-close (&optional asynch buffer) + "Expunge articles and close current folder in BUFFER. +If ASYNCH, do not wait for succesful completion of the command. +If BUFFER is nil the current buffer is assumed." + (with-current-buffer (or buffer (current-buffer)) + (when imap-current-mailbox + (if asynch + (imap-add-callback (imap-send-command "CLOSE") + `(lambda (tag status) + (message "IMAP mailbox `%s' closed... %s" + imap-current-mailbox status) + (when (eq ,imap-current-mailbox + imap-current-mailbox) + ;; Don't wipe out data if another mailbox + ;; was selected... + (setq imap-current-mailbox nil + imap-message-data nil + imap-state 'auth)))) + (when (imap-ok-p (imap-send-command-wait "CLOSE")) + (setq imap-current-mailbox nil + imap-message-data nil + imap-state 'auth))) + t))) + +(defun imap-mailbox-create-1 (mailbox) + (imap-ok-p (imap-send-command-wait (list "CREATE \"" mailbox "\"")))) + +(defun imap-mailbox-create (mailbox &optional buffer) + "Create MAILBOX on server in BUFFER. +If BUFFER is nil the current buffer is assumed." + (with-current-buffer (or buffer (current-buffer)) + (imap-mailbox-create-1 (imap-utf7-encode mailbox)))) + +(defun imap-mailbox-delete (mailbox &optional buffer) + "Delete MAILBOX on server in BUFFER. +If BUFFER is nil the current buffer is assumed." + (let ((mailbox (imap-utf7-encode mailbox))) + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p + (imap-send-command-wait (list "DELETE \"" mailbox "\"")))))) + +(defun imap-mailbox-rename (oldname newname &optional buffer) + "Rename mailbox OLDNAME to NEWNAME on server in BUFFER. +If BUFFER is nil the current buffer is assumed." + (let ((oldname (imap-utf7-encode oldname)) + (newname (imap-utf7-encode newname))) + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p + (imap-send-command-wait (list "RENAME \"" oldname "\" " + "\"" newname "\"")))))) + +(defun imap-mailbox-lsub (&optional root reference add-delimiter buffer) + "Return a list of subscribed mailboxes on server in BUFFER. +If ROOT is non-nil, only list matching mailboxes. If ADD-DELIMITER is +non-nil, a hierarchy delimiter is added to root. REFERENCE is a +implementation-specific string that has to be passed to lsub command." + (with-current-buffer (or buffer (current-buffer)) + ;; Make sure we know the hierarchy separator for root's hierarchy + (when (and add-delimiter (null (imap-mailbox-get-1 'delimiter root))) + (imap-send-command-wait (concat "LIST \"" reference "\" \"" + (imap-utf7-encode root) "\""))) + ;; clear list data (NB not delimiter and other stuff) + (imap-mailbox-map-1 (lambda (mailbox) + (imap-mailbox-put 'lsub nil mailbox))) + (when (imap-ok-p + (imap-send-command-wait + (concat "LSUB \"" reference "\" \"" (imap-utf7-encode root) + (and add-delimiter (imap-mailbox-get-1 'delimiter root)) + "%\""))) + (let (out) + (imap-mailbox-map-1 (lambda (mailbox) + (when (imap-mailbox-get-1 'lsub mailbox) + (push (imap-utf7-decode mailbox) out)))) + (nreverse out))))) + +(defun imap-mailbox-list (root &optional reference add-delimiter buffer) + "Return a list of mailboxes matching ROOT on server in BUFFER. +If ADD-DELIMITER is non-nil, a hierarchy delimiter is added to +root. REFERENCE is a implementation-specific string that has to be +passed to list command." + (with-current-buffer (or buffer (current-buffer)) + ;; Make sure we know the hierarchy separator for root's hierarchy + (when (and add-delimiter (null (imap-mailbox-get-1 'delimiter root))) + (imap-send-command-wait (concat "LIST \"" reference "\" \"" + (imap-utf7-encode root) "\""))) + ;; clear list data (NB not delimiter and other stuff) + (imap-mailbox-map-1 (lambda (mailbox) + (imap-mailbox-put 'list nil mailbox))) + (when (imap-ok-p + (imap-send-command-wait + (concat "LIST \"" reference "\" \"" (imap-utf7-encode root) + (and add-delimiter (imap-mailbox-get-1 'delimiter root)) + "%\""))) + (let (out) + (imap-mailbox-map-1 (lambda (mailbox) + (when (imap-mailbox-get-1 'list mailbox) + (push (imap-utf7-decode mailbox) out)))) + (nreverse out))))) + +(defun imap-mailbox-subscribe (mailbox &optional buffer) + "Send the SUBSCRIBE command on the mailbox to server in BUFFER. +Returns non-nil if successful." + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p (imap-send-command-wait (concat "SUBSCRIBE \"" + (imap-utf7-encode mailbox) + "\""))))) + +(defun imap-mailbox-unsubscribe (mailbox &optional buffer) + "Send the SUBSCRIBE command on the mailbox to server in BUFFER. +Returns non-nil if successful." + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p (imap-send-command-wait (concat "UNSUBSCRIBE " + (imap-utf7-encode mailbox) + "\""))))) + +(defun imap-mailbox-status (mailbox items &optional buffer) + "Get status items ITEM in MAILBOX from server in BUFFER. +ITEMS can be a symbol or a list of symbols, valid symbols are one of +the STATUS data items -- ie 'messages, 'recent, 'uidnext, 'uidvalidity +or 'unseen. If ITEMS is a list of symbols, a list of values is +returned, if ITEMS is a symbol only its value is returned." + (with-current-buffer (or buffer (current-buffer)) + (when (imap-ok-p + (imap-send-command-wait (list "STATUS \"" + (imap-utf7-encode mailbox) + "\" " + (upcase + (format "%s" + (if (listp items) + items + (list items))))))) + (if (listp items) + (mapcar (lambda (item) + (imap-mailbox-get item mailbox)) + items) + (imap-mailbox-get items mailbox))))) + +(defun imap-mailbox-status-asynch (mailbox items &optional buffer) + "Send status item request ITEM on MAILBOX to server in BUFFER. +ITEMS can be a symbol or a list of symbols, valid symbols are one of +the STATUS data items -- ie 'messages, 'recent, 'uidnext, 'uidvalidity +or 'unseen. The IMAP command tag is returned." + (with-current-buffer (or buffer (current-buffer)) + (imap-send-command (list "STATUS \"" + (imap-utf7-encode mailbox) + "\" " + (upcase + (format "%s" + (if (listp items) + items + (list items)))))))) + +(defun imap-mailbox-acl-get (&optional mailbox buffer) + "Get ACL on mailbox from server in BUFFER." + (let ((mailbox (imap-utf7-encode mailbox))) + (with-current-buffer (or buffer (current-buffer)) + (when (imap-ok-p + (imap-send-command-wait (list "GETACL \"" + (or mailbox imap-current-mailbox) + "\""))) + (imap-mailbox-get-1 'acl (or mailbox imap-current-mailbox)))))) + +(defun imap-mailbox-acl-set (identifier rights &optional mailbox buffer) + "Change/set ACL for IDENTIFIER to RIGHTS in MAILBOX from server in BUFFER." + (let ((mailbox (imap-utf7-encode mailbox))) + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p + (imap-send-command-wait (list "SETACL \"" + (or mailbox imap-current-mailbox) + "\" " + identifier + " " + rights)))))) + +(defun imap-mailbox-acl-delete (identifier &optional mailbox buffer) + "Removes any <identifier,rights> pair for IDENTIFIER in MAILBOX from server in BUFFER." + (let ((mailbox (imap-utf7-encode mailbox))) + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p + (imap-send-command-wait (list "DELETEACL \"" + (or mailbox imap-current-mailbox) + "\" " + identifier)))))) + + +;; Message functions: + +(defun imap-current-message (&optional buffer) + (with-current-buffer (or buffer (current-buffer)) + imap-current-message)) + +(defun imap-list-to-message-set (list) + (mapconcat (lambda (item) + (number-to-string item)) + (if (listp list) + list + (list list)) + ",")) + +(defun imap-range-to-message-set (range) + (mapconcat + (lambda (item) + (if (consp item) + (format "%d:%d" + (car item) (cdr item)) + (format "%d" item))) + (if (and (listp range) (not (listp (cdr range)))) + (list range) ;; make (1 . 2) into ((1 . 2)) + range) + ",")) + +(defun imap-fetch-asynch (uids props &optional nouidfetch buffer) + (with-current-buffer (or buffer (current-buffer)) + (imap-send-command (format "%sFETCH %s %s" (if nouidfetch "" "UID ") + (if (listp uids) + (imap-list-to-message-set uids) + uids) + props)))) + +(defun imap-fetch (uids props &optional receive nouidfetch buffer) + "Fetch properties PROPS from message set UIDS from server in BUFFER. +UIDS can be a string, number or a list of numbers. If RECEIVE +is non-nil return these properties." + (with-current-buffer (or buffer (current-buffer)) + (when (imap-ok-p (imap-send-command-wait + (format "%sFETCH %s %s" (if nouidfetch "" "UID ") + (if (listp uids) + (imap-list-to-message-set uids) + uids) + props))) + (if (or (null receive) (stringp uids)) + t + (if (listp uids) + (mapcar (lambda (uid) + (if (listp receive) + (mapcar (lambda (prop) + (imap-message-get uid prop)) + receive) + (imap-message-get uid receive))) + uids) + (imap-message-get uids receive)))))) + +(defun imap-message-put (uid propname value &optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (if imap-message-data + (put (intern (number-to-string uid) imap-message-data) + propname value) + (error "Imap-message-data is nil, uid %s prop %s value %s buffer %s" + uid propname value (current-buffer))) + t)) + +(defun imap-message-get (uid propname &optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (get (intern-soft (number-to-string uid) imap-message-data) + propname))) + +(defun imap-message-map (func propname &optional buffer) + "Map a function across each mailbox in `imap-message-data', returning a list." + (with-current-buffer (or buffer (current-buffer)) + (let (result) + (mapatoms + (lambda (s) + (push (funcall func (get s 'UID) (get s propname)) result)) + imap-message-data) + result))) + +(defmacro imap-message-envelope-date (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 0))) + +(defmacro imap-message-envelope-subject (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 1))) + +(defmacro imap-message-envelope-from (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 2))) + +(defmacro imap-message-envelope-sender (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 3))) + +(defmacro imap-message-envelope-reply-to (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 4))) + +(defmacro imap-message-envelope-to (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 5))) + +(defmacro imap-message-envelope-cc (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 6))) + +(defmacro imap-message-envelope-bcc (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 7))) + +(defmacro imap-message-envelope-in-reply-to (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 8))) + +(defmacro imap-message-envelope-message-id (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (elt (imap-message-get ,uid 'ENVELOPE) 9))) + +(defmacro imap-message-body (uid &optional buffer) + `(with-current-buffer (or ,buffer (current-buffer)) + (imap-message-get ,uid 'BODY))) + +(defun imap-search (predicate &optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (imap-mailbox-put 'search 'dummy) + (when (imap-ok-p (imap-send-command-wait (concat "UID SEARCH " predicate))) + (if (eq (imap-mailbox-get-1 'search imap-current-mailbox) 'dummy) + (progn + (message "Missing SEARCH response to a SEARCH command (server not RFC compliant)...") + nil) + (imap-mailbox-get-1 'search imap-current-mailbox))))) + +(defun imap-message-flag-permanent-p (flag &optional mailbox buffer) + "Return t if FLAG can be permanently (between IMAP sessions) saved on articles, in MAILBOX on server in BUFFER." + (with-current-buffer (or buffer (current-buffer)) + (or (member "\\*" (imap-mailbox-get 'permanentflags mailbox)) + (member flag (imap-mailbox-get 'permanentflags mailbox))))) + +(defun imap-message-flags-set (articles flags &optional silent buffer) + (when (and articles flags) + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p (imap-send-command-wait + (concat "UID STORE " articles + " FLAGS" (if silent ".SILENT") " (" flags ")")))))) + +(defun imap-message-flags-del (articles flags &optional silent buffer) + (when (and articles flags) + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p (imap-send-command-wait + (concat "UID STORE " articles + " -FLAGS" (if silent ".SILENT") " (" flags ")")))))) + +(defun imap-message-flags-add (articles flags &optional silent buffer) + (when (and articles flags) + (with-current-buffer (or buffer (current-buffer)) + (imap-ok-p (imap-send-command-wait + (concat "UID STORE " articles + " +FLAGS" (if silent ".SILENT") " (" flags ")")))))) + +;; Cf. http://thread.gmane.org/gmane.emacs.gnus.general/65317/focus=65343 +;; Signal an error if we'd get an integer overflow. +;; +;; FIXME: Identify relevant calls to `string-to-number' and replace them with +;; `imap-string-to-integer'. +(defun imap-string-to-integer (string &optional base) + (let ((number (string-to-number string base))) + (if (> number most-positive-fixnum) + (error + (format "String %s cannot be converted to a lisp integer" number)) + number))) + +(defun imap-message-copyuid-1 (mailbox) + (if (imap-capability 'UIDPLUS) + (list (nth 0 (imap-mailbox-get-1 'copyuid mailbox)) + (string-to-number (nth 2 (imap-mailbox-get-1 'copyuid mailbox)))) + (let ((old-mailbox imap-current-mailbox) + (state imap-state) + (imap-message-data (make-vector 2 0))) + (when (imap-mailbox-examine-1 mailbox) + (prog1 + (and (imap-fetch "*" "UID") + (list (imap-mailbox-get-1 'uidvalidity mailbox) + (apply 'max (imap-message-map + (lambda (uid prop) uid) 'UID)))) + (if old-mailbox + (imap-mailbox-select old-mailbox (eq state 'examine)) + (imap-mailbox-unselect))))))) + +(defun imap-message-copyuid (mailbox &optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (imap-message-copyuid-1 (imap-utf7-decode mailbox)))) + +(defun imap-message-copy (articles mailbox + &optional dont-create no-copyuid buffer) + "Copy ARTICLES (a string message set) to MAILBOX on server in +BUFFER, creating mailbox if it doesn't exist. If dont-create is +non-nil, it will not create a mailbox. On success, return a list with +the UIDVALIDITY of the mailbox the article(s) was copied to as the +first element, rest of list contain the saved articles' UIDs." + (when articles + (with-current-buffer (or buffer (current-buffer)) + (let ((mailbox (imap-utf7-encode mailbox))) + (if (let ((cmd (concat "UID COPY " articles " \"" mailbox "\"")) + (imap-current-target-mailbox mailbox)) + (if (imap-ok-p (imap-send-command-wait cmd)) + t + (when (and (not dont-create) + ;; removed because of buggy Oracle server + ;; that doesn't send TRYCREATE tags (which + ;; is a MUST according to specifications): + ;;(imap-mailbox-get-1 'trycreate mailbox) + (imap-mailbox-create-1 mailbox)) + (imap-ok-p (imap-send-command-wait cmd))))) + (or no-copyuid + (imap-message-copyuid-1 mailbox))))))) + +(defun imap-message-appenduid-1 (mailbox) + (if (imap-capability 'UIDPLUS) + (imap-mailbox-get-1 'appenduid mailbox) + (let ((old-mailbox imap-current-mailbox) + (state imap-state) + (imap-message-data (make-vector 2 0))) + (when (imap-mailbox-examine-1 mailbox) + (prog1 + (and (imap-fetch "*" "UID") + (list (imap-mailbox-get-1 'uidvalidity mailbox) + (apply 'max (imap-message-map + (lambda (uid prop) uid) 'UID)))) + (if old-mailbox + (imap-mailbox-select old-mailbox (eq state 'examine)) + (imap-mailbox-unselect))))))) + +(defun imap-message-appenduid (mailbox &optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (imap-message-appenduid-1 (imap-utf7-encode mailbox)))) + +(defun imap-message-append (mailbox article &optional flags date-time buffer) + "Append ARTICLE (a buffer) to MAILBOX on server in BUFFER. +FLAGS and DATE-TIME is currently not used. Return a cons holding +uidvalidity of MAILBOX and UID the newly created article got, or nil +on failure." + (let ((mailbox (imap-utf7-encode mailbox))) + (with-current-buffer (or buffer (current-buffer)) + (and (let ((imap-current-target-mailbox mailbox)) + (imap-ok-p + (imap-send-command-wait + (list "APPEND \"" mailbox "\" " article)))) + (imap-message-appenduid-1 mailbox))))) + +(defun imap-body-lines (body) + "Return number of lines in article by looking at the mime bodystructure BODY." + (if (listp body) + (if (stringp (car body)) + (cond ((and (string= (upcase (car body)) "TEXT") + (numberp (nth 7 body))) + (nth 7 body)) + ((and (string= (upcase (car body)) "MESSAGE") + (numberp (nth 9 body))) + (nth 9 body)) + (t 0)) + (apply '+ (mapcar 'imap-body-lines body))) + 0)) + +(defun imap-envelope-from (from) + "Return a from string line." + (and from + (concat (aref from 0) + (if (aref from 0) " <") + (aref from 2) + "@" + (aref from 3) + (if (aref from 0) ">")))) + + +;; Internal functions. + +(defun imap-add-callback (tag func) + (setq imap-callbacks (append (list (cons tag func)) imap-callbacks))) + +(defun imap-send-command-1 (cmdstr) + (setq cmdstr (concat cmdstr imap-client-eol)) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert cmdstr))) + (process-send-string imap-process cmdstr)) + +(defun imap-send-command (command &optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (if (not (listp command)) (setq command (list command))) + (let ((tag (setq imap-tag (1+ imap-tag))) + cmd cmdstr) + (setq cmdstr (concat (number-to-string imap-tag) " ")) + (while (setq cmd (pop command)) + (cond ((stringp cmd) + (setq cmdstr (concat cmdstr cmd))) + ((bufferp cmd) + (let ((eol imap-client-eol) + (calcfirst imap-calculate-literal-size-first) + size) + (with-current-buffer cmd + (if calcfirst + (setq size (buffer-size))) + (when (not (equal eol "\r\n")) + ;; XXX modifies buffer! + (goto-char (point-min)) + (while (search-forward "\r\n" nil t) + (replace-match eol))) + (if (not calcfirst) + (setq size (buffer-size)))) + (setq cmdstr + (concat cmdstr (format "{%d}" size)))) + (unwind-protect + (progn + (imap-send-command-1 cmdstr) + (setq cmdstr nil) + (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE)) + (setq command nil) ;; abort command if no cont-req + (let ((process imap-process) + (stream imap-stream) + (eol imap-client-eol)) + (with-current-buffer cmd + (and imap-log + (with-current-buffer (get-buffer-create + imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert-buffer-substring cmd))) + (process-send-region process (point-min) + (point-max))) + (process-send-string process imap-client-eol)))) + (setq imap-continuation nil))) + ((functionp cmd) + (imap-send-command-1 cmdstr) + (setq cmdstr nil) + (unwind-protect + (if (not (eq (imap-wait-for-tag tag) 'INCOMPLETE)) + (setq command nil) ;; abort command if no cont-req + (setq command (cons (funcall cmd imap-continuation) + command))) + (setq imap-continuation nil))) + (t + (error "Unknown command type")))) + (if cmdstr + (imap-send-command-1 cmdstr)) + tag))) + +(defun imap-wait-for-tag (tag &optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (let (imap-have-messaged) + (while (and (null imap-continuation) + (memq (process-status imap-process) '(open run)) + (< imap-reached-tag tag)) + (let ((len (/ (point-max) 1024)) + message-log-max) + (unless (< len 10) + (setq imap-have-messaged t) + (message "imap read: %dk" len)) + (accept-process-output imap-process + (truncate imap-read-timeout) + (truncate (* (- imap-read-timeout + (truncate imap-read-timeout)) + 1000))))) + ;; A process can die _before_ we have processed everything it + ;; has to say. Moreover, this can happen in between the call to + ;; accept-process-output and the call to process-status in an + ;; iteration of the loop above. + (when (and (null imap-continuation) + (< imap-reached-tag tag)) + (accept-process-output imap-process 0 0)) + (when imap-have-messaged + (message "")) + (and (memq (process-status imap-process) '(open run)) + (or (assq tag imap-failed-tags) + (if imap-continuation + 'INCOMPLETE + 'OK)))))) + +(defun imap-sentinel (process string) + (delete-process process)) + +(defun imap-find-next-line () + "Return point at end of current line, taking into account literals. +Return nil if no complete line has arrived." + (when (re-search-forward (concat imap-server-eol "\\|{\\([0-9]+\\)}" + imap-server-eol) + nil t) + (if (match-string 1) + (if (< (point-max) (+ (point) (string-to-number (match-string 1)))) + nil + (goto-char (+ (point) (string-to-number (match-string 1)))) + (imap-find-next-line)) + (point)))) + +(defun imap-arrival-filter (proc string) + "IMAP process filter." + ;; Sometimes, we are called even though the process has died. + ;; Better abstain from doing stuff in that case. + (when (buffer-name (process-buffer proc)) + (with-current-buffer (process-buffer proc) + (goto-char (point-max)) + (insert string) + (and imap-log + (with-current-buffer (get-buffer-create imap-log-buffer) + (imap-disable-multibyte) + (buffer-disable-undo) + (goto-char (point-max)) + (insert string))) + (let (end) + (goto-char (point-min)) + (while (setq end (imap-find-next-line)) + (save-restriction + (narrow-to-region (point-min) end) + (delete-backward-char (length imap-server-eol)) + (goto-char (point-min)) + (unwind-protect + (cond ((eq imap-state 'initial) + (imap-parse-greeting)) + ((or (eq imap-state 'auth) + (eq imap-state 'nonauth) + (eq imap-state 'selected) + (eq imap-state 'examine)) + (imap-parse-response)) + (t + (message "Unknown state %s in arrival filter" + imap-state))) + (delete-region (point-min) (point-max))))))))) + + +;; Imap parser. + +(defsubst imap-forward () + (or (eobp) (forward-char))) + +;; number = 1*DIGIT +;; ; Unsigned 32-bit integer +;; ; (0 <= n < 4,294,967,296) + +(defsubst imap-parse-number () + (when (looking-at "[0-9]+") + (prog1 + (string-to-number (match-string 0)) + (goto-char (match-end 0))))) + +;; literal = "{" number "}" CRLF *CHAR8 +;; ; Number represents the number of CHAR8s + +(defsubst imap-parse-literal () + (when (looking-at "{\\([0-9]+\\)}\r\n") + (let ((pos (match-end 0)) + (len (string-to-number (match-string 1)))) + (if (< (point-max) (+ pos len)) + nil + (goto-char (+ pos len)) + (buffer-substring pos (+ pos len)))))) + +;; string = quoted / literal +;; +;; quoted = DQUOTE *QUOTED-CHAR DQUOTE +;; +;; QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / +;; "\" quoted-specials +;; +;; quoted-specials = DQUOTE / "\" +;; +;; TEXT-CHAR = <any CHAR except CR and LF> + +(defsubst imap-parse-string () + (cond ((eq (char-after) ?\") + (forward-char 1) + (let ((p (point)) (name "")) + (skip-chars-forward "^\"\\\\") + (setq name (buffer-substring p (point))) + (while (eq (char-after) ?\\) + (setq p (1+ (point))) + (forward-char 2) + (skip-chars-forward "^\"\\\\") + (setq name (concat name (buffer-substring p (point))))) + (forward-char 1) + name)) + ((eq (char-after) ?{) + (imap-parse-literal)))) + +;; nil = "NIL" + +(defsubst imap-parse-nil () + (if (looking-at "NIL") + (goto-char (match-end 0)))) + +;; nstring = string / nil + +(defsubst imap-parse-nstring () + (or (imap-parse-string) + (and (imap-parse-nil) + nil))) + +;; astring = atom / string +;; +;; atom = 1*ATOM-CHAR +;; +;; ATOM-CHAR = <any CHAR except atom-specials> +;; +;; atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / +;; quoted-specials +;; +;; list-wildcards = "%" / "*" +;; +;; quoted-specials = DQUOTE / "\" + +(defsubst imap-parse-astring () + (or (imap-parse-string) + (buffer-substring (point) + (if (re-search-forward "[(){ \r\n%*\"\\]" nil t) + (goto-char (1- (match-end 0))) + (end-of-line) + (point))))) + +;; address = "(" addr-name SP addr-adl SP addr-mailbox SP +;; addr-host ")" +;; +;; addr-adl = nstring +;; ; Holds route from [RFC-822] route-addr if +;; ; non-nil +;; +;; addr-host = nstring +;; ; nil indicates [RFC-822] group syntax. +;; ; Otherwise, holds [RFC-822] domain name +;; +;; addr-mailbox = nstring +;; ; nil indicates end of [RFC-822] group; if +;; ; non-nil and addr-host is nil, holds +;; ; [RFC-822] group name. +;; ; Otherwise, holds [RFC-822] local-part +;; ; after removing [RFC-822] quoting +;; +;; addr-name = nstring +;; ; If non-nil, holds phrase from [RFC-822] +;; ; mailbox after removing [RFC-822] quoting +;; + +(defsubst imap-parse-address () + (let (address) + (when (eq (char-after) ?\() + (imap-forward) + (setq address (vector (prog1 (imap-parse-nstring) + (imap-forward)) + (prog1 (imap-parse-nstring) + (imap-forward)) + (prog1 (imap-parse-nstring) + (imap-forward)) + (imap-parse-nstring))) + (when (eq (char-after) ?\)) + (imap-forward) + address)))) + +;; address-list = "(" 1*address ")" / nil +;; +;; nil = "NIL" + +(defsubst imap-parse-address-list () + (if (eq (char-after) ?\() + (let (address addresses) + (imap-forward) + (while (and (not (eq (char-after) ?\))) + ;; next line for MS Exchange bug + (progn (and (eq (char-after) ? ) (imap-forward)) t) + (setq address (imap-parse-address))) + (setq addresses (cons address addresses))) + (when (eq (char-after) ?\)) + (imap-forward) + (nreverse addresses))) + ;; With assert, the code might not be eval'd. + ;; (assert (imap-parse-nil) t "In imap-parse-address-list") + (imap-parse-nil))) + +;; mailbox = "INBOX" / astring +;; ; INBOX is case-insensitive. All case variants of +;; ; INBOX (e.g. "iNbOx") MUST be interpreted as INBOX +;; ; not as an astring. An astring which consists of +;; ; the case-insensitive sequence "I" "N" "B" "O" "X" +;; ; is considered to be INBOX and not an astring. +;; ; Refer to section 5.1 for further +;; ; semantic details of mailbox names. + +(defsubst imap-parse-mailbox () + (let ((mailbox (imap-parse-astring))) + (if (string-equal "INBOX" (upcase mailbox)) + "INBOX" + mailbox))) + +;; greeting = "*" SP (resp-cond-auth / resp-cond-bye) CRLF +;; +;; resp-cond-auth = ("OK" / "PREAUTH") SP resp-text +;; ; Authentication condition +;; +;; resp-cond-bye = "BYE" SP resp-text + +(defun imap-parse-greeting () + "Parse a IMAP greeting." + (cond ((looking-at "\\* OK ") + (setq imap-state 'nonauth)) + ((looking-at "\\* PREAUTH ") + (setq imap-state 'auth)) + ((looking-at "\\* BYE ") + (setq imap-state 'closed)))) + +;; response = *(continue-req / response-data) response-done +;; +;; continue-req = "+" SP (resp-text / base64) CRLF +;; +;; response-data = "*" SP (resp-cond-state / resp-cond-bye / +;; mailbox-data / message-data / capability-data) CRLF +;; +;; response-done = response-tagged / response-fatal +;; +;; response-fatal = "*" SP resp-cond-bye CRLF +;; ; Server closes connection immediately +;; +;; response-tagged = tag SP resp-cond-state CRLF +;; +;; resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text +;; ; Status condition +;; +;; resp-cond-bye = "BYE" SP resp-text +;; +;; mailbox-data = "FLAGS" SP flag-list / +;; "LIST" SP mailbox-list / +;; "LSUB" SP mailbox-list / +;; "SEARCH" *(SP nz-number) / +;; "STATUS" SP mailbox SP "(" +;; [status-att SP number *(SP status-att SP number)] ")" / +;; number SP "EXISTS" / +;; number SP "RECENT" +;; +;; message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att)) +;; +;; capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1" +;; *(SP capability) +;; ; IMAP4rev1 servers which offer RFC 1730 +;; ; compatibility MUST list "IMAP4" as the first +;; ; capability. + +(defun imap-parse-response () + "Parse a IMAP command response." + (let (token) + (case (setq token (read (current-buffer))) + (+ (setq imap-continuation + (or (buffer-substring (min (point-max) (1+ (point))) + (point-max)) + t))) + (* (case (prog1 (setq token (read (current-buffer))) + (imap-forward)) + (OK (imap-parse-resp-text)) + (NO (imap-parse-resp-text)) + (BAD (imap-parse-resp-text)) + (BYE (imap-parse-resp-text)) + (FLAGS (imap-mailbox-put 'flags (imap-parse-flag-list))) + (LIST (imap-parse-data-list 'list)) + (LSUB (imap-parse-data-list 'lsub)) + (SEARCH (imap-mailbox-put + 'search + (read (concat "(" (buffer-substring (point) (point-max)) ")")))) + (STATUS (imap-parse-status)) + (CAPABILITY (setq imap-capability + (read (concat "(" (upcase (buffer-substring + (point) (point-max))) + ")")))) + (ID (setq imap-id (read (buffer-substring (point) + (point-max))))) + (ACL (imap-parse-acl)) + (t (case (prog1 (read (current-buffer)) + (imap-forward)) + (EXISTS (imap-mailbox-put 'exists token)) + (RECENT (imap-mailbox-put 'recent token)) + (EXPUNGE t) + (FETCH (imap-parse-fetch token)) + (t (message "Garbage: %s" (buffer-string))))))) + (t (let (status) + (if (not (integerp token)) + (message "Garbage: %s" (buffer-string)) + (case (prog1 (setq status (read (current-buffer))) + (imap-forward)) + (OK (progn + (setq imap-reached-tag (max imap-reached-tag token)) + (imap-parse-resp-text))) + (NO (progn + (setq imap-reached-tag (max imap-reached-tag token)) + (save-excursion + (imap-parse-resp-text)) + (let (code text) + (when (eq (char-after) ?\[) + (setq code (buffer-substring (point) + (search-forward "]"))) + (imap-forward)) + (setq text (buffer-substring (point) (point-max))) + (push (list token status code text) + imap-failed-tags)))) + (BAD (progn + (setq imap-reached-tag (max imap-reached-tag token)) + (save-excursion + (imap-parse-resp-text)) + (let (code text) + (when (eq (char-after) ?\[) + (setq code (buffer-substring (point) + (search-forward "]"))) + (imap-forward)) + (setq text (buffer-substring (point) (point-max))) + (push (list token status code text) imap-failed-tags) + (error "Internal error, tag %s status %s code %s text %s" + token status code text)))) + (t (message "Garbage: %s" (buffer-string)))) + (when (assq token imap-callbacks) + (funcall (cdr (assq token imap-callbacks)) token status) + (setq imap-callbacks + (imap-remassoc token imap-callbacks))))))))) + +;; resp-text = ["[" resp-text-code "]" SP] text +;; +;; text = 1*TEXT-CHAR +;; +;; TEXT-CHAR = <any CHAR except CR and LF> + +(defun imap-parse-resp-text () + (imap-parse-resp-text-code)) + +;; resp-text-code = "ALERT" / +;; "BADCHARSET [SP "(" astring *(SP astring) ")" ] / +;; "NEWNAME" SP string SP string / +;; "PARSE" / +;; "PERMANENTFLAGS" SP "(" +;; [flag-perm *(SP flag-perm)] ")" / +;; "READ-ONLY" / +;; "READ-WRITE" / +;; "TRYCREATE" / +;; "UIDNEXT" SP nz-number / +;; "UIDVALIDITY" SP nz-number / +;; "UNSEEN" SP nz-number / +;; resp-text-atom [SP 1*<any TEXT-CHAR except "]">] +;; +;; resp_code_apnd = "APPENDUID" SPACE nz_number SPACE uniqueid +;; +;; resp_code_copy = "COPYUID" SPACE nz_number SPACE set SPACE set +;; +;; set = sequence-num / (sequence-num ":" sequence-num) / +;; (set "," set) +;; ; Identifies a set of messages. For message +;; ; sequence numbers, these are consecutive +;; ; numbers from 1 to the number of messages in +;; ; the mailbox +;; ; Comma delimits individual numbers, colon +;; ; delimits between two numbers inclusive. +;; ; Example: 2,4:7,9,12:* is 2,4,5,6,7,9,12,13, +;; ; 14,15 for a mailbox with 15 messages. +;; +;; sequence-num = nz-number / "*" +;; ; * is the largest number in use. For message +;; ; sequence numbers, it is the number of messages +;; ; in the mailbox. For unique identifiers, it is +;; ; the unique identifier of the last message in +;; ; the mailbox. +;; +;; flag-perm = flag / "\*" +;; +;; flag = "\Answered" / "\Flagged" / "\Deleted" / +;; "\Seen" / "\Draft" / flag-keyword / flag-extension +;; ; Does not include "\Recent" +;; +;; flag-extension = "\" atom +;; ; Future expansion. Client implementations +;; ; MUST accept flag-extension flags. Server +;; ; implementations MUST NOT generate +;; ; flag-extension flags except as defined by +;; ; future standard or standards-track +;; ; revisions of this specification. +;; +;; flag-keyword = atom +;; +;; resp-text-atom = 1*<any ATOM-CHAR except "]"> + +(defun imap-parse-resp-text-code () + ;; xxx next line for stalker communigate pro 3.3.1 bug + (when (looking-at " \\[") + (imap-forward)) + (when (eq (char-after) ?\[) + (imap-forward) + (cond ((search-forward "PERMANENTFLAGS " nil t) + (imap-mailbox-put 'permanentflags (imap-parse-flag-list))) + ((search-forward "UIDNEXT \\([0-9]+\\)" nil t) + (imap-mailbox-put 'uidnext (match-string 1))) + ((search-forward "UNSEEN " nil t) + (imap-mailbox-put 'first-unseen (read (current-buffer)))) + ((looking-at "UIDVALIDITY \\([0-9]+\\)") + (imap-mailbox-put 'uidvalidity (match-string 1))) + ((search-forward "READ-ONLY" nil t) + (imap-mailbox-put 'read-only t)) + ((search-forward "NEWNAME " nil t) + (let (oldname newname) + (setq oldname (imap-parse-string)) + (imap-forward) + (setq newname (imap-parse-string)) + (imap-mailbox-put 'newname newname oldname))) + ((search-forward "TRYCREATE" nil t) + (imap-mailbox-put 'trycreate t imap-current-target-mailbox)) + ((looking-at "APPENDUID \\([0-9]+\\) \\([0-9]+\\)") + (imap-mailbox-put 'appenduid + (list (match-string 1) + (string-to-number (match-string 2))) + imap-current-target-mailbox)) + ((looking-at "COPYUID \\([0-9]+\\) \\([0-9,:]+\\) \\([0-9,:]+\\)") + (imap-mailbox-put 'copyuid (list (match-string 1) + (match-string 2) + (match-string 3)) + imap-current-target-mailbox)) + ((search-forward "ALERT] " nil t) + (message "Imap server %s information: %s" imap-server + (buffer-substring (point) (point-max))))))) + +;; mailbox-list = "(" [mbx-list-flags] ")" SP +;; (DQUOTE QUOTED-CHAR DQUOTE / nil) SP mailbox +;; +;; mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag +;; *(SP mbx-list-oflag) / +;; mbx-list-oflag *(SP mbx-list-oflag) +;; +;; mbx-list-oflag = "\Noinferiors" / flag-extension +;; ; Other flags; multiple possible per LIST response +;; +;; mbx-list-sflag = "\Noselect" / "\Marked" / "\Unmarked" +;; ; Selectability flags; only one per LIST response +;; +;; QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> / +;; "\" quoted-specials +;; +;; quoted-specials = DQUOTE / "\" + +(defun imap-parse-data-list (type) + (let (flags delimiter mailbox) + (setq flags (imap-parse-flag-list)) + (when (looking-at " NIL\\| \"\\\\?\\(.\\)\"") + (setq delimiter (match-string 1)) + (goto-char (1+ (match-end 0))) + (when (setq mailbox (imap-parse-mailbox)) + (imap-mailbox-put type t mailbox) + (imap-mailbox-put 'list-flags flags mailbox) + (imap-mailbox-put 'delimiter delimiter mailbox))))) + +;; msg_att ::= "(" 1#("ENVELOPE" SPACE envelope / +;; "FLAGS" SPACE "(" #(flag / "\Recent") ")" / +;; "INTERNALDATE" SPACE date_time / +;; "RFC822" [".HEADER" / ".TEXT"] SPACE nstring / +;; "RFC822.SIZE" SPACE number / +;; "BODY" ["STRUCTURE"] SPACE body / +;; "BODY" section ["<" number ">"] SPACE nstring / +;; "UID" SPACE uniqueid) ")" +;; +;; date_time ::= <"> date_day_fixed "-" date_month "-" date_year +;; SPACE time SPACE zone <"> +;; +;; section ::= "[" [section_text / (nz_number *["." nz_number] +;; ["." (section_text / "MIME")])] "]" +;; +;; section_text ::= "HEADER" / "HEADER.FIELDS" [".NOT"] +;; SPACE header_list / "TEXT" +;; +;; header_fld_name ::= astring +;; +;; header_list ::= "(" 1#header_fld_name ")" + +(defsubst imap-parse-header-list () + (when (eq (char-after) ?\() + (let (strlist) + (while (not (eq (char-after) ?\))) + (imap-forward) + (push (imap-parse-astring) strlist)) + (imap-forward) + (nreverse strlist)))) + +(defsubst imap-parse-fetch-body-section () + (let ((section + (buffer-substring (point) (1- (re-search-forward "[] ]" nil t))))) + (if (eq (char-before) ? ) + (prog1 + (mapconcat 'identity (cons section (imap-parse-header-list)) " ") + (search-forward "]" nil t)) + section))) + +(defun imap-parse-fetch (response) + (when (eq (char-after) ?\() + (let (uid flags envelope internaldate rfc822 rfc822header rfc822text + rfc822size body bodydetail bodystructure flags-empty) + (while (not (eq (char-after) ?\))) + (imap-forward) + (let ((token (read (current-buffer)))) + (imap-forward) + (cond ((eq token 'UID) + (setq uid (condition-case () + (read (current-buffer)) + (error)))) + ((eq token 'FLAGS) + (setq flags (imap-parse-flag-list)) + (if (not flags) + (setq flags-empty 't))) + ((eq token 'ENVELOPE) + (setq envelope (imap-parse-envelope))) + ((eq token 'INTERNALDATE) + (setq internaldate (imap-parse-string))) + ((eq token 'RFC822) + (setq rfc822 (imap-parse-nstring))) + ((eq token 'RFC822.HEADER) + (setq rfc822header (imap-parse-nstring))) + ((eq token 'RFC822.TEXT) + (setq rfc822text (imap-parse-nstring))) + ((eq token 'RFC822.SIZE) + (setq rfc822size (read (current-buffer)))) + ((eq token 'BODY) + (if (eq (char-before) ?\[) + (push (list + (upcase (imap-parse-fetch-body-section)) + (and (eq (char-after) ?<) + (buffer-substring (1+ (point)) + (search-forward ">" nil t))) + (progn (imap-forward) + (imap-parse-nstring))) + bodydetail) + (setq body (imap-parse-body)))) + ((eq token 'BODYSTRUCTURE) + (setq bodystructure (imap-parse-body)))))) + (when uid + (setq imap-current-message uid) + (imap-message-put uid 'UID uid) + (and (or flags flags-empty) (imap-message-put uid 'FLAGS flags)) + (and envelope (imap-message-put uid 'ENVELOPE envelope)) + (and internaldate (imap-message-put uid 'INTERNALDATE internaldate)) + (and rfc822 (imap-message-put uid 'RFC822 rfc822)) + (and rfc822header (imap-message-put uid 'RFC822.HEADER rfc822header)) + (and rfc822text (imap-message-put uid 'RFC822.TEXT rfc822text)) + (and rfc822size (imap-message-put uid 'RFC822.SIZE rfc822size)) + (and body (imap-message-put uid 'BODY body)) + (and bodydetail (imap-message-put uid 'BODYDETAIL bodydetail)) + (and bodystructure (imap-message-put uid 'BODYSTRUCTURE bodystructure)) + (run-hooks 'imap-fetch-data-hook))))) + +;; mailbox-data = ... +;; "STATUS" SP mailbox SP "(" +;; [status-att SP number +;; *(SP status-att SP number)] ")" +;; ... +;; +;; status-att = "MESSAGES" / "RECENT" / "UIDNEXT" / "UIDVALIDITY" / +;; "UNSEEN" + +(defun imap-parse-status () + (let ((mailbox (imap-parse-mailbox))) + (if (eq (char-after) ? ) + (forward-char)) + (when (and mailbox (eq (char-after) ?\()) + (while (and (not (eq (char-after) ?\))) + (or (forward-char) t) + (looking-at "\\([A-Za-z]+\\) ")) + (let ((token (upcase (match-string 1)))) + (goto-char (match-end 0)) + (cond ((string= token "MESSAGES") + (imap-mailbox-put 'messages (read (current-buffer)) mailbox)) + ((string= token "RECENT") + (imap-mailbox-put 'recent (read (current-buffer)) mailbox)) + ((string= token "UIDNEXT") + (and (looking-at "[0-9]+") + (imap-mailbox-put 'uidnext (match-string 0) mailbox) + (goto-char (match-end 0)))) + ((string= token "UIDVALIDITY") + (and (looking-at "[0-9]+") + (imap-mailbox-put 'uidvalidity (match-string 0) mailbox) + (goto-char (match-end 0)))) + ((string= token "UNSEEN") + (imap-mailbox-put 'unseen (read (current-buffer)) mailbox)) + (t + (message "Unknown status data %s in mailbox %s ignored" + token mailbox) + (read (current-buffer))))))))) + +;; acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE +;; rights) +;; +;; identifier ::= astring +;; +;; rights ::= astring + +(defun imap-parse-acl () + (let ((mailbox (imap-parse-mailbox)) + identifier rights acl) + (while (eq (char-after) ?\ ) + (imap-forward) + (setq identifier (imap-parse-astring)) + (imap-forward) + (setq rights (imap-parse-astring)) + (setq acl (append acl (list (cons identifier rights))))) + (imap-mailbox-put 'acl acl mailbox))) + +;; flag-list = "(" [flag *(SP flag)] ")" +;; +;; flag = "\Answered" / "\Flagged" / "\Deleted" / +;; "\Seen" / "\Draft" / flag-keyword / flag-extension +;; ; Does not include "\Recent" +;; +;; flag-keyword = atom +;; +;; flag-extension = "\" atom +;; ; Future expansion. Client implementations +;; ; MUST accept flag-extension flags. Server +;; ; implementations MUST NOT generate +;; ; flag-extension flags except as defined by +;; ; future standard or standards-track +;; ; revisions of this specification. + +(defun imap-parse-flag-list () + (let (flag-list start) + (assert (eq (char-after) ?\() nil "In imap-parse-flag-list") + (while (and (not (eq (char-after) ?\))) + (setq start (progn + (imap-forward) + ;; next line for Courier IMAP bug. + (skip-chars-forward " ") + (point))) + (> (skip-chars-forward "^ )" (point-at-eol)) 0)) + (push (buffer-substring start (point)) flag-list)) + (assert (eq (char-after) ?\)) nil "In imap-parse-flag-list") + (imap-forward) + (nreverse flag-list))) + +;; envelope = "(" env-date SP env-subject SP env-from SP env-sender SP +;; env-reply-to SP env-to SP env-cc SP env-bcc SP +;; env-in-reply-to SP env-message-id ")" +;; +;; env-bcc = "(" 1*address ")" / nil +;; +;; env-cc = "(" 1*address ")" / nil +;; +;; env-date = nstring +;; +;; env-from = "(" 1*address ")" / nil +;; +;; env-in-reply-to = nstring +;; +;; env-message-id = nstring +;; +;; env-reply-to = "(" 1*address ")" / nil +;; +;; env-sender = "(" 1*address ")" / nil +;; +;; env-subject = nstring +;; +;; env-to = "(" 1*address ")" / nil + +(defun imap-parse-envelope () + (when (eq (char-after) ?\() + (imap-forward) + (vector (prog1 (imap-parse-nstring) ;; date + (imap-forward)) + (prog1 (imap-parse-nstring) ;; subject + (imap-forward)) + (prog1 (imap-parse-address-list) ;; from + (imap-forward)) + (prog1 (imap-parse-address-list) ;; sender + (imap-forward)) + (prog1 (imap-parse-address-list) ;; reply-to + (imap-forward)) + (prog1 (imap-parse-address-list) ;; to + (imap-forward)) + (prog1 (imap-parse-address-list) ;; cc + (imap-forward)) + (prog1 (imap-parse-address-list) ;; bcc + (imap-forward)) + (prog1 (imap-parse-nstring) ;; in-reply-to + (imap-forward)) + (prog1 (imap-parse-nstring) ;; message-id + (imap-forward))))) + +;; body-fld-param = "(" string SP string *(SP string SP string) ")" / nil + +(defsubst imap-parse-string-list () + (cond ((eq (char-after) ?\() ;; body-fld-param + (let (strlist str) + (imap-forward) + (while (setq str (imap-parse-string)) + (push str strlist) + ;; buggy stalker communigate pro 3.0 doesn't print SPC + ;; between body-fld-param's sometimes + (or (eq (char-after) ?\") + (imap-forward))) + (nreverse strlist))) + ((imap-parse-nil) + nil))) + +;; body-extension = nstring / number / +;; "(" body-extension *(SP body-extension) ")" +;; ; Future expansion. Client implementations +;; ; MUST accept body-extension fields. Server +;; ; implementations MUST NOT generate +;; ; body-extension fields except as defined by +;; ; future standard or standards-track +;; ; revisions of this specification. + +(defun imap-parse-body-extension () + (if (eq (char-after) ?\() + (let (b-e) + (imap-forward) + (push (imap-parse-body-extension) b-e) + (while (eq (char-after) ?\ ) + (imap-forward) + (push (imap-parse-body-extension) b-e)) + (assert (eq (char-after) ?\)) nil "In imap-parse-body-extension") + (imap-forward) + (nreverse b-e)) + (or (imap-parse-number) + (imap-parse-nstring)))) + +;; body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang +;; *(SP body-extension)]] +;; ; MUST NOT be returned on non-extensible +;; ; "BODY" fetch +;; +;; body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang +;; *(SP body-extension)]] +;; ; MUST NOT be returned on non-extensible +;; ; "BODY" fetch + +(defsubst imap-parse-body-ext () + (let (ext) + (when (eq (char-after) ?\ ) ;; body-fld-dsp + (imap-forward) + (let (dsp) + (if (eq (char-after) ?\() + (progn + (imap-forward) + (push (imap-parse-string) dsp) + (imap-forward) + (push (imap-parse-string-list) dsp) + (imap-forward)) + ;; With assert, the code might not be eval'd. + ;; (assert (imap-parse-nil) t "In imap-parse-body-ext") + (imap-parse-nil)) + (push (nreverse dsp) ext)) + (when (eq (char-after) ?\ ) ;; body-fld-lang + (imap-forward) + (if (eq (char-after) ?\() + (push (imap-parse-string-list) ext) + (push (imap-parse-nstring) ext)) + (while (eq (char-after) ?\ ) ;; body-extension + (imap-forward) + (setq ext (append (imap-parse-body-extension) ext))))) + ext)) + +;; body = "(" body-type-1part / body-type-mpart ")" +;; +;; body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang +;; *(SP body-extension)]] +;; ; MUST NOT be returned on non-extensible +;; ; "BODY" fetch +;; +;; body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang +;; *(SP body-extension)]] +;; ; MUST NOT be returned on non-extensible +;; ; "BODY" fetch +;; +;; body-fields = body-fld-param SP body-fld-id SP body-fld-desc SP +;; body-fld-enc SP body-fld-octets +;; +;; body-fld-desc = nstring +;; +;; body-fld-dsp = "(" string SP body-fld-param ")" / nil +;; +;; body-fld-enc = (DQUOTE ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ +;; "QUOTED-PRINTABLE") DQUOTE) / string +;; +;; body-fld-id = nstring +;; +;; body-fld-lang = nstring / "(" string *(SP string) ")" +;; +;; body-fld-lines = number +;; +;; body-fld-md5 = nstring +;; +;; body-fld-octets = number +;; +;; body-fld-param = "(" string SP string *(SP string SP string) ")" / nil +;; +;; body-type-1part = (body-type-basic / body-type-msg / body-type-text) +;; [SP body-ext-1part] +;; +;; body-type-basic = media-basic SP body-fields +;; ; MESSAGE subtype MUST NOT be "RFC822" +;; +;; body-type-msg = media-message SP body-fields SP envelope +;; SP body SP body-fld-lines +;; +;; body-type-text = media-text SP body-fields SP body-fld-lines +;; +;; body-type-mpart = 1*body SP media-subtype +;; [SP body-ext-mpart] +;; +;; media-basic = ((DQUOTE ("APPLICATION" / "AUDIO" / "IMAGE" / +;; "MESSAGE" / "VIDEO") DQUOTE) / string) SP media-subtype +;; ; Defined in [MIME-IMT] +;; +;; media-message = DQUOTE "MESSAGE" DQUOTE SP DQUOTE "RFC822" DQUOTE +;; ; Defined in [MIME-IMT] +;; +;; media-subtype = string +;; ; Defined in [MIME-IMT] +;; +;; media-text = DQUOTE "TEXT" DQUOTE SP media-subtype +;; ; Defined in [MIME-IMT] + +(defun imap-parse-body () + (let (body) + (when (eq (char-after) ?\() + (imap-forward) + (if (eq (char-after) ?\() + (let (subbody) + (while (and (eq (char-after) ?\() + (setq subbody (imap-parse-body))) + ;; buggy stalker communigate pro 3.0 insert a SPC between + ;; parts in multiparts + (when (and (eq (char-after) ?\ ) + (eq (char-after (1+ (point))) ?\()) + (imap-forward)) + (push subbody body)) + (imap-forward) + (push (imap-parse-string) body) ;; media-subtype + (when (eq (char-after) ?\ ) ;; body-ext-mpart: + (imap-forward) + (if (eq (char-after) ?\() ;; body-fld-param + (push (imap-parse-string-list) body) + (push (and (imap-parse-nil) nil) body)) + (setq body + (append (imap-parse-body-ext) body))) ;; body-ext-... + (assert (eq (char-after) ?\)) nil "In imap-parse-body") + (imap-forward) + (nreverse body)) + + (push (imap-parse-string) body) ;; media-type + (imap-forward) + (push (imap-parse-string) body) ;; media-subtype + (imap-forward) + ;; next line for Sun SIMS bug + (and (eq (char-after) ? ) (imap-forward)) + (if (eq (char-after) ?\() ;; body-fld-param + (push (imap-parse-string-list) body) + (push (and (imap-parse-nil) nil) body)) + (imap-forward) + (push (imap-parse-nstring) body) ;; body-fld-id + (imap-forward) + (push (imap-parse-nstring) body) ;; body-fld-desc + (imap-forward) + ;; next `or' for Sun SIMS bug, it regard body-fld-enc as a + ;; nstring and return nil instead of defaulting back to 7BIT + ;; as the standard says. + (push (or (imap-parse-nstring) "7BIT") body) ;; body-fld-enc + (imap-forward) + (push (imap-parse-number) body) ;; body-fld-octets + + ;; ok, we're done parsing the required parts, what comes now is one + ;; of three things: + ;; + ;; envelope (then we're parsing body-type-msg) + ;; body-fld-lines (then we're parsing body-type-text) + ;; body-ext-1part (then we're parsing body-type-basic) + ;; + ;; the problem is that the two first are in turn optionally followed +;; by the third. So we parse the first two here (if there are any)... + + (when (eq (char-after) ?\ ) + (imap-forward) + (let (lines) + (cond ((eq (char-after) ?\() ;; body-type-msg: + (push (imap-parse-envelope) body) ;; envelope + (imap-forward) + (push (imap-parse-body) body) ;; body + ;; buggy stalker communigate pro 3.0 doesn't print + ;; number of lines in message/rfc822 attachment + (if (eq (char-after) ?\)) + (push 0 body) + (imap-forward) + (push (imap-parse-number) body))) ;; body-fld-lines + ((setq lines (imap-parse-number)) ;; body-type-text: + (push lines body)) ;; body-fld-lines + (t + (backward-char))))) ;; no match... + + ;; ...and then parse the third one here... + + (when (eq (char-after) ?\ ) ;; body-ext-1part: + (imap-forward) + (push (imap-parse-nstring) body) ;; body-fld-md5 + (setq body (append (imap-parse-body-ext) body))) ;; body-ext-1part.. + + (assert (eq (char-after) ?\)) nil "In imap-parse-body 2") + (imap-forward) + (nreverse body))))) + +(when imap-debug ; (untrace-all) + (require 'trace) + (buffer-disable-undo (get-buffer-create imap-debug-buffer)) + (mapc (lambda (f) (trace-function-background f imap-debug-buffer)) + '( + imap-utf7-encode + imap-utf7-decode + imap-error-text + imap-kerberos4s-p + imap-kerberos4-open + imap-ssl-p + imap-ssl-open + imap-network-p + imap-network-open + imap-interactive-login + imap-kerberos4a-p + imap-kerberos4-auth + imap-cram-md5-p + imap-cram-md5-auth + imap-login-p + imap-login-auth + imap-anonymous-p + imap-anonymous-auth + imap-open-1 + imap-open + imap-opened + imap-authenticate + imap-close + imap-capability + imap-namespace + imap-send-command-wait + imap-mailbox-put + imap-mailbox-get + imap-mailbox-map-1 + imap-mailbox-map + imap-current-mailbox + imap-current-mailbox-p-1 + imap-current-mailbox-p + imap-mailbox-select-1 + imap-mailbox-select + imap-mailbox-examine-1 + imap-mailbox-examine + imap-mailbox-unselect + imap-mailbox-expunge + imap-mailbox-close + imap-mailbox-create-1 + imap-mailbox-create + imap-mailbox-delete + imap-mailbox-rename + imap-mailbox-lsub + imap-mailbox-list + imap-mailbox-subscribe + imap-mailbox-unsubscribe + imap-mailbox-status + imap-mailbox-acl-get + imap-mailbox-acl-set + imap-mailbox-acl-delete + imap-current-message + imap-list-to-message-set + imap-fetch-asynch + imap-fetch + imap-message-put + imap-message-get + imap-message-map + imap-search + imap-message-flag-permanent-p + imap-message-flags-set + imap-message-flags-del + imap-message-flags-add + imap-message-copyuid-1 + imap-message-copyuid + imap-message-copy + imap-message-appenduid-1 + imap-message-appenduid + imap-message-append + imap-body-lines + imap-envelope-from + imap-send-command-1 + imap-send-command + imap-wait-for-tag + imap-sentinel + imap-find-next-line + imap-arrival-filter + imap-parse-greeting + imap-parse-response + imap-parse-resp-text + imap-parse-resp-text-code + imap-parse-data-list + imap-parse-fetch + imap-parse-status + imap-parse-acl + imap-parse-flag-list + imap-parse-envelope + imap-parse-body-extension + imap-parse-body + ))) + +(provide 'imap) + +;;; arch-tag: 27369ed6-33e4-482f-96f1-8bb906ba70f7 +;;; imap.el ends here diff --git a/lisp/net/netrc.el b/lisp/net/netrc.el index a5a63d894eb..8c588a11451 100644 --- a/lisp/net/netrc.el +++ b/lisp/net/netrc.el @@ -32,27 +32,51 @@ ;;; Code: ;;; -;;; .netrc and .authinforc parsing +;;; .netrc and .authinfo rc parsing ;;; +;; use encrypt if loaded (encrypt-file-alist has to be set as well) +(eval-and-compile + (autoload 'encrypt-find-model "encrypt") + (autoload 'encrypt-insert-file-contents "encrypt")) (defalias 'netrc-point-at-eol (if (fboundp 'point-at-eol) 'point-at-eol 'line-end-position)) +(eval-when-compile + (defvar encrypt-file-alist) + ;; This is unnecessary in the compiled version as it is a macro. + (if (fboundp 'bound-and-true-p) + (defalias 'netrc-bound-and-true-p 'bound-and-true-p) + (defmacro netrc-bound-and-true-p (var) + "Return the value of symbol VAR if it is bound, else nil." + `(and (boundp (quote ,var)) ,var)))) + +(defgroup netrc nil + "Netrc configuration." + :group 'comm) + +(defvar netrc-services-file "/etc/services" + "The name of the services file.") (defun netrc-parse (file) - "Parse FILE and return a list of all entries in the file." + (interactive "fFile to Parse: ") + "Parse FILE and return an list of all entries in the file." (when (file-exists-p file) (with-temp-buffer (let ((tokens '("machine" "default" "login" "password" "account" "macdef" "force" "port")) + (encryption-model (when (netrc-bound-and-true-p encrypt-file-alist) + (encrypt-find-model file))) alist elem result pair) - (insert-file-contents file) + (if encryption-model + (encrypt-insert-file-contents file encryption-model) + (insert-file-contents file)) (goto-char (point-min)) ;; Go through the file, line by line. (while (not (eobp)) - (narrow-to-region (point) (netrc-point-at-eol)) + (narrow-to-region (point) (point-at-eol)) ;; For each line, get the tokens and values. (while (not (eobp)) (skip-chars-forward "\t ") @@ -113,16 +137,77 @@ Entries without port tokens default to DEFAULTPORT." (when result (setq result (nreverse result)) (while (and result - (not (equal (or port defaultport "nntp") - (or (netrc-get (car result) "port") - defaultport "nntp")))) + (not (netrc-port-equal + (or port defaultport "nntp") + (or (netrc-get (car result) "port") + defaultport "nntp")))) (pop result)) (car result)))) +(defun netrc-machine-user-or-password (mode authinfo-file-or-list machines ports defaults) + "Get the user name or password according to MODE from AUTHINFO-FILE-OR-LIST. +Matches a machine from MACHINES and a port from PORTS, giving +default ports DEFAULTS to `netrc-machine'. + +MODE can be \"login\" or \"password\", suitable for passing to +`netrc-get'." + (let ((authinfo-list (if (stringp authinfo-file-or-list) + (netrc-parse authinfo-file-or-list) + authinfo-file-or-list)) + (ports (or ports '(nil))) + (defaults (or defaults '(nil))) + info) + (dolist (machine machines) + (dolist (default defaults) + (dolist (port ports) + (let ((alist (netrc-machine authinfo-list machine port default))) + (setq info (or (netrc-get alist mode) info)))))) + info)) + (defun netrc-get (alist type) "Return the value of token TYPE from ALIST." (cdr (assoc type alist))) +(defun netrc-port-equal (port1 port2) + (when (numberp port1) + (setq port1 (or (netrc-find-service-name port1) port1))) + (when (numberp port2) + (setq port2 (or (netrc-find-service-name port2) port2))) + (equal port1 port2)) + +(defun netrc-parse-services () + (when (file-exists-p netrc-services-file) + (let ((services nil)) + (with-temp-buffer + (insert-file-contents netrc-services-file) + (while (search-forward "#" nil t) + (delete-region (1- (point)) (point-at-eol))) + (goto-char (point-min)) + (while (re-search-forward + "^ *\\([^ \n\t]+\\)[ \t]+\\([0-9]+\\)/\\([^ \t\n]+\\)" nil t) + (push (list (match-string 1) (string-to-number (match-string 2)) + (intern (downcase (match-string 3)))) + services)) + (nreverse services))))) + +(defun netrc-find-service-name (number &optional type) + (let ((services (netrc-parse-services)) + service) + (setq type (or type 'tcp)) + (while (and (setq service (pop services)) + (not (and (= number (cadr service)) + (eq type (car (cddr service))))))) + (car service))) + +(defun netrc-find-service-number (name &optional type) + (let ((services (netrc-parse-services)) + service) + (setq type (or type 'tcp)) + (while (and (setq service (pop services)) + (not (and (string= name (car service)) + (eq type (car (cddr service))))))) + (cadr service))) + (provide 'netrc) ;;; arch-tag: af9929cc-2d12-482f-936e-eb4366f9fa55 diff --git a/lisp/net/newsticker.el b/lisp/net/newsticker.el index f43ee0dbc61..475581deaae 100644 --- a/lisp/net/newsticker.el +++ b/lisp/net/newsticker.el @@ -1199,10 +1199,11 @@ buffers *newsticker-wget-<feed>* will not be closed." (unless (fboundp 'match-string-no-properties) (defalias 'match-string-no-properties 'match-string)) -(unless (fboundp 'replace-regexp-in-string) - (defun replace-regexp-in-string (re rp st) - (save-match-data ;; apparently XEmacs needs save-match-data - (replace-in-string st re rp)))) +(when (featurep 'xemacs) + (unless (fboundp 'replace-regexp-in-string) + (defun replace-regexp-in-string (re rp st) + (save-match-data ;; apparently XEmacs needs save-match-data + (replace-in-string st re rp))))) ;; copied from subr.el (unless (fboundp 'add-to-invisibility-spec) @@ -2599,6 +2600,9 @@ If URL is nil it is searched at point." (add-to-list 'newsticker-url-list (list name url nil nil nil) t) (customize-variable 'newsticker-url-list)) +;; External. +(declare-function w3m-toggle-inline-image "ext:w3m" (&optional force no-cache)) + (defun newsticker-w3m-show-inline-images () "Show inline images in visible text ranges. In-line images in invisible text ranges are hidden. This function @@ -4842,6 +4846,11 @@ The face is chosen according the values of NT-FACE and AGE." ;; ====================================================================== ;;; HTML rendering ;; ====================================================================== + +;; External. +(declare-function htmlr-reset "ext:htmlr" ()) +(declare-function htmlr-step "ext:htmlr" ()) + (defun newsticker-htmlr-render (pos1 pos2) ; "Replacement for `htmlr-render'. Renders the HTML code in the region POS1 to POS2 using htmlr." diff --git a/lisp/net/ntlm.el b/lisp/net/ntlm.el new file mode 100644 index 00000000000..126f6688f0d --- /dev/null +++ b/lisp/net/ntlm.el @@ -0,0 +1,540 @@ +;;; ntlm.el --- NTLM (NT LanManager) authentication support + +;; Copyright (C) 2001, 2007 Free Software Foundation, Inc. + +;; Author: Taro Kawagishi <tarok@transpulse.org> +;; Keywords: NTLM, SASL +;; Version: 1.00 +;; Created: February 2001 + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This library is a direct translation of the Samba release 2.2.0 +;; implementation of Windows NT and LanManager compatible password +;; encryption. +;; +;; Interface functions: +;; +;; ntlm-build-auth-request +;; This will return a binary string, which should be used in the +;; base64 encoded form and it is the caller's responsibility to encode +;; the returned string with base64. +;; +;; ntlm-build-auth-response +;; It is the caller's responsibility to pass a base64 decoded string +;; (which will be a binary string) as the first argument and to +;; encode the returned string with base64. The second argument user +;; should be given in user@domain format. +;; +;; ntlm-get-password-hashes +;; +;; +;; NTLM authentication procedure example: +;; +;; 1. Open a network connection to the Exchange server at the IMAP port (143) +;; 2. Receive an opening message such as: +;; "* OK Microsoft Exchange IMAP4rev1 server version 5.5.2653.7 (XXXX) ready" +;; 3. Ask for IMAP server capability by sending "NNN capability" +;; 4. Receive a capability message such as: +;; "* CAPABILITY IMAP4 IMAP4rev1 IDLE LITERAL+ LOGIN-REFERRALS MAILBOX-REFERRALS NAMESPACE AUTH=NTLM" +;; 5. Ask for NTLM authentication by sending a string +;; "NNN authenticate ntlm" +;; 6. Receive continuation acknowledgment "+" +;; 7. Send NTLM authentication request generated by 'ntlm-build-auth-request +;; 8. Receive NTLM challenge string following acknowledgment "+" +;; 9. Generate response to challenge by 'ntlm-build-auth-response +;; (here two hash function values of the user password are encrypted) +;; 10. Receive authentication completion message such as +;; "NNN OK AUTHENTICATE NTLM completed." + +;;; Code: + +(require 'md4) + +;;; +;;; NTLM authentication interface functions + +(defun ntlm-build-auth-request (user &optional domain) + "Return the NTLM authentication request string for USER and DOMAIN. +USER is a string representing a user name to be authenticated and +DOMAIN is a NT domain. USER can include a NT domain part as in +user@domain where the string after @ is used as the domain if DOMAIN +is not given." + (interactive) + (let ((request-ident (concat "NTLMSSP" (make-string 1 0))) + (request-msgType (concat (make-string 1 1) (make-string 3 0))) + ;0x01 0x00 0x00 0x00 + (request-flags (concat (make-string 1 7) (make-string 1 178) + (make-string 2 0))) + ;0x07 0xb2 0x00 0x00 + lu ld off-d off-u) + (when (string-match "@" user) + (unless domain + (setq domain (substring user (1+ (match-beginning 0))))) + (setq user (substring user 0 (match-beginning 0)))) + ;; set fields offsets within the request struct + (setq lu (length user)) + (setq ld (length domain)) + (setq off-u 32) ;offset to the string 'user + (setq off-d (+ 32 lu)) ;offset to the string 'domain + ;; pack the request struct in a string + (concat request-ident ;8 bytes + request-msgType ;4 bytes + request-flags ;4 bytes + (md4-pack-int16 lu) ;user field, count field + (md4-pack-int16 lu) ;user field, max count field + (md4-pack-int32 (cons 0 off-u)) ;user field, offset field + (md4-pack-int16 ld) ;domain field, count field + (md4-pack-int16 ld) ;domain field, max count field + (md4-pack-int32 (cons 0 off-d)) ;domain field, offset field + user ;bufer field + domain ;bufer field + ))) + +(eval-when-compile + (defmacro ntlm-string-as-unibyte (string) + (if (fboundp 'string-as-unibyte) + `(string-as-unibyte ,string) + string))) + +(defun ntlm-build-auth-response (challenge user password-hashes) + "Return the response string to a challenge string CHALLENGE given by +the NTLM based server for the user USER and the password hash list +PASSWORD-HASHES. NTLM uses two hash values which are represented +by PASSWORD-HASHES. PASSWORD-HASHES should be a return value of + (list (ntlm-smb-passwd-hash password) (ntlm-md4hash password))" + (let* ((rchallenge (ntlm-string-as-unibyte challenge)) + ;; get fields within challenge struct + ;;(ident (substring rchallenge 0 8)) ;ident, 8 bytes + ;;(msgType (substring rchallenge 8 12)) ;msgType, 4 bytes + (uDomain (substring rchallenge 12 20)) ;uDomain, 8 bytes + (flags (substring rchallenge 20 24)) ;flags, 4 bytes + (challengeData (substring rchallenge 24 32)) ;challengeData, 8 bytes + uDomain-len uDomain-offs + ;; response struct and its fields + lmRespData ;lmRespData, 24 bytes + ntRespData ;ntRespData, 24 bytes + domain ;ascii domain string + lu ld off-lm off-nt off-d off-u off-w off-s) + ;; extract domain string from challenge string + (setq uDomain-len (md4-unpack-int16 (substring uDomain 0 2))) + (setq uDomain-offs (md4-unpack-int32 (substring uDomain 4 8))) + (setq domain + (ntlm-unicode2ascii (substring challenge + (cdr uDomain-offs) + (+ (cdr uDomain-offs) uDomain-len)) + (/ uDomain-len 2))) + ;; overwrite domain in case user is given in <user>@<domain> format + (when (string-match "@" user) + (setq domain (substring user (1+ (match-beginning 0)))) + (setq user (substring user 0 (match-beginning 0)))) + + ;; generate response data + (setq lmRespData + (ntlm-smb-owf-encrypt (car password-hashes) challengeData)) + (setq ntRespData + (ntlm-smb-owf-encrypt (cadr password-hashes) challengeData)) + + ;; get offsets to fields to pack the response struct in a string + (setq lu (length user)) + (setq ld (length domain)) + (setq off-lm 64) ;offset to string 'lmResponse + (setq off-nt (+ 64 24)) ;offset to string 'ntResponse + (setq off-d (+ 64 48)) ;offset to string 'uDomain + (setq off-u (+ 64 48 (* 2 ld))) ;offset to string 'uUser + (setq off-w (+ 64 48 (* 2 (+ ld lu)))) ;offset to string 'uWks + (setq off-s (+ 64 48 (* 2 (+ ld lu lu)))) ;offset to string 'sessionKey + ;; pack the response struct in a string + (concat "NTLMSSP\0" ;response ident field, 8 bytes + (md4-pack-int32 '(0 . 3)) ;response msgType field, 4 bytes + + ;; lmResponse field, 8 bytes + ;;AddBytes(response,lmResponse,lmRespData,24); + (md4-pack-int16 24) ;len field + (md4-pack-int16 24) ;maxlen field + (md4-pack-int32 (cons 0 off-lm)) ;field offset + + ;; ntResponse field, 8 bytes + ;;AddBytes(response,ntResponse,ntRespData,24); + (md4-pack-int16 24) ;len field + (md4-pack-int16 24) ;maxlen field + (md4-pack-int32 (cons 0 off-nt)) ;field offset + + ;; uDomain field, 8 bytes + ;;AddUnicodeString(response,uDomain,domain); + ;;AddBytes(response, uDomain, udomain, 2*ld); + (md4-pack-int16 (* 2 ld)) ;len field + (md4-pack-int16 (* 2 ld)) ;maxlen field + (md4-pack-int32 (cons 0 off-d)) ;field offset + + ;; uUser field, 8 bytes + ;;AddUnicodeString(response,uUser,u); + ;;AddBytes(response, uUser, uuser, 2*lu); + (md4-pack-int16 (* 2 lu)) ;len field + (md4-pack-int16 (* 2 lu)) ;maxlen field + (md4-pack-int32 (cons 0 off-u)) ;field offset + + ;; uWks field, 8 bytes + ;;AddUnicodeString(response,uWks,u); + (md4-pack-int16 (* 2 lu)) ;len field + (md4-pack-int16 (* 2 lu)) ;maxlen field + (md4-pack-int32 (cons 0 off-w)) ;field offset + + ;; sessionKey field, 8 bytes + ;;AddString(response,sessionKey,NULL); + (md4-pack-int16 0) ;len field + (md4-pack-int16 0) ;maxlen field + (md4-pack-int32 (cons 0 (- off-s off-lm))) ;field offset + + ;; flags field, 4 bytes + flags ; + + ;; buffer field + lmRespData ;lmResponse, 24 bytes + ntRespData ;ntResponse, 24 bytes + (ntlm-ascii2unicode domain ;unicode domain string, 2*ld bytes + (length domain)) ; + (ntlm-ascii2unicode user ;unicode user string, 2*lu bytes + (length user)) ; + (ntlm-ascii2unicode user ;unicode user string, 2*lu bytes + (length user)) ; + ))) + +(defun ntlm-get-password-hashes (password) + "Return a pair of SMB hash and NT MD4 hash of the given password PASSWORD" + (list (ntlm-smb-passwd-hash password) + (ntlm-md4hash password))) + +(defun ntlm-ascii2unicode (str len) + "Convert an ASCII string into a NT Unicode string, which is +little-endian utf16." + (let ((utf (make-string (* 2 len) 0)) (i 0) val) + (while (and (< i len) + (not (zerop (setq val (aref str i))))) + (aset utf (* 2 i) val) + (aset utf (1+ (* 2 i)) 0) + (setq i (1+ i))) + utf)) + +(defun ntlm-unicode2ascii (str len) + "Extract 7 bits ASCII part of a little endian utf16 string STR of length LEN." + (let ((buf (make-string len 0)) (i 0) (j 0)) + (while (< i len) + (aset buf i (logand (aref str j) 127)) ;(string-to-number "7f" 16) + (setq i (1+ i) + j (+ 2 j))) + buf)) + +(defun ntlm-smb-passwd-hash (passwd) + "Return the SMB password hash string of 16 bytes long for the given password +string PASSWD. PASSWD is truncated to 14 bytes if longer." + (let ((len (min (length passwd) 14))) + (ntlm-smb-des-e-p16 + (concat (substring (upcase passwd) 0 len) ;fill top 14 bytes with passwd + (make-string (- 15 len) 0))))) + +(defun ntlm-smb-owf-encrypt (passwd c8) + "Return the response string of 24 bytes long for the given password +string PASSWD based on the DES encryption. PASSWD is of at most 14 +bytes long and the challenge string C8 of 8 bytes long." + (let ((len (min (length passwd) 16)) p22) + (setq p22 (concat (substring passwd 0 len) ;fill top 16 bytes with passwd + (make-string (- 22 len) 0))) + (ntlm-smb-des-e-p24 p22 c8))) + +(defun ntlm-smb-des-e-p24 (p22 c8) + "Return a 24 bytes hashed string for a 21 bytes string P22 and a 8 bytes +string C8." + (concat (ntlm-smb-hash c8 p22 t) ;hash first 8 bytes of p22 + (ntlm-smb-hash c8 (substring p22 7) t) + (ntlm-smb-hash c8 (substring p22 14) t))) + +(defconst ntlm-smb-sp8 [75 71 83 33 64 35 36 37]) + +(defun ntlm-smb-des-e-p16 (p15) + "Return a 16 bytes hashed string for a 15 bytes string P15." + (concat (ntlm-smb-hash ntlm-smb-sp8 p15 t) ;hash of first 8 bytes of p15 + (ntlm-smb-hash ntlm-smb-sp8 ;hash of last 8 bytes of p15 + (substring p15 7) t))) + +(defun ntlm-smb-hash (in key forw) + "Return the hash string of length 8 for a string IN of length 8 and +a string KEY of length 8. FORW is t or nil." + (let ((out (make-string 8 0)) + outb ;string of length 64 + (inb (make-string 64 0)) + (keyb (make-string 64 0)) + (key2 (ntlm-smb-str-to-key key)) + (i 0) aa) + (while (< i 64) + (unless (zerop (logand (aref in (/ i 8)) (lsh 1 (- 7 (% i 8))))) + (aset inb i 1)) + (unless (zerop (logand (aref key2 (/ i 8)) (lsh 1 (- 7 (% i 8))))) + (aset keyb i 1)) + (setq i (1+ i))) + (setq outb (ntlm-smb-dohash inb keyb forw)) + (setq i 0) + (while (< i 64) + (unless (zerop (aref outb i)) + (setq aa (aref out (/ i 8))) + (aset out (/ i 8) + (logior aa (lsh 1 (- 7 (% i 8)))))) + (setq i (1+ i))) + out)) + +(defun ntlm-smb-str-to-key (str) + "Return a string of length 8 for the given string STR of length 7." + (let ((key (make-string 8 0)) + (i 7)) + (aset key 0 (lsh (aref str 0) -1)) + (aset key 1 (logior + (lsh (logand (aref str 0) 1) 6) + (lsh (aref str 1) -2))) + (aset key 2 (logior + (lsh (logand (aref str 1) 3) 5) + (lsh (aref str 2) -3))) + (aset key 3 (logior + (lsh (logand (aref str 2) 7) 4) + (lsh (aref str 3) -4))) + (aset key 4 (logior + (lsh (logand (aref str 3) 15) 3) + (lsh (aref str 4) -5))) + (aset key 5 (logior + (lsh (logand (aref str 4) 31) 2) + (lsh (aref str 5) -6))) + (aset key 6 (logior + (lsh (logand (aref str 5) 63) 1) + (lsh (aref str 6) -7))) + (aset key 7 (logand (aref str 6) 127)) + (while (>= i 0) + (aset key i (lsh (aref key i) 1)) + (setq i (1- i))) + key)) + +(defconst ntlm-smb-perm1 [57 49 41 33 25 17 9 + 1 58 50 42 34 26 18 + 10 2 59 51 43 35 27 + 19 11 3 60 52 44 36 + 63 55 47 39 31 23 15 + 7 62 54 46 38 30 22 + 14 6 61 53 45 37 29 + 21 13 5 28 20 12 4]) + +(defconst ntlm-smb-perm2 [14 17 11 24 1 5 + 3 28 15 6 21 10 + 23 19 12 4 26 8 + 16 7 27 20 13 2 + 41 52 31 37 47 55 + 30 40 51 45 33 48 + 44 49 39 56 34 53 + 46 42 50 36 29 32]) + +(defconst ntlm-smb-perm3 [58 50 42 34 26 18 10 2 + 60 52 44 36 28 20 12 4 + 62 54 46 38 30 22 14 6 + 64 56 48 40 32 24 16 8 + 57 49 41 33 25 17 9 1 + 59 51 43 35 27 19 11 3 + 61 53 45 37 29 21 13 5 + 63 55 47 39 31 23 15 7]) + +(defconst ntlm-smb-perm4 [32 1 2 3 4 5 + 4 5 6 7 8 9 + 8 9 10 11 12 13 + 12 13 14 15 16 17 + 16 17 18 19 20 21 + 20 21 22 23 24 25 + 24 25 26 27 28 29 + 28 29 30 31 32 1]) + +(defconst ntlm-smb-perm5 [16 7 20 21 + 29 12 28 17 + 1 15 23 26 + 5 18 31 10 + 2 8 24 14 + 32 27 3 9 + 19 13 30 6 + 22 11 4 25]) + +(defconst ntlm-smb-perm6 [40 8 48 16 56 24 64 32 + 39 7 47 15 55 23 63 31 + 38 6 46 14 54 22 62 30 + 37 5 45 13 53 21 61 29 + 36 4 44 12 52 20 60 28 + 35 3 43 11 51 19 59 27 + 34 2 42 10 50 18 58 26 + 33 1 41 9 49 17 57 25]) + +(defconst ntlm-smb-sc [1 1 2 2 2 2 2 2 1 2 2 2 2 2 2 1]) + +(defconst ntlm-smb-sbox [[[14 4 13 1 2 15 11 8 3 10 6 12 5 9 0 7] + [ 0 15 7 4 14 2 13 1 10 6 12 11 9 5 3 8] + [ 4 1 14 8 13 6 2 11 15 12 9 7 3 10 5 0] + [15 12 8 2 4 9 1 7 5 11 3 14 10 0 6 13]] + [[15 1 8 14 6 11 3 4 9 7 2 13 12 0 5 10] + [ 3 13 4 7 15 2 8 14 12 0 1 10 6 9 11 5] + [ 0 14 7 11 10 4 13 1 5 8 12 6 9 3 2 15] + [13 8 10 1 3 15 4 2 11 6 7 12 0 5 14 9]] + [[10 0 9 14 6 3 15 5 1 13 12 7 11 4 2 8] + [13 7 0 9 3 4 6 10 2 8 5 14 12 11 15 1] + [13 6 4 9 8 15 3 0 11 1 2 12 5 10 14 7] + [ 1 10 13 0 6 9 8 7 4 15 14 3 11 5 2 12]] + [[ 7 13 14 3 0 6 9 10 1 2 8 5 11 12 4 15] + [13 8 11 5 6 15 0 3 4 7 2 12 1 10 14 9] + [10 6 9 0 12 11 7 13 15 1 3 14 5 2 8 4] + [ 3 15 0 6 10 1 13 8 9 4 5 11 12 7 2 14]] + [[ 2 12 4 1 7 10 11 6 8 5 3 15 13 0 14 9] + [14 11 2 12 4 7 13 1 5 0 15 10 3 9 8 6] + [ 4 2 1 11 10 13 7 8 15 9 12 5 6 3 0 14] + [11 8 12 7 1 14 2 13 6 15 0 9 10 4 5 3]] + [[12 1 10 15 9 2 6 8 0 13 3 4 14 7 5 11] + [10 15 4 2 7 12 9 5 6 1 13 14 0 11 3 8] + [ 9 14 15 5 2 8 12 3 7 0 4 10 1 13 11 6] + [ 4 3 2 12 9 5 15 10 11 14 1 7 6 0 8 13]] + [[ 4 11 2 14 15 0 8 13 3 12 9 7 5 10 6 1] + [13 0 11 7 4 9 1 10 14 3 5 12 2 15 8 6] + [ 1 4 11 13 12 3 7 14 10 15 6 8 0 5 9 2] + [ 6 11 13 8 1 4 10 7 9 5 0 15 14 2 3 12]] + [[13 2 8 4 6 15 11 1 10 9 3 14 5 0 12 7] + [ 1 15 13 8 10 3 7 4 12 5 6 11 0 14 9 2] + [ 7 11 4 1 9 12 14 2 0 6 10 13 15 3 5 8] + [ 2 1 14 7 4 10 8 13 15 12 9 0 3 5 6 11]]]) + +(defsubst ntlm-string-permute (in perm n) + "Return a string of length N for a string IN and a permutation vector +PERM of size N. The length of IN should be height of PERM." + (let ((i 0) (out (make-string n 0))) + (while (< i n) + (aset out i (aref in (- (aref perm i) 1))) + (setq i (1+ i))) + out)) + +(defsubst ntlm-string-lshift (str count len) + "Return a string by circularly shifting a string STR by COUNT to the left. +length of STR is LEN." + (let ((c (% count len))) + (concat (substring str c len) (substring str 0 c)))) + +(defsubst ntlm-string-xor (in1 in2 n) + "Return exclusive-or of sequences in1 and in2" + (let ((w (make-string n 0)) (i 0)) + (while (< i n) + (aset w i (logxor (aref in1 i) (aref in2 i))) + (setq i (1+ i))) + w)) + +(defun ntlm-smb-dohash (in key forw) + "Return the hash value for a string IN and a string KEY. +Length of IN and KEY are 64. FORW non nill means forward, nil means +backward." + (let (pk1 ;string of length 56 + c ;string of length 28 + d ;string of length 28 + cd ;string of length 56 + (ki (make-vector 16 0)) ;vector of string of length 48 + pd1 ;string of length 64 + l ;string of length 32 + r ;string of length 32 + rl ;string of length 64 + (i 0) (j 0) (k 0)) + (setq pk1 (ntlm-string-permute key ntlm-smb-perm1 56)) + (setq c (substring pk1 0 28)) + (setq d (substring pk1 28 56)) + + (setq i 0) + (while (< i 16) + (setq c (ntlm-string-lshift c (aref ntlm-smb-sc i) 28)) + (setq d (ntlm-string-lshift d (aref ntlm-smb-sc i) 28)) + (setq cd (concat (substring c 0 28) (substring d 0 28))) + (aset ki i (ntlm-string-permute cd ntlm-smb-perm2 48)) + (setq i (1+ i))) + + (setq pd1 (ntlm-string-permute in ntlm-smb-perm3 64)) + + (setq l (substring pd1 0 32)) + (setq r (substring pd1 32 64)) + + (setq i 0) + (let (er ;string of length 48 + erk ;string of length 48 + (b (make-vector 8 0)) ;vector of strings of length 6 + cb ;string of length 32 + pcb ;string of length 32 + r2 ;string of length 32 + jj m n bj sbox-jmn) + (while (< i 16) + (setq er (ntlm-string-permute r ntlm-smb-perm4 48)) + (setq erk (ntlm-string-xor er + (aref ki (if forw i (- 15 i))) + 48)) + (setq j 0) + (while (< j 8) + (setq jj (* 6 j)) + (aset b j (substring erk jj (+ jj 6))) + (setq j (1+ j))) + (setq j 0) + (while (< j 8) + (setq bj (aref b j)) + (setq m (logior (lsh (aref bj 0) 1) (aref bj 5))) + (setq n (logior (lsh (aref bj 1) 3) + (lsh (aref bj 2) 2) + (lsh (aref bj 3) 1) + (aref bj 4))) + (setq k 0) + (setq sbox-jmn (aref (aref (aref ntlm-smb-sbox j) m) n)) + (while (< k 4) + (aset bj k + (if (zerop (logand sbox-jmn (lsh 1 (- 3 k)))) + 0 1)) + (setq k (1+ k))) + (setq j (1+ j))) + + (setq j 0) + (setq cb nil) + (while (< j 8) + (setq cb (concat cb (substring (aref b j) 0 4))) + (setq j (1+ j))) + + (setq pcb (ntlm-string-permute cb ntlm-smb-perm5 32)) + (setq r2 (ntlm-string-xor l pcb 32)) + (setq l r) + (setq r r2) + (setq i (1+ i)))) + (setq rl (concat r l)) + (ntlm-string-permute rl ntlm-smb-perm6 64))) + +(defun ntlm-md4hash (passwd) + "Return the 16 bytes MD4 hash of a string PASSWD after converting it +into a Unicode string. PASSWD is truncated to 128 bytes if longer." + (let (len wpwd) + ;; Password cannot be longer than 128 characters + (setq len (length passwd)) + (if (> len 128) + (setq len 128)) + ;; Password must be converted to NT unicode + (setq wpwd (ntlm-ascii2unicode passwd len)) + ;; Calculate length in bytes + (setq len (* len 2)) + (md4 wpwd len))) + +(provide 'ntlm) + +;;; arch-tag: 348ace18-f8e2-4176-8fe9-d9ab4e96f296 +;;; ntlm.el ends here diff --git a/lisp/net/quickurl.el b/lisp/net/quickurl.el index 3d237154618..d71d243621f 100644 --- a/lisp/net/quickurl.el +++ b/lisp/net/quickurl.el @@ -90,14 +90,6 @@ (require 'pp) (require 'browse-url) -;; Attempt to handle older/other emacs. -(eval-and-compile - ;; If customize isn't available just use defvar instead. - (unless (fboundp 'defgroup) - (defmacro defgroup (&rest rest) nil) - (defmacro defcustom (symbol init docstring &rest rest) - `(defvar ,symbol ,init ,docstring)))) - ;; Customize options. (defgroup quickurl nil diff --git a/lisp/net/rcirc.el b/lisp/net/rcirc.el index 5bc775b8957..3bdcdd92f86 100644 --- a/lisp/net/rcirc.el +++ b/lisp/net/rcirc.el @@ -55,9 +55,49 @@ :link '(custom-manual "(rcirc)") :group 'applications) -(defcustom rcirc-default-server "irc.freenode.net" - "The default server to connect to." - :type 'string +(defcustom rcirc-server-alist + '(("irc.freenode.net" :channels ("#rcirc"))) + "An alist of IRC connections to establish when running `rcirc'. +Each element looks like (SERVER-NAME PARAMETERS). + +SERVER-NAME is a string describing the server to connect +to. + +The optional PARAMETERS come in pairs PARAMETER VALUE. + +The following parameters are recognized: + +`:nick' + +VALUE must be a string. If absent, `rcirc-default-nick' is used +for this connection. + +`:port' + +VALUE must be a number or string. If absent, +`rcirc-default-port' is used. + +`:user-name' + +VALUE must be a string. If absent, `rcirc-default-user-name' is +used. + +`:full-name' + +VALUE must be a string. If absent, `rcirc-default-full-name' is +used. + +`:channels' + +VALUE must be a list of strings describing which channels to join +when connecting to this server. If absent, no channels will be +connected to automatically." + :type '(alist :key-type string + :value-type (plist :options ((:nick string) + (:port integer) + (:user-name string) + (:full-name string) + (:channels (repeat string))))) :group 'rcirc) (defcustom rcirc-default-port 6667 @@ -75,19 +115,13 @@ :type 'string :group 'rcirc) -(defcustom rcirc-default-user-full-name (if (string= (user-full-name) "") - rcirc-default-user-name - (user-full-name)) +(defcustom rcirc-default-full-name (if (string= (user-full-name) "") + rcirc-default-user-name + (user-full-name)) "The full name sent to the server when connecting." :type 'string :group 'rcirc) -(defcustom rcirc-startup-channels-alist '(("^irc.freenode.net$" "#rcirc")) - "Alist of channels to join at startup. -Each element looks like (SERVER-REGEXP . CHANNEL-LIST)." - :type '(alist :key-type string :value-type (repeat string)) - :group 'rcirc) - (defcustom rcirc-fill-flag t "*Non-nil means line-wrap messages printed in channel buffers." :type 'boolean @@ -95,11 +129,9 @@ Each element looks like (SERVER-REGEXP . CHANNEL-LIST)." (defcustom rcirc-fill-column nil "*Column beyond which automatic line-wrapping should happen. -If nil, use value of `fill-column'. -If `window-width', use the window's width as maximum. -If `frame-width', use the frame's width as maximum." +If nil, use value of `fill-column'. If 'frame-width, use the +maximum frame width." :type '(choice (const :tag "Value of `fill-column'") - (const :tag "Full window width" window-width) (const :tag "Full frame width" frame-width) (integer :tag "Number of columns")) :group 'rcirc) @@ -120,6 +152,11 @@ underneath each nick." "If non-nil, activity in this buffer is considered low priority.") (make-variable-buffer-local 'rcirc-low-priority-flag) +(defvar rcirc-omit-mode nil + "Non-nil if Rcirc-Omit mode is enabled. +Use the command `rcirc-omit-mode' to change this variable.") +(make-variable-buffer-local 'rcirc-omit-mode) + (defcustom rcirc-time-format "%H:%M " "*Describes how timestamps are printed. Used as the first arg to `format-time-string'." @@ -145,7 +182,8 @@ number. If zero or nil, no truncating is done." :group 'rcirc) (defcustom rcirc-scroll-show-maximum-output t - "*If non-nil, scroll buffer to keep the point at the bottom of the window." + "*If non-nil, scroll buffer to keep the point at the bottom of +the window." :type 'boolean :group 'rcirc) @@ -285,6 +323,9 @@ and the cdr part is used for encoding." (defvar rcirc-nick-table nil) +(defvar rcirc-recent-quit-alist nil + "Alist of nicks that have recently quit or parted the channel.") + (defvar rcirc-nick-syntax-table (let ((table (make-syntax-table text-mode-syntax-table))) (mapc (lambda (c) (modify-syntax-entry c "w" table)) @@ -319,36 +360,72 @@ and the cdr part is used for encoding." (defconst rcirc-id-string (concat "rcirc on GNU Emacs " emacs-version)) (defvar rcirc-startup-channels nil) + ;;;###autoload (defun rcirc (arg) - "Connect to IRC. -If ARG is non-nil, prompt for a server to connect to." + "Connect to all servers in `rcirc-server-alist'. + +Do not connect to a server if it is already connected. + +If ARG is non-nil, instead prompt for connection parameters." (interactive "P") (if arg - (let* ((server (read-string "IRC Server: " rcirc-default-server)) - (port (read-string "IRC Port: " (number-to-string rcirc-default-port))) - (nick (read-string "IRC Nick: " rcirc-default-nick)) + (let* ((server (completing-read "IRC Server: " + rcirc-server-alist + nil nil + (caar rcirc-server-alist))) + (server-plist (cdr (assoc-string server rcirc-server-alist))) + (port (read-string "IRC Port: " + (number-to-string + (or (plist-get server-plist 'port) + rcirc-default-port)))) + (nick (read-string "IRC Nick: " + (or (plist-get server-plist 'nick) + rcirc-default-nick))) (channels (split-string (read-string "IRC Channels: " - (mapconcat 'identity (rcirc-startup-channels server) " ")) + (mapconcat 'identity + (plist-get server-plist + 'channels) + " ")) "[, ]+" t))) - (rcirc-connect server port nick rcirc-default-user-name rcirc-default-user-full-name + (rcirc-connect server port nick rcirc-default-user-name + rcirc-default-full-name channels)) - ;; make new connection using defaults unless already connected to - ;; the default rcirc-server - (let (connected) - (dolist (p (rcirc-process-list)) - (when (string= rcirc-default-server (process-name p)) - (setq connected p))) - (if (not connected) - (rcirc-connect rcirc-default-server rcirc-default-port - rcirc-default-nick rcirc-default-user-name - rcirc-default-user-full-name - (rcirc-startup-channels rcirc-default-server)) - (switch-to-buffer (process-buffer connected)) - (message "Connected to %s" - (process-contact (get-buffer-process (current-buffer)) - :host)))))) + ;; connect to servers in `rcirc-server-alist' + (let (connected-servers) + (dolist (c rcirc-server-alist) + (let ((server (car c)) + (nick (or (plist-get (cdr c) :nick) rcirc-default-nick)) + (port (or (plist-get (cdr c) :port) rcirc-default-port)) + (user-name (or (plist-get (cdr c) :user-name) + rcirc-default-user-name)) + (full-name (or (plist-get (cdr c) :full-name) + rcirc-default-full-name)) + (channels (plist-get (cdr c) :channels))) + (when server + (let (connected) + (dolist (p (rcirc-process-list)) + (when (string= server (process-name p)) + (setq connected p))) + (if (not connected) + (condition-case e + (rcirc-connect server port nick user-name + full-name channels) + (quit (message "Quit connecting to %s" server))) + (with-current-buffer (process-buffer connected) + (setq connected-servers + (cons (process-contact (get-buffer-process + (current-buffer)) :host) + connected-servers)))))))) + (when connected-servers + (message "Already connected to %s" + (if (cdr connected-servers) + (concat (mapconcat 'identity (butlast connected-servers) ", ") + ", and " + (car (last connected-servers))) + (car connected-servers))))))) + ;;;###autoload (defalias 'irc 'rcirc) @@ -365,7 +442,8 @@ If ARG is non-nil, prompt for a server to connect to." (defvar rcirc-process nil) ;;;###autoload -(defun rcirc-connect (&optional server port nick user-name full-name startup-channels) +(defun rcirc-connect (server &optional port nick user-name full-name + startup-channels) (save-excursion (message "Connecting to %s..." server) (let* ((inhibit-eol-conversion) @@ -374,10 +452,9 @@ If ARG is non-nil, prompt for a server to connect to." (string-to-number port) port) rcirc-default-port)) - (server (or server rcirc-default-server)) (nick (or nick rcirc-default-nick)) (user-name (or user-name rcirc-default-user-name)) - (full-name (or full-name rcirc-default-user-full-name)) + (full-name (or full-name rcirc-default-full-name)) (startup-channels startup-channels) (process (make-network-process :name server :host server :service port-number))) ;; set up process @@ -412,6 +489,8 @@ If ARG is non-nil, prompt for a server to connect to." (make-local-variable 'rcirc-connecting) (setq rcirc-connecting t) + (add-hook 'auto-save-hook 'rcirc-log-write) + ;; identify (rcirc-send-string process (concat "NICK " nick)) (rcirc-send-string process (concat "USER " user-name @@ -446,12 +525,21 @@ last ping." (mapc (lambda (process) (with-rcirc-process-buffer process (when (not rcirc-connecting) - (rcirc-send-string process (concat "PING " (rcirc-server-name process)))))) + (rcirc-send-string process + (format "PRIVMSG %s :\C-aKEEPALIVE %f\C-a" + rcirc-nick + (time-to-seconds + (current-time))))))) (rcirc-process-list)) ;; no processes, clean up timer (cancel-timer rcirc-keepalive-timer) (setq rcirc-keepalive-timer nil))) +(defun rcirc-handler-ctcp-KEEPALIVE (process target sender message) + (with-rcirc-process-buffer process + (setq header-line-format (format "%f" (- (time-to-seconds (current-time)) + (string-to-number message)))))) + (defvar rcirc-debug-buffer " *rcirc debug*") (defvar rcirc-debug-flag nil "If non-nil, write information to `rcirc-debug-buffer'.") @@ -461,14 +549,13 @@ Debug text is written to `rcirc-debug-buffer' if `rcirc-debug-flag' is non-nil." (when rcirc-debug-flag (save-excursion - (save-window-excursion - (set-buffer (get-buffer-create rcirc-debug-buffer)) - (goto-char (point-max)) - (insert (concat - "[" - (format-time-string "%Y-%m-%dT%T ") (process-name process) - "] " - text)))))) + (set-buffer (get-buffer-create rcirc-debug-buffer)) + (goto-char (point-max)) + (insert (concat + "[" + (format-time-string "%Y-%m-%dT%T ") (process-name process) + "] " + text))))) (defvar rcirc-sentinel-hooks nil "Hook functions called when the process sentinel is called. @@ -486,12 +573,16 @@ Functions are called with PROCESS and SENTINEL arguments.") (process-name process) sentinel (process-status process)) (not rcirc-target)) - ;; remove the prompt from buffers - (let ((inhibit-read-only t)) - (delete-region rcirc-prompt-start-marker - rcirc-prompt-end-marker)))) + (rcirc-disconnect-buffer))) (run-hook-with-args 'rcirc-sentinel-hooks process sentinel)))) +(defun rcirc-disconnect-buffer (&optional buffer) + (with-current-buffer (or buffer (current-buffer)) + ;; set rcirc-target to nil for each channel so cleanup + ;; doesnt happen when we reconnect + (setq rcirc-target nil) + (setq mode-line-process ":disconnected"))) + (defun rcirc-process-list () "Return a list of rcirc processes." (let (ps) @@ -530,7 +621,6 @@ Function is called with PROCESS, COMMAND, SENDER, ARGS and LINE.") process)))))) (defun rcirc-delete-process (process) - (message "delete process %S" process) (delete-process process)) (defvar rcirc-trap-errors-flag t) @@ -593,7 +683,8 @@ With no argument or nil as argument, use the current buffer." (defun rcirc-server-name (process) "Return PROCESS server name, given by the 001 response." (with-rcirc-process-buffer process - (or rcirc-server-name rcirc-default-server))) + (or rcirc-server-name + (warn "server name for process %S unknown" process)))) (defun rcirc-nick (process) "Return PROCESS nick." @@ -610,9 +701,10 @@ With no argument or nil as argument, use the current buffer." (defvar rcirc-max-message-length 420 "Messages longer than this value will be split.") -(defun rcirc-send-message (process target message &optional noticep) +(defun rcirc-send-message (process target message &optional noticep silent) "Send TARGET associated with PROCESS a privmsg with text MESSAGE. -If NOTICEP is non-nil, send a notice instead of privmsg." +If NOTICEP is non-nil, send a notice instead of privmsg. +If SILENT is non-nil, do not print the message in any irc buffer." ;; max message length is 512 including CRLF (let* ((response (if noticep "NOTICE" "PRIVMSG")) (oversize (> (length message) rcirc-max-message-length)) @@ -625,8 +717,9 @@ If NOTICEP is non-nil, send a notice instead of privmsg." (more (if oversize (substring message rcirc-max-message-length)))) (rcirc-get-buffer-create process target) - (rcirc-print process (rcirc-nick process) response target text) (rcirc-send-string process (concat response " " target " :" text)) + (unless silent + (rcirc-print process (rcirc-nick process) response target text)) (when more (rcirc-send-message process target more noticep)))) (defvar rcirc-input-ring nil) @@ -676,7 +769,6 @@ If NOTICEP is non-nil, send a notice instead of privmsg." rcirc-target)))))) (let ((completion (car rcirc-nick-completions))) (when completion - (rcirc-put-nick-channel (rcirc-buffer-process) completion rcirc-target) (delete-region (+ rcirc-prompt-end-marker rcirc-nick-completion-start-offset) (point)) @@ -711,7 +803,8 @@ If NOTICEP is non-nil, send a notice instead of privmsg." (define-key rcirc-mode-map (kbd "C-c C-d") 'rcirc-cmd-mode) (define-key rcirc-mode-map (kbd "C-c C-m") 'rcirc-cmd-msg) (define-key rcirc-mode-map (kbd "C-c C-r") 'rcirc-cmd-nick) ; rename -(define-key rcirc-mode-map (kbd "C-c C-o") 'rcirc-cmd-oper) +(define-key rcirc-mode-map (kbd "C-c C-o") 'rcirc-omit-mode) +(define-key rcirc-mode-map (kbd "M-o") 'rcirc-omit-mode) (define-key rcirc-mode-map (kbd "C-c C-p") 'rcirc-cmd-part) (define-key rcirc-mode-map (kbd "C-c C-q") 'rcirc-cmd-query) (define-key rcirc-mode-map (kbd "C-c C-t") 'rcirc-cmd-topic) @@ -737,6 +830,14 @@ If NOTICEP is non-nil, send a notice instead of privmsg." (defvar rcirc-last-post-time nil) +(defvar rcirc-log-alist nil + "Alist of lines to log to disk when `rcirc-log-flag' is non-nil. +Each element looks like (FILENAME . TEXT).") + +(defvar rcirc-current-line 0 + "The current number of responses printed in this channel. +This number is independent of the number of lines in the buffer.") + (defun rcirc-mode (process target) "Major mode for IRC channel buffers. @@ -745,6 +846,7 @@ If NOTICEP is non-nil, send a notice instead of privmsg." (use-local-map rcirc-mode-map) (setq mode-name "rcirc") (setq major-mode 'rcirc-mode) + (setq mode-line-process nil) (make-local-variable 'rcirc-input-ring) (setq rcirc-input-ring (make-ring rcirc-input-ring-size)) @@ -756,12 +858,26 @@ If NOTICEP is non-nil, send a notice instead of privmsg." (setq rcirc-topic nil) (make-local-variable 'rcirc-last-post-time) (setq rcirc-last-post-time (current-time)) + (make-local-variable 'fill-paragraph-function) + (setq fill-paragraph-function 'rcirc-fill-paragraph) + (make-local-variable 'rcirc-recent-quit-alist) + (setq rcirc-recent-quit-alist nil) + (make-local-variable 'rcirc-current-line) + (setq rcirc-current-line 0) (make-local-variable 'rcirc-short-buffer-name) (setq rcirc-short-buffer-name nil) (make-local-variable 'rcirc-urls) (setq use-hard-newlines t) + ;; setup for omitting responses + (setq buffer-invisibility-spec '()) + (setq buffer-display-table (make-display-table)) + (set-display-table-slot buffer-display-table 4 + (let ((glyph (make-glyph-code + ?. 'font-lock-keyword-face))) + (make-vector 3 glyph))) + (make-local-variable 'rcirc-decode-coding-system) (make-local-variable 'rcirc-encode-coding-system) (dolist (i rcirc-coding-system-alist) @@ -873,14 +989,16 @@ If ALL is non-nil, update prompts in all IRC buffers." (when rcirc-target (rcirc-remove-nick-channel (rcirc-buffer-process) (rcirc-buffer-nick) - rcirc-target)))))) + rcirc-target)))) + (setq rcirc-target nil))) (defun rcirc-generate-new-buffer-name (process target) "Return a buffer name based on PROCESS and TARGET. This is used for the initial name given to IRC buffers." - (if target - (concat target "@" (process-name process)) - (concat "*" (process-name process) "*"))) + (substring-no-properties + (if target + (concat target "@" (process-name process)) + (concat "*" (process-name process) "*")))) (defun rcirc-get-buffer (process target &optional server) "Return the buffer associated with the PROCESS and TARGET. @@ -902,14 +1020,15 @@ Create the buffer if it doesn't exist." (when (not rcirc-target) (setq rcirc-target target)) buffer) - ;; create the buffer - (with-rcirc-process-buffer process - (let ((new-buffer (get-buffer-create - (rcirc-generate-new-buffer-name process target)))) - (with-current-buffer new-buffer - (rcirc-mode process target)) - (rcirc-put-nick-channel process (rcirc-nick process) target) - new-buffer))))) + ;; create the buffer + (with-rcirc-process-buffer process + (let ((new-buffer (get-buffer-create + (rcirc-generate-new-buffer-name process target)))) + (with-current-buffer new-buffer + (rcirc-mode process target) + (rcirc-put-nick-channel process (rcirc-nick process) target + rcirc-current-line)) + new-buffer))))) (defun rcirc-send-input () "Send input to target associated with the current buffer." @@ -943,6 +1062,14 @@ Create the buffer if it doesn't exist." (ring-insert rcirc-input-ring input) (setq rcirc-input-ring-index 0)))))) +(defun rcirc-fill-paragraph (&optional arg) + (interactive "p") + (when (> (point) rcirc-prompt-end-marker) + (save-restriction + (narrow-to-region rcirc-prompt-end-marker (point-max)) + (let ((fill-column rcirc-max-message-length)) + (fill-region (point-min) (point-max)))))) + (defun rcirc-process-input-line (line) (if (string-match "^/\\([^ ]+\\) ?\\(.*\\)$" line) (rcirc-process-command (match-string 1 line) @@ -984,7 +1111,8 @@ Create the buffer if it doesn't exist." (interactive) (let ((pos (1+ (- (point) rcirc-prompt-end-marker)))) (goto-char (point-max)) - (let ((text (buffer-substring rcirc-prompt-end-marker (point))) + (let ((text (buffer-substring-no-properties rcirc-prompt-end-marker + (point))) (parent (buffer-name))) (delete-region rcirc-prompt-end-marker (point)) (setq rcirc-window-configuration (current-window-configuration)) @@ -1021,7 +1149,6 @@ Create the buffer if it doesn't exist." (defun rcirc-multiline-minor-submit () "Send the text in buffer back to parent buffer." (interactive) - (assert rcirc-parent-buffer) (untabify (point-min) (point-max)) (let ((text (buffer-substring (point-min) (point-max))) (buffer (current-buffer)) @@ -1052,12 +1179,12 @@ Create the buffer if it doesn't exist." (process-buffer process))))) (defcustom rcirc-response-formats - '(("PRIVMSG" . "%T<%N> %m") - ("NOTICE" . "%T-%N- %m") - ("ACTION" . "%T[%N %m]") - ("COMMAND" . "%T%m") - ("ERROR" . "%T%fw!!! %m") - (t . "%T%fp*** %fs%n %r %m")) + '(("PRIVMSG" . "<%N> %m") + ("NOTICE" . "-%N- %m") + ("ACTION" . "[%N %m]") + ("COMMAND" . "%m") + ("ERROR" . "%fw!!! %m") + (t . "%fp*** %fs%n %r %m")) "An alist of formats used for printing responses. The format is looked up using the response-type as a key; if no match is found, the default entry (with a key of `t') is used. @@ -1069,7 +1196,6 @@ the of the following escape sequences replaced by the described values: %n The sender's nick %N The sender's nick (with face `rcirc-my-nick' or `rcirc-other-nick') %r The response-type - %T The timestamp (with face `rcirc-timestamp') %t The target %fw Following text uses the face `font-lock-warning-face' %fp Following text uses the face `rcirc-server-prefix' @@ -1082,92 +1208,67 @@ the of the following escape sequences replaced by the described values: :value-type string) :group 'rcirc) +(defcustom rcirc-omit-responses + '("JOIN" "PART" "QUIT" "NICK") + "Responses which will be hidden when `rcirc-omit-mode' is enabled." + :type '(repeat string) + :group 'rcirc) + (defun rcirc-format-response-string (process sender response target text) "Return a nicely-formatted response string, incorporating TEXT \(and perhaps other arguments). The specific formatting used is found by looking up RESPONSE in `rcirc-response-formats'." - (let ((chunks - (split-string (or (cdr (assoc response rcirc-response-formats)) - (cdr (assq t rcirc-response-formats))) - "%")) - (sender (or sender "")) - (result "") - (face nil) - key face-key repl) - (when (equal (car chunks) "") - (pop chunks)) - (dolist (chunk chunks) - (if (equal chunk "") - (setq key ?%) - (setq key (aref chunk 0)) - (setq chunk (substring chunk 1))) - (setq repl - (cond ((eq key ?%) - ;; %% -- literal % character - "%") - ((or (eq key ?n) (eq key ?N)) - ;; %n/%N -- nick - (let ((nick (concat (if (string= (rcirc-server-name process) - sender) - "" - sender) - (and target (concat "," target))))) - (rcirc-facify nick - (if (eq key ?n) - face - (cond ((string= sender (rcirc-nick process)) - 'rcirc-my-nick) - ((and rcirc-bright-nicks - (string-match - (regexp-opt rcirc-bright-nicks) - sender)) - 'rcirc-bright-nick) - ((and rcirc-dim-nicks - (string-match - (regexp-opt rcirc-dim-nicks) - sender)) - 'rcirc-dim-nick) - (t - 'rcirc-other-nick)))))) - ((eq key ?T) - ;; %T -- timestamp - (rcirc-facify - (format-time-string rcirc-time-format (current-time)) - 'rcirc-timestamp)) - ((eq key ?m) - ;; %m -- message text - (rcirc-markup-text process sender response (rcirc-facify text face))) - ((eq key ?t) - ;; %t -- target - (rcirc-facify (or rcirc-target "") face)) - ((eq key ?r) - ;; %r -- response - (rcirc-facify response face)) - ((eq key ?f) - ;; %f -- change face - (setq face-key (aref chunk 0)) - (setq chunk (substring chunk 1)) - (cond ((eq face-key ?w) - ;; %fw -- warning face - (setq face 'font-lock-warning-face)) - ((eq face-key ?p) - ;; %fp -- server-prefix face - (setq face 'rcirc-server-prefix)) - ((eq face-key ?s) - ;; %fs -- warning face - (setq face 'rcirc-server)) - ((eq face-key ?-) - ;; %fs -- warning face - (setq face nil)) - ((and (eq face-key ?\[) - (string-match "^\\([^]]*\\)[]]" chunk) - (facep (match-string 1 chunk))) - ;; %f[...] -- named face - (setq face (intern (match-string 1 chunk))) - (setq chunk (substring chunk (match-end 0))))) - ""))) - (setq result (concat result repl (rcirc-facify chunk face)))) - result)) + (with-temp-buffer + (insert (or (cdr (assoc response rcirc-response-formats)) + (cdr (assq t rcirc-response-formats)))) + (goto-char (point-min)) + (let ((start (point-min)) + (sender (if (or (not sender) + (string= (rcirc-server-name process) sender)) + "" + sender)) + face) + (while (re-search-forward "%\\(\\(f\\(.\\)\\)\\|\\(.\\)\\)" nil t) + (rcirc-add-face start (match-beginning 0) face) + (setq start (match-beginning 0)) + (replace-match + (case (aref (match-string 1) 0) + (?f (setq face + (case (string-to-char (match-string 3)) + (?w 'font-lock-warning-face) + (?p 'rcirc-server-prefix) + (?s 'rcirc-server) + (t nil))) + "") + (?n sender) + (?N (let ((my-nick (rcirc-nick process))) + (save-match-data + (with-syntax-table rcirc-nick-syntax-table + (rcirc-facify sender + (cond ((string= sender my-nick) + 'rcirc-my-nick) + ((and rcirc-bright-nicks + (string-match + (regexp-opt rcirc-bright-nicks + 'words) + sender)) + 'rcirc-bright-nick) + ((and rcirc-dim-nicks + (string-match + (regexp-opt rcirc-dim-nicks + 'words) + sender)) + 'rcirc-dim-nick) + (t + 'rcirc-other-nick))))))) + (?m (propertize text 'rcirc-text text)) + (?r response) + (?t (or target "")) + (t (concat "UNKNOWN CODE:" (match-string 0)))) + t t nil 0) + (rcirc-add-face (match-beginning 0) (match-end 0) face)) + (rcirc-add-face start (match-beginning 0) face)) + (buffer-substring (point-min) (point-max)))) (defun rcirc-target-buffer (process sender response target text) "Return a buffer to print the server response." @@ -1177,7 +1278,8 @@ is found by looking up RESPONSE in `rcirc-response-formats'." (rcirc-any-buffer process)) ((not (rcirc-channel-p target)) ;; message from another user - (if (string= response "PRIVMSG") + (if (or (string= response "PRIVMSG") + (string= response "ACTION")) (rcirc-get-buffer-create process (if (string= sender rcirc-nick) target sender)) @@ -1190,16 +1292,74 @@ is found by looking up RESPONSE in `rcirc-response-formats'." (defvar rcirc-last-sender nil) (make-variable-buffer-local 'rcirc-last-sender) +(defcustom rcirc-log-directory "~/.emacs.d/rcirc-log" + "Directory to keep IRC logfiles." + :type 'directory + :group 'rcirc) + +(defcustom rcirc-log-flag nil + "Non-nil means log IRC activity to disk. +Logfiles are kept in `rcirc-log-directory'." + :type 'boolean + :group 'rcirc) + +(defcustom rcirc-omit-threshold 100 + "Number of lines since last activity from a nick before `rcirc-omit-responses' are omitted." + :type 'integer + :group 'rcirc) + +(defun rcirc-last-quit-line (process nick target) + "Return the line number where NICK left TARGET. +Returns nil if the information is not recorded." + (let ((chanbuf (rcirc-get-buffer process target))) + (when chanbuf + (cdr (assoc-string nick (with-current-buffer chanbuf + rcirc-recent-quit-alist)))))) + +(defun rcirc-last-line (process nick target) + "Return the line from the last activity from NICK in TARGET." + (let* ((chanbuf (rcirc-get-buffer process target)) + (line (or (cdr (assoc-string target + (gethash nick (with-rcirc-server-buffer + rcirc-nick-table)) t)) + (rcirc-last-quit-line process nick target)))) + (if line + line + ;;(message "line is nil for %s in %s" nick target) + nil))) + +(defun rcirc-elapsed-lines (process nick target) + "Return the number of lines since activity from NICK in TARGET." + (let ((last-activity-line (rcirc-last-line process nick target))) + (when (and last-activity-line + (> last-activity-line 0)) + (- rcirc-current-line last-activity-line)))) + +(defvar rcirc-markup-text-functions + '(rcirc-markup-attributes + rcirc-markup-my-nick + rcirc-markup-urls + rcirc-markup-keywords + rcirc-markup-bright-nicks) + + "List of functions used to manipulate text before it is printed. + +Each function takes two arguments, SENDER, and RESPONSE. The +buffer is narrowed with the text to be printed and the point is +at the beginning of the `rcirc-text' propertized text.") + (defun rcirc-print (process sender response target text &optional activity) "Print TEXT in the buffer associated with TARGET. Format based on SENDER and RESPONSE. If ACTIVITY is non-nil, record activity." (or text (setq text "")) - (unless (or (member sender rcirc-ignore-list) - (member (with-syntax-table rcirc-nick-syntax-table - (when (string-match "^\\([^/]\\w*\\)[:,]" text) - (match-string 1 text))) - rcirc-ignore-list)) + (unless (and (or (member sender rcirc-ignore-list) + (member (with-syntax-table rcirc-nick-syntax-table + (when (string-match "^\\([^/]\\w*\\)[:,]" text) + (match-string 1 text))) + rcirc-ignore-list)) + ;; do not ignore if we sent the message + (not (string= sender (rcirc-nick process)))) (let* ((buffer (rcirc-target-buffer process sender response target text)) (inhibit-read-only t)) (with-current-buffer buffer @@ -1212,7 +1372,8 @@ record activity." (setq text (decode-coding-string text rcirc-decode-coding-system)) ;; mark the line with overlay arrow (unless (or (marker-position overlay-arrow-position) - (get-buffer-window (current-buffer))) + (get-buffer-window (current-buffer)) + (member response rcirc-omit-responses)) (set-marker overlay-arrow-position (marker-position rcirc-prompt-start-marker)))) @@ -1222,44 +1383,46 @@ record activity." (set-marker-insertion-type rcirc-prompt-start-marker t) (set-marker-insertion-type rcirc-prompt-end-marker t) - (let ((fmted-text - (rcirc-format-response-string process sender response nil - text))) - - (insert fmted-text (propertize "\n" 'hard t)) - (set-marker-insertion-type rcirc-prompt-start-marker nil) - (set-marker-insertion-type rcirc-prompt-end-marker nil) - - (let ((text-start (make-marker))) - (set-marker text-start - (or (next-single-property-change fill-start - 'rcirc-text) - rcirc-prompt-end-marker)) - ;; squeeze spaces out of text before rcirc-text - (fill-region fill-start (1- text-start)) - - ;; fill the text we just inserted, maybe - (when (and rcirc-fill-flag - (not (string= response "372"))) ;/motd - (let ((fill-prefix - (or rcirc-fill-prefix - (make-string (- text-start fill-start) ?\s))) - (fill-column (cond ((eq rcirc-fill-column 'frame-width) - (1- (frame-width))) - ((eq rcirc-fill-column 'window-width) - (1- (window-width))) - (rcirc-fill-column - rcirc-fill-column) - (t fill-column)))) - (fill-region fill-start rcirc-prompt-start-marker 'left t))))) - - ;; set inserted text to be read-only - (when rcirc-read-only-flag - (put-text-property rcirc-prompt-start-marker fill-start 'read-only t) - (let ((inhibit-read-only t)) - (put-text-property rcirc-prompt-start-marker fill-start - 'front-sticky t) - (put-text-property (1- (point)) (point) 'rear-nonsticky t))) + (let ((start (point))) + (insert (rcirc-format-response-string process sender response nil + text) + (propertize "\n" 'hard t)) + + ;; squeeze spaces out of text before rcirc-text + (fill-region fill-start + (1- (or (next-single-property-change fill-start + 'rcirc-text) + rcirc-prompt-end-marker))) + + ;; run markup functions + (save-excursion + (save-restriction + (narrow-to-region start rcirc-prompt-start-marker) + (goto-char (or (next-single-property-change start 'rcirc-text) + (point))) + (when (rcirc-buffer-process) + (save-excursion (rcirc-markup-timestamp sender response)) + (dolist (fn rcirc-markup-text-functions) + (save-excursion (funcall fn sender response))) + (when rcirc-fill-flag + (save-excursion (rcirc-markup-fill sender response)))) + + (when rcirc-read-only-flag + (add-text-properties (point-min) (point-max) + '(read-only t front-sticky t)))) + ;; make text omittable + (let ((last-activity-lines (rcirc-elapsed-lines process sender target))) + (if (and (not (string= (rcirc-nick process) sender)) + (member response rcirc-omit-responses) + (or (not last-activity-lines) + (< rcirc-omit-threshold last-activity-lines))) + (put-text-property (1- start) (1- rcirc-prompt-start-marker) + 'invisible 'rcirc-omit) + ;; otherwise increment the line count + (setq rcirc-current-line (1+ rcirc-current-line)))))) + + (set-marker-insertion-type rcirc-prompt-start-marker nil) + (set-marker-insertion-type rcirc-prompt-end-marker nil) ;; truncate buffer if it is very long (save-excursion @@ -1275,27 +1438,26 @@ record activity." (window-buffer w)) (>= (window-point w) rcirc-prompt-end-marker)) - (set-window-point w (point-max)))) + (set-window-point w (point-max)))) nil t) ;; restore the point (goto-char (if moving rcirc-prompt-end-marker old-point)) - ;; keep window on bottom line if it was already there + ;; keep window on bottom line if it was already there (when rcirc-scroll-show-maximum-output (walk-windows (lambda (w) (when (eq (window-buffer w) (current-buffer)) (with-current-buffer (window-buffer w) (when (eq major-mode 'rcirc-mode) (with-selected-window w - (when (<= (- (window-height) - (count-screen-lines - (window-point) - (window-start)) + (when (<= (- (window-height) + (count-screen-lines (window-point) + (window-start)) 1) 0) (recenter -1))))))) - nil t)) + nil t)) ;; flush undo (can we do something smarter here?) (buffer-disable-undo) @@ -1305,22 +1467,61 @@ record activity." (when (and activity (not rcirc-ignore-buffer-activity-flag) (not (and rcirc-dim-nicks sender - (string-match (regexp-opt rcirc-dim-nicks) sender)))) + (string-match (regexp-opt rcirc-dim-nicks) sender) + (rcirc-channel-p target)))) (rcirc-record-activity (current-buffer) (when (not (rcirc-channel-p rcirc-target)) 'nick))) + (when rcirc-log-flag + (rcirc-log process sender response target text)) + (sit-for 0) ; displayed text before hook (run-hook-with-args 'rcirc-print-hooks process sender response target text))))) -(defun rcirc-startup-channels (server) - "Return the list of startup channels for SERVER." - (let (channels) - (dolist (i rcirc-startup-channels-alist) - (if (string-match (car i) server) - (setq channels (append channels (cdr i))))) - channels)) +(defcustom rcirc-log-filename-function 'rcirc-generate-new-buffer-name + "A function to generate the filename used by rcirc's logging facility. + +It is called with two arguments, PROCESS and TARGET (see +`rcirc-generate-new-buffer-name' for their meaning), and should +return the filename, or nil if no logging is desired for this +session. + +If the returned filename is absolute (`file-name-absolute-p' +returns true), then it is used as-is, otherwise the resulting +file is put into `rcirc-log-directory'." + :group 'rcirc + :type 'function) + +(defun rcirc-log (process sender response target text) + "Record line in `rcirc-log', to be later written to disk." + (let ((filename (funcall rcirc-log-filename-function process target))) + (unless (null filename) + (let ((cell (assoc-string filename rcirc-log-alist)) + (line (concat (format-time-string rcirc-time-format) + (substring-no-properties + (rcirc-format-response-string process sender + response target text)) + "\n"))) + (if cell + (setcdr cell (concat (cdr cell) line)) + (setq rcirc-log-alist + (cons (cons filename line) rcirc-log-alist))))))) + +(defun rcirc-log-write () + "Flush `rcirc-log-alist' data to disk. + +Log data is written to `rcirc-log-directory', except for +log-files with absolute names (see `rcirc-log-filename-function')." + (dolist (cell rcirc-log-alist) + (let ((filename (expand-file-name (car cell) rcirc-log-directory)) + (coding-system-for-write 'utf-8)) + (make-directory (file-name-directory filename) t) + (with-temp-buffer + (insert (cdr cell)) + (write-region (point-min) (point-max) filename t 'quiet)))) + (setq rcirc-log-alist nil)) (defun rcirc-join-channels (process channels) "Join CHANNELS." @@ -1345,15 +1546,19 @@ record activity." (mapcar (lambda (x) (car x)) (gethash nick rcirc-nick-table)))) -(defun rcirc-put-nick-channel (process nick channel) - "Add CHANNEL to list associated with NICK." +(defun rcirc-put-nick-channel (process nick channel &optional line) + "Add CHANNEL to list associated with NICK. +Update the associated linestamp if LINE is non-nil. + +If the record doesn't exist, and LINE is nil, set the linestamp +to zero." (let ((nick (rcirc-user-nick nick))) (with-rcirc-process-buffer process (let* ((chans (gethash nick rcirc-nick-table)) (record (assoc-string channel chans t))) (if record - (setcdr record (current-time)) - (puthash nick (cons (cons channel (current-time)) + (when line (setcdr record line)) + (puthash nick (cons (cons channel (or line 0)) chans) rcirc-nick-table)))))) @@ -1389,7 +1594,10 @@ record activity." (setq nicks (cons (cons k (cdr record)) nicks))))) rcirc-nick-table) (mapcar (lambda (x) (car x)) - (sort nicks (lambda (x y) (time-less-p (cdr y) (cdr x))))))) + (sort nicks (lambda (x y) + (let ((lx (or (cdr x) 0)) + (ly (or (cdr y) 0))) + (< ly lx))))))) (list target)))) (defun rcirc-ignore-update-automatic (nick) @@ -1437,6 +1645,9 @@ if NICK is also on `rcirc-ignore-list-automatic'." (or (assq 'rcirc-low-priority-flag minor-mode-alist) (setq minor-mode-alist (cons '(rcirc-low-priority-flag " LowPri") minor-mode-alist))) +(or (assq 'rcirc-omit-mode minor-mode-alist) + (setq minor-mode-alist + (cons '(rcirc-omit-mode " Omit") minor-mode-alist))) (defun rcirc-toggle-ignore-buffer-activity () "Toggle the value of `rcirc-ignore-buffer-activity-flag'." @@ -1458,30 +1669,48 @@ if NICK is also on `rcirc-ignore-list-automatic'." "Activity in this buffer is normal priority")) (force-mode-line-update)) -(defvar rcirc-switch-to-buffer-function 'switch-to-buffer - "Function to use when switching buffers. -Possible values are `switch-to-buffer', `pop-to-buffer', and -`display-buffer'.") +(defun rcirc-omit-mode () + "Toggle the Rcirc-Omit mode. +If enabled, \"uninteresting\" lines are not shown. +Uninteresting lines are those whose responses are listed in +`rcirc-omit-responses'." + (interactive) + (setq rcirc-omit-mode (not rcirc-omit-mode)) + (if rcirc-omit-mode + (progn + (add-to-invisibility-spec '(rcirc-omit . t)) + (message "Rcirc-Omit mode enabled")) + (remove-from-invisibility-spec '(rcirc-omit . t)) + (message "Rcirc-Omit mode disabled")) + (recenter (when (> (point) rcirc-prompt-start-marker) -1))) (defun rcirc-switch-to-server-buffer () "Switch to the server buffer associated with current channel buffer." (interactive) - (funcall rcirc-switch-to-buffer-function rcirc-server-buffer)) + (switch-to-buffer rcirc-server-buffer)) (defun rcirc-jump-to-first-unread-line () "Move the point to the first unread line in this buffer." (interactive) - (when (marker-position overlay-arrow-position) - (goto-char overlay-arrow-position))) - -(defvar rcirc-last-non-irc-buffer nil - "The buffer to switch to when there is no more activity.") + (if (marker-position overlay-arrow-position) + (goto-char overlay-arrow-position) + (message "No unread messages"))) + +(defun rcirc-non-irc-buffer () + (let ((buflist (buffer-list)) + buffer) + (while (and buflist (not buffer)) + (with-current-buffer (car buflist) + (unless (or (eq major-mode 'rcirc-mode) + (= ?\s (aref (buffer-name) 0)) ; internal buffers + (get-buffer-window (current-buffer))) + (setq buffer (current-buffer)))) + (setq buflist (cdr buflist))) + buffer)) (defun rcirc-next-active-buffer (arg) - "Go to the next rcirc buffer with activity. -With prefix ARG, go to the next low priority buffer with activity. -The function given by `rcirc-switch-to-buffer-function' is used to -show the buffer." + "Switch to the next rcirc buffer with activity. +With prefix ARG, go to the next low priority buffer with activity." (interactive "P") (let* ((pair (rcirc-split-activity rcirc-activity)) (lopri (car pair)) @@ -1489,24 +1718,18 @@ show the buffer." (if (or (and (not arg) hipri) (and arg lopri)) (progn - (unless (eq major-mode 'rcirc-mode) - (setq rcirc-last-non-irc-buffer (current-buffer))) - (funcall rcirc-switch-to-buffer-function - (car (if arg lopri hipri)))) + (switch-to-buffer (car (if arg lopri hipri))) + (when (> (point) rcirc-prompt-start-marker) + (recenter -1))) (if (eq major-mode 'rcirc-mode) - (if (not (and rcirc-last-non-irc-buffer - (buffer-live-p rcirc-last-non-irc-buffer))) - (message "No IRC activity. Start something.") - (message "No more IRC activity. Go back to work.") - (funcall rcirc-switch-to-buffer-function rcirc-last-non-irc-buffer) - (setq rcirc-last-non-irc-buffer nil)) - (message (concat - "No IRC activity." - (when lopri - (concat - " Type C-u " - (key-description (this-command-keys)) - " for low priority activity.")))))))) + (switch-to-buffer (rcirc-non-irc-buffer)) + (message "%s" (concat + "No IRC activity." + (when lopri + (concat + " Type C-u " + (key-description (this-command-keys)) + " for low priority activity.")))))))) (defvar rcirc-activity-hooks nil "Hook to be run when there is channel activity. @@ -1518,23 +1741,33 @@ activity. Only run if the buffer is not visible and (defun rcirc-record-activity (buffer &optional type) "Record BUFFER activity with TYPE." (with-current-buffer buffer - (when (not (get-buffer-window (current-buffer) t)) - (setq rcirc-activity - (sort (add-to-list 'rcirc-activity (current-buffer)) - (lambda (b1 b2) - (let ((t1 (with-current-buffer b1 rcirc-last-post-time)) - (t2 (with-current-buffer b2 rcirc-last-post-time))) - (time-less-p t2 t1))))) - (pushnew type rcirc-activity-types) - (rcirc-update-activity-string))) + (let ((old-activity rcirc-activity) + (old-types rcirc-activity-types)) + (when (not (get-buffer-window (current-buffer) t)) + (setq rcirc-activity + (sort (add-to-list 'rcirc-activity (current-buffer)) + (lambda (b1 b2) + (let ((t1 (with-current-buffer b1 rcirc-last-post-time)) + (t2 (with-current-buffer b2 rcirc-last-post-time))) + (time-less-p t2 t1))))) + (pushnew type rcirc-activity-types) + (unless (and (equal rcirc-activity old-activity) + (member type old-types)) + (rcirc-update-activity-string))))) (run-hook-with-args 'rcirc-activity-hooks buffer)) (defun rcirc-clear-activity (buffer) "Clear the BUFFER activity." - (setq rcirc-activity (delete buffer rcirc-activity)) + (setq rcirc-activity (remove buffer rcirc-activity)) (with-current-buffer buffer (setq rcirc-activity-types nil))) +(defun rcirc-clear-unread (buffer) + "Erase the last read message arrow from BUFFER." + (when (buffer-live-p buffer) + (with-current-buffer buffer + (set-marker overlay-arrow-position nil)))) + (defun rcirc-split-activity (activity) "Return a cons cell with ACTIVITY split into (lopri . hipri)." (let (lopri hipri) @@ -1546,6 +1779,9 @@ activity. Only run if the buffer is not visible and (add-to-list 'hipri buf t)))) (cons lopri hipri))) +(defvar rcirc-update-activity-string-hook nil + "Hook run whenever the activity string is updated.") + ;; TODO: add mouse properties (defun rcirc-update-activity-string () "Update mode-line string." @@ -1554,19 +1790,18 @@ activity. Only run if the buffer is not visible and (hipri (cdr pair))) (setq rcirc-activity-string (cond ((or hipri lopri) - (concat "-" - (and hipri "[") + (concat (and hipri "[") (rcirc-activity-string hipri) (and hipri lopri ",") (and lopri (concat "(" (rcirc-activity-string lopri) ")")) - (and hipri "]") - "-")) + (and hipri "]"))) ((not (null (rcirc-process-list))) - "-[]-") - (t ""))))) + "[]") + (t "[]"))) + (run-hooks 'rcirc-update-activity-string-hook))) (defun rcirc-activity-string (buffers) (mapconcat (lambda (b) @@ -1586,33 +1821,47 @@ activity. Only run if the buffer is not visible and (with-current-buffer buffer (or rcirc-short-buffer-name (buffer-name)))) -(defvar rcirc-current-buffer nil) -(defun rcirc-window-configuration-change () - "Go through visible windows and remove buffers from activity list. -Also, clear the overlay arrow if the current buffer is now hidden." - (let ((current-now-hidden t)) +(defun rcirc-visible-buffers () + "Return a list of the visible buffers that are in rcirc-mode." + (let (acc) (walk-windows (lambda (w) - (let ((buf (window-buffer w))) - (with-current-buffer buf - (when (eq major-mode 'rcirc-mode) - (rcirc-clear-activity buf))) - (when (eq buf rcirc-current-buffer) - (setq current-now-hidden nil))))) - ;; add overlay arrow if the buffer isn't displayed - (when (and current-now-hidden - rcirc-current-buffer - (buffer-live-p rcirc-current-buffer)) - (with-current-buffer rcirc-current-buffer - (when (and (eq major-mode 'rcirc-mode) - (marker-position overlay-arrow-position)) - (set-marker overlay-arrow-position nil))))) - - ;; remove any killed buffers from list - (setq rcirc-activity - (delq nil (mapcar (lambda (buf) (when (buffer-live-p buf) buf)) - rcirc-activity))) - (rcirc-update-activity-string) - (setq rcirc-current-buffer (current-buffer))) + (with-current-buffer (window-buffer w) + (when (eq major-mode 'rcirc-mode) + (push (current-buffer) acc))))) + acc)) + +(defvar rcirc-visible-buffers nil) +(defun rcirc-window-configuration-change () + (unless (minibuffer-window-active-p (minibuffer-window)) + ;; delay this until command has finished to make sure window is + ;; actually visible before clearing activity + (add-hook 'post-command-hook 'rcirc-window-configuration-change-1))) + +(defun rcirc-window-configuration-change-1 () + ;; clear activity and overlay arrows + (let* ((old-activity rcirc-activity) + (hidden-buffers rcirc-visible-buffers)) + + (setq rcirc-visible-buffers (rcirc-visible-buffers)) + + (dolist (vbuf rcirc-visible-buffers) + (setq hidden-buffers (delq vbuf hidden-buffers)) + ;; clear activity for all visible buffers + (rcirc-clear-activity vbuf)) + + ;; clear unread arrow from recently hidden buffers + (dolist (hbuf hidden-buffers) + (rcirc-clear-unread hbuf)) + + ;; remove any killed buffers from list + (setq rcirc-activity + (delq nil (mapcar (lambda (buf) (when (buffer-live-p buf) buf)) + rcirc-activity))) + ;; update the mode-line string + (unless (equal old-activity rcirc-activity) + (rcirc-update-activity-string))) + + (remove-hook 'post-command-hook 'rcirc-window-configuration-change-1)) ;;; buffer name abbreviation @@ -1722,8 +1971,9 @@ Also, clear the overlay arrow if the current buffer is now hidden." (car (split-string channel))))) (rcirc-send-string process (concat "JOIN " channel)) (when (not (eq (selected-window) (minibuffer-window))) - (funcall rcirc-switch-to-buffer-function buffer)))) + (switch-to-buffer buffer)))) +;; TODO: /part #channel reason, or consider removing #channel altogether (defun-rcirc-command part (channel) "Part CHANNEL." (interactive "sPart channel: ") @@ -1902,7 +2152,7 @@ keywords when no KEYWORD is given." word-boundary)) (optional (and "/" - (1+ (char "-a-zA-Z0-9_=!?#$\@~`%&*+|\\/:;.,{}[]()")) + (1+ (char "-a-zA-Z0-9_='!?#$\@~`%&*+|\\/:;.,{}[]()")) (char "-a-zA-Z0-9_=#$\@~`%&*+|\\/:;{}[]()"))))) "Regexp matching URLs. Set to nil to disable URL features in rcirc.") @@ -1931,39 +2181,12 @@ keywords when no KEYWORD is given." (rcirc-browse-url-at-point (posn-point position))))) -(defvar rcirc-markup-text-functions - '(rcirc-markup-body-text - rcirc-markup-attributes - rcirc-markup-my-nick - rcirc-markup-urls - rcirc-markup-keywords - rcirc-markup-bright-nicks) - "List of functions used to manipulate text before it is printed. - -Each function takes three arguments, PROCESS, SENDER, RESPONSE -and CHANNEL-BUFFER. The current buffer is temporary buffer that -contains the text to manipulate. Each function works on the text -in this buffer.") +(defun rcirc-markup-timestamp (sender response) + (goto-char (point-min)) + (insert (rcirc-facify (format-time-string rcirc-time-format) + 'rcirc-timestamp))) -(defun rcirc-markup-text (process sender response text) - "Return TEXT with properties added based on various patterns." - (let ((channel-buffer (current-buffer))) - (with-temp-buffer - (insert text) - (goto-char (point-min)) - (dolist (fn rcirc-markup-text-functions) - (save-excursion - (funcall fn process sender response channel-buffer))) - (buffer-substring (point-min) (point-max))))) - -(defun rcirc-markup-body-text (process sender response channel-buffer) - ;; We add the text property `rcirc-text' to identify this as the - ;; body text. - (add-text-properties (point-min) (point-max) - (list 'rcirc-text (buffer-substring-no-properties - (point-min) (point-max))))) - -(defun rcirc-markup-attributes (process sender response channel-buffer) +(defun rcirc-markup-attributes (sender response) (while (re-search-forward "\\([\C-b\C-_\C-v]\\).*?\\(\\1\\|\C-o\\)" nil t) (rcirc-add-face (match-beginning 0) (match-end 0) (case (char-after (match-beginning 1)) @@ -1979,19 +2202,21 @@ in this buffer.") (while (re-search-forward "\C-o+" nil t) (delete-region (match-beginning 0) (match-end 0)))) -(defun rcirc-markup-my-nick (process sender response channel-buffer) +(defun rcirc-markup-my-nick (sender response) (with-syntax-table rcirc-nick-syntax-table (while (re-search-forward (concat "\\b" - (regexp-quote (rcirc-nick process)) + (regexp-quote (rcirc-nick + (rcirc-buffer-process))) "\\b") nil t) (rcirc-add-face (match-beginning 0) (match-end 0) 'rcirc-nick-in-message) (when (string= response "PRIVMSG") - (rcirc-add-face (point-min) (point-max) 'rcirc-nick-in-message-full-line) - (rcirc-record-activity channel-buffer 'nick))))) + (rcirc-add-face (point-min) (point-max) + 'rcirc-nick-in-message-full-line) + (rcirc-record-activity (current-buffer) 'nick))))) -(defun rcirc-markup-urls (process sender response channel-buffer) +(defun rcirc-markup-urls (sender response) (while (re-search-forward rcirc-url-regexp nil t) (let ((start (match-beginning 0)) (end (match-end 0))) @@ -1999,30 +2224,43 @@ in this buffer.") (add-text-properties start end (list 'mouse-face 'highlight 'keymap rcirc-browse-url-map)) ;; record the url - (let ((url (buffer-substring-no-properties start end))) - (with-current-buffer channel-buffer - (push url rcirc-urls)))))) - -(defun rcirc-markup-keywords (process sender response channel-buffer) - (let* ((target (with-current-buffer channel-buffer (or rcirc-target ""))) - (keywords (delq nil (mapcar (lambda (keyword) - (when (not (string-match keyword target)) - keyword)) - rcirc-keywords)))) - (when keywords - (while (re-search-forward (regexp-opt keywords 'words) nil t) - (rcirc-add-face (match-beginning 0) (match-end 0) 'rcirc-keyword) - (when (and (string= response "PRIVMSG") - (not (string= sender (rcirc-nick process)))) - (rcirc-record-activity channel-buffer 'keyword)))))) - -(defun rcirc-markup-bright-nicks (process sender response channel-buffer) + (push (buffer-substring-no-properties start end) rcirc-urls)))) + +(defun rcirc-markup-keywords (sender response) + (when (and (string= response "PRIVMSG") + (not (string= sender (rcirc-nick (rcirc-buffer-process))))) + (let* ((target (or rcirc-target "")) + (keywords (delq nil (mapcar (lambda (keyword) + (when (not (string-match keyword + target)) + keyword)) + rcirc-keywords)))) + (when keywords + (while (re-search-forward (regexp-opt keywords 'words) nil t) + (rcirc-add-face (match-beginning 0) (match-end 0) 'rcirc-keyword) + (rcirc-record-activity (current-buffer) 'keyword)))))) + +(defun rcirc-markup-bright-nicks (sender response) (when (and rcirc-bright-nicks (string= response "NAMES")) (with-syntax-table rcirc-nick-syntax-table (while (re-search-forward (regexp-opt rcirc-bright-nicks 'words) nil t) (rcirc-add-face (match-beginning 0) (match-end 0) 'rcirc-bright-nick))))) + +(defun rcirc-markup-fill (sender response) + (when (not (string= response "372")) ; /motd + (let ((fill-prefix + (or rcirc-fill-prefix + (make-string (- (point) (line-beginning-position)) ?\s))) + (fill-column (- (cond ((eq rcirc-fill-column 'frame-width) + (1- (frame-width))) + (rcirc-fill-column + rcirc-fill-column) + (t fill-column)) + ;; make sure ... doesn't cause line wrapping + 3))) + (fill-region (point) (point-max) nil t)))) ;;; handlers ;; these are called with the server PROCESS, the SENDER, which is a @@ -2031,7 +2269,6 @@ in this buffer.") ;; verbatim (defun rcirc-handler-001 (process sender args text) (rcirc-handler-generic process "001" sender args text) - ;; set the real server name (with-rcirc-process-buffer process (setq rcirc-connecting nil) (rcirc-reschedule-timeout process) @@ -2049,9 +2286,9 @@ in this buffer.") (if (string-match "^\C-a\\(.*\\)\C-a$" message) (rcirc-handler-CTCP process target sender (match-string 1 message)) (rcirc-print process sender "PRIVMSG" target message t)) - ;; update nick timestamp - (if (member target (rcirc-nick-channels process sender)) - (rcirc-put-nick-channel process sender target)))) + ;; update nick linestamp + (with-current-buffer (rcirc-get-buffer process target t) + (rcirc-put-nick-channel process sender target rcirc-current-line)))) (defun rcirc-handler-NOTICE (process sender args text) (let ((target (car args)) @@ -2076,21 +2313,29 @@ in this buffer.") (defun rcirc-handler-JOIN (process sender args text) (let ((channel (car args))) - (rcirc-get-buffer-create process channel) + (with-current-buffer (rcirc-get-buffer-create process channel) + ;; when recently rejoining, restore the linestamp + (rcirc-put-nick-channel process sender channel + (let ((last-activity-lines + (rcirc-elapsed-lines process sender channel))) + (when (and last-activity-lines + (< last-activity-lines rcirc-omit-threshold)) + (rcirc-last-line process sender channel))))) + (rcirc-print process sender "JOIN" channel "") ;; print in private chat buffer if it exists (when (rcirc-get-buffer (rcirc-buffer-process) sender) - (rcirc-print process sender "JOIN" sender channel)) - - (rcirc-put-nick-channel process sender channel))) + (rcirc-print process sender "JOIN" sender channel)))) ;; PART and KICK are handled the same way (defun rcirc-handler-PART-or-KICK (process response channel sender nick args) (rcirc-ignore-update-automatic nick) (if (not (string= nick (rcirc-nick process))) ;; this is someone else leaving - (rcirc-remove-nick-channel process nick channel) + (progn + (rcirc-maybe-remember-nick-quit process nick channel) + (rcirc-remove-nick-channel process nick channel)) ;; this is us leaving (mapc (lambda (n) (rcirc-remove-nick-channel process n channel)) @@ -2099,8 +2344,7 @@ in this buffer.") ;; if the buffer is still around, make it inactive (let ((buffer (rcirc-get-buffer process channel))) (when buffer - (with-current-buffer buffer - (setq rcirc-target nil)))))) + (rcirc-disconnect-buffer buffer))))) (defun rcirc-handler-PART (process sender args text) (let* ((channel (car args)) @@ -2125,16 +2369,30 @@ in this buffer.") (rcirc-handler-PART-or-KICK process "KICK" channel sender nick reason))) +(defun rcirc-maybe-remember-nick-quit (process nick channel) + "Remember NICK as leaving CHANNEL if they recently spoke." + (let ((elapsed-lines (rcirc-elapsed-lines process nick channel))) + (when (and elapsed-lines + (< elapsed-lines rcirc-omit-threshold)) + (let ((buffer (rcirc-get-buffer process channel))) + (when buffer + (with-current-buffer buffer + (let ((record (assoc-string nick rcirc-recent-quit-alist t)) + (line (rcirc-last-line process nick channel))) + (if record + (setcdr record line) + (setq rcirc-recent-quit-alist + (cons (cons nick line) + rcirc-recent-quit-alist)))))))))) + (defun rcirc-handler-QUIT (process sender args text) (rcirc-ignore-update-automatic sender) (mapc (lambda (channel) - (rcirc-print process sender "QUIT" channel (apply 'concat args))) + ;; broadcast quit message each channel + (rcirc-print process sender "QUIT" channel (apply 'concat args)) + ;; record nick in quit table if they recently spoke + (rcirc-maybe-remember-nick-quit process sender channel)) (rcirc-nick-channels process sender)) - - ;; print in private chat buffer if it exists - (when (rcirc-get-buffer (rcirc-buffer-process) sender) - (rcirc-print process sender "QUIT" sender (apply 'concat args))) - (rcirc-nick-remove process sender)) (defun rcirc-handler-NICK (process sender args text) @@ -2169,7 +2427,7 @@ in this buffer.") (when rcirc-auto-authenticate-flag (rcirc-authenticate)))))) (defun rcirc-handler-PING (process sender args text) - (rcirc-send-string process (concat "PONG " (car args)))) + (rcirc-send-string process (concat "PONG :" (car args)))) (defun rcirc-handler-PONG (process sender args text) ;; do nothing @@ -2289,7 +2547,7 @@ Passwords are stored in `rcirc-authinfo' (which see)." process (concat "PRIVMSG chanserv :identify " - (cadr args) " " (car args)))) + (car args) " " (cadr args)))) ((equal method 'bitlbee) (rcirc-send-string process @@ -2314,7 +2572,8 @@ Passwords are stored in `rcirc-authinfo' (which see)." (format "%s sent unsupported ctcp: %s" sender text) t) (funcall handler process target sender args) - (if (not (string= request "ACTION")) + (unless (or (string= request "ACTION") + (string= request "KEEPALIVE")) (rcirc-print process sender "CTCP" target (format "%s" text) t)))))) diff --git a/lisp/net/rcompile.el b/lisp/net/rcompile.el index c8305eeea28..f4e9d312437 100644 --- a/lisp/net/rcompile.el +++ b/lisp/net/rcompile.el @@ -188,8 +188,7 @@ See \\[compile]." (when (featurep 'tramp) (set (make-local-variable 'comint-file-name-prefix) (funcall (symbol-function 'tramp-make-tramp-file-name) - nil ;; multi-method. To be removed with Tramp 2.1. - nil + nil ;; method. remote-compile-user remote-compile-host "")))))) diff --git a/lisp/net/sasl-cram.el b/lisp/net/sasl-cram.el new file mode 100644 index 00000000000..32f1e69f81f --- /dev/null +++ b/lisp/net/sasl-cram.el @@ -0,0 +1,52 @@ +;;; sasl-cram.el --- CRAM-MD5 module for the SASL client framework + +;; Copyright (C) 2000, 2007 Free Software Foundation, Inc. + +;; Author: Daiki Ueno <ueno@unixuser.org> +;; Kenichi OKADA <okada@opaopa.org> +;; Keywords: SASL, CRAM-MD5 + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +(require 'sasl) +(require 'hmac-md5) + +(defconst sasl-cram-md5-steps + '(ignore ;no initial response + sasl-cram-md5-response)) + +(defun sasl-cram-md5-response (client step) + (let ((passphrase + (sasl-read-passphrase + (format "CRAM-MD5 passphrase for %s: " + (sasl-client-name client))))) + (unwind-protect + (concat (sasl-client-name client) " " + (encode-hex-string + (hmac-md5 (sasl-step-data step) passphrase))) + (fillarray passphrase 0)))) + +(put 'sasl-cram 'sasl-mechanism + (sasl-make-mechanism "CRAM-MD5" sasl-cram-md5-steps)) + +(provide 'sasl-cram) + +;;; arch-tag: 46cb281b-975a-4fe0-a39f-3018691b1b05 +;;; sasl-cram.el ends here diff --git a/lisp/net/sasl-digest.el b/lisp/net/sasl-digest.el new file mode 100644 index 00000000000..6c544518e7f --- /dev/null +++ b/lisp/net/sasl-digest.el @@ -0,0 +1,159 @@ +;;; sasl-digest.el --- DIGEST-MD5 module for the SASL client framework + +;; Copyright (C) 2000, 2007 Free Software Foundation, Inc. + +;; Author: Daiki Ueno <ueno@unixuser.org> +;; Kenichi OKADA <okada@opaopa.org> +;; Keywords: SASL, DIGEST-MD5 + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This program is implemented from draft-leach-digest-sasl-05.txt. +;; +;; It is caller's responsibility to base64-decode challenges and +;; base64-encode responses in IMAP4 AUTHENTICATE command. +;; +;; Passphrase should be longer than 16 bytes. (See RFC 2195) + +;;; Commentary: + +(require 'sasl) +(require 'hmac-md5) + +(defvar sasl-digest-md5-nonce-count 1) +(defvar sasl-digest-md5-unique-id-function + sasl-unique-id-function) + +(defvar sasl-digest-md5-syntax-table + (let ((table (make-syntax-table))) + (modify-syntax-entry ?= "." table) + (modify-syntax-entry ?, "." table) + table) + "A syntax table for parsing digest-challenge attributes.") + +(defconst sasl-digest-md5-steps + '(ignore ;no initial response + sasl-digest-md5-response + ignore)) ;"" + +(defun sasl-digest-md5-parse-string (string) + "Parse STRING and return a property list. +The value is a cons cell of the form \(realm nonce qop-options stale maxbuf +charset algorithm cipher-opts auth-param)." + (with-temp-buffer + (set-syntax-table sasl-digest-md5-syntax-table) + (save-excursion + (insert string) + (goto-char (point-min)) + (insert "(") + (while (progn (forward-sexp) (not (eobp))) + (delete-char 1) + (insert " ")) + (insert ")") + (read (point-min-marker))))) + +(defun sasl-digest-md5-digest-uri (serv-type host &optional serv-name) + (concat serv-type "/" host + (if (and serv-name + (not (string= host serv-name))) + (concat "/" serv-name)))) + +(defun sasl-digest-md5-cnonce () + (let ((sasl-unique-id-function sasl-digest-md5-unique-id-function)) + (sasl-unique-id))) + +(defun sasl-digest-md5-response-value (username + realm + nonce + cnonce + nonce-count + qop + digest-uri + authzid) + (let ((passphrase + (sasl-read-passphrase + (format "DIGEST-MD5 passphrase for %s: " + username)))) + (unwind-protect + (encode-hex-string + (md5-binary + (concat + (encode-hex-string + (md5-binary (concat (md5-binary + (concat username ":" realm ":" passphrase)) + ":" nonce ":" cnonce + (if authzid + (concat ":" authzid))))) + ":" nonce + ":" (format "%08x" nonce-count) ":" cnonce ":" qop ":" + (encode-hex-string + (md5-binary + (concat "AUTHENTICATE:" digest-uri + (if (member qop '("auth-int" "auth-conf")) + ":00000000000000000000000000000000"))))))) + (fillarray passphrase 0)))) + +(defun sasl-digest-md5-response (client step) + (let* ((plist + (sasl-digest-md5-parse-string (sasl-step-data step))) + (realm + (or (sasl-client-property client 'realm) + (plist-get plist 'realm))) ;need to check + (nonce-count + (or (sasl-client-property client 'nonce-count) + sasl-digest-md5-nonce-count)) + (qop + (or (sasl-client-property client 'qop) + "auth")) + (digest-uri + (sasl-digest-md5-digest-uri + (sasl-client-service client)(sasl-client-server client))) + (cnonce + (or (sasl-client-property client 'cnonce) + (sasl-digest-md5-cnonce)))) + (sasl-client-set-property client 'nonce-count (1+ nonce-count)) + (unless (string= qop "auth") + (sasl-error (format "Unsupported \"qop-value\": %s" qop))) + (concat + "username=\"" (sasl-client-name client) "\"," + "realm=\"" realm "\"," + "nonce=\"" (plist-get plist 'nonce) "\"," + "cnonce=\"" cnonce "\"," + (format "nc=%08x," nonce-count) + "digest-uri=\"" digest-uri "\"," + "qop=" qop "," + "response=" + (sasl-digest-md5-response-value + (sasl-client-name client) + realm + (plist-get plist 'nonce) + cnonce + nonce-count + qop + digest-uri + (plist-get plist 'authzid))))) + +(put 'sasl-digest 'sasl-mechanism + (sasl-make-mechanism "DIGEST-MD5" sasl-digest-md5-steps)) + +(provide 'sasl-digest) + +;;; arch-tag: 786e02ed-1bc4-4b3c-bf34-96c27e31084d +;;; sasl-digest.el ends here diff --git a/lisp/net/sasl-ntlm.el b/lisp/net/sasl-ntlm.el new file mode 100644 index 00000000000..cd8304db70a --- /dev/null +++ b/lisp/net/sasl-ntlm.el @@ -0,0 +1,68 @@ +;;; sasl-ntlm.el --- NTLM (NT Lan Manager) module for the SASL client framework + +;; Copyright (C) 2000, 2007 Free Software Foundation, Inc. + +;; Author: Taro Kawagishi <tarok@transpulse.org> +;; Keywords: SASL, NTLM +;; Version: 1.00 +;; Created: February 2001 + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This is a SASL interface layer for NTLM authentication message +;; generation by ntlm.el + +;;; Code: + +(require 'sasl) +(require 'ntlm) + +(defconst sasl-ntlm-steps + '(ignore ;nothing to do before making + sasl-ntlm-request ;authentication request + sasl-ntlm-response) ;response to challenge + "A list of functions to be called in sequnece for the NTLM +authentication steps. Ther are called by 'sasl-next-step.") + +(defun sasl-ntlm-request (client step) + "SASL step function to generate a NTLM authentication request to the server. +Called from 'sasl-next-step. +CLIENT is a vector [mechanism user service server sasl-client-properties] +STEP is a vector [<previous step function> <result of previous step function>]" + (let ((user (sasl-client-name client))) + (ntlm-build-auth-request user))) + +(defun sasl-ntlm-response (client step) + "SASL step function to generate a NTLM response against the server +challenge stored in the 2nd element of STEP. Called from 'sasl-next-step." + (let* ((user (sasl-client-name client)) + (passphrase + (sasl-read-passphrase (format "NTLM passphrase for %s: " user))) + (challenge (sasl-step-data step))) + (ntlm-build-auth-response challenge user + (ntlm-get-password-hashes passphrase)))) + +(put 'sasl-ntlm 'sasl-mechanism + (sasl-make-mechanism "NTLM" sasl-ntlm-steps)) + +(provide 'sasl-ntlm) + +;;; arch-tag: 1d9164c1-1df0-418f-b7ab-360157fd05dc +;;; sasl-ntlm.el ends here diff --git a/lisp/net/sasl.el b/lisp/net/sasl.el new file mode 100644 index 00000000000..9118d288da4 --- /dev/null +++ b/lisp/net/sasl.el @@ -0,0 +1,273 @@ +;;; sasl.el --- SASL client framework + +;; Copyright (C) 2000, 2007 Free Software Foundation, Inc. + +;; Author: Daiki Ueno <ueno@unixuser.org> +;; Keywords: SASL + +;; 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, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This module provides common interface functions to share several +;; SASL mechanism drivers. The toplevel is designed to be mostly +;; compatible with [Java-SASL]. +;; +;; [SASL] J. Myers, "Simple Authentication and Security Layer (SASL)", +;; RFC 2222, October 1997. +;; +;; [Java-SASL] R. Weltman & R. Lee, "The Java SASL Application Program +;; Interface", draft-weltman-java-sasl-03.txt, March 2000. + +;;; Code: + +(defvar sasl-mechanisms + '("CRAM-MD5" "DIGEST-MD5" "PLAIN" "LOGIN" "ANONYMOUS" + "NTLM" "SCRAM-MD5")) + +(defvar sasl-mechanism-alist + '(("CRAM-MD5" sasl-cram) + ("DIGEST-MD5" sasl-digest) + ("PLAIN" sasl-plain) + ("LOGIN" sasl-login) + ("ANONYMOUS" sasl-anonymous) + ("NTLM" sasl-ntlm) + ("SCRAM-MD5" sasl-scram))) + +(defvar sasl-unique-id-function #'sasl-unique-id-function) + +(put 'sasl-error 'error-message "SASL error") +(put 'sasl-error 'error-conditions '(sasl-error error)) + +(defun sasl-error (datum) + (signal 'sasl-error (list datum))) + +;;; @ SASL client +;;; + +(defun sasl-make-client (mechanism name service server) + "Return a newly allocated SASL client. +NAME is name of the authorization. SERVICE is name of the service desired. +SERVER is the fully qualified host name of the server to authenticate to." + (vector mechanism name service server (make-symbol "sasl-client-properties"))) + +(defun sasl-client-mechanism (client) + "Return the authentication mechanism driver of CLIENT." + (aref client 0)) + +(defun sasl-client-name (client) + "Return the authorization name of CLIENT, a string." + (aref client 1)) + +(defun sasl-client-service (client) + "Return the service name of CLIENT, a string." + (aref client 2)) + +(defun sasl-client-server (client) + "Return the server name of CLIENT, a string." + (aref client 3)) + +(defun sasl-client-set-properties (client plist) + "Destructively set the properties of CLIENT. +The second argument PLIST is the new property list." + (setplist (aref client 4) plist)) + +(defun sasl-client-set-property (client property value) + "Add the given property/value to CLIENT." + (put (aref client 4) property value)) + +(defun sasl-client-property (client property) + "Return the value of the PROPERTY of CLIENT." + (get (aref client 4) property)) + +(defun sasl-client-properties (client) + "Return the properties of CLIENT." + (symbol-plist (aref client 4))) + +;;; @ SASL mechanism +;;; + +(defun sasl-make-mechanism (name steps) + "Make an authentication mechanism. +NAME is a IANA registered SASL mechanism name. +STEPS is list of continuation function." + (vector name + (mapcar + (lambda (step) + (let ((symbol (make-symbol (symbol-name step)))) + (fset symbol (symbol-function step)) + symbol)) + steps))) + +(defun sasl-mechanism-name (mechanism) + "Return name of MECHANISM, a string." + (aref mechanism 0)) + +(defun sasl-mechanism-steps (mechanism) + "Return the authentication steps of MECHANISM, a list of functions." + (aref mechanism 1)) + +(defun sasl-find-mechanism (mechanisms) + "Retrieve an apropriate mechanism object from MECHANISMS hints." + (let* ((sasl-mechanisms sasl-mechanisms) + (mechanism + (catch 'done + (while sasl-mechanisms + (if (member (car sasl-mechanisms) mechanisms) + (throw 'done (nth 1 (assoc (car sasl-mechanisms) + sasl-mechanism-alist)))) + (setq sasl-mechanisms (cdr sasl-mechanisms)))))) + (if mechanism + (require mechanism)) + (get mechanism 'sasl-mechanism))) + +;;; @ SASL authentication step +;;; + +(defun sasl-step-data (step) + "Return the data which STEP holds, a string." + (aref step 1)) + +(defun sasl-step-set-data (step data) + "Store DATA string to STEP." + (aset step 1 data)) + +(defun sasl-next-step (client step) + "Evaluate the challenge and prepare an appropriate next response. +The data type of the value and optional 2nd argument STEP is nil or +opaque authentication step which holds the reference to the next action +and the current challenge. At the first time STEP should be set to nil." + (let* ((steps + (sasl-mechanism-steps + (sasl-client-mechanism client))) + (function + (if (vectorp step) + (nth 1 (memq (aref step 0) steps)) + (car steps)))) + (if function + (vector function (funcall function client step))))) + +(defvar sasl-read-passphrase nil) +(defun sasl-read-passphrase (prompt) + (if (not sasl-read-passphrase) + (if (functionp 'read-passwd) + (setq sasl-read-passphrase 'read-passwd) + (if (load "passwd" t) + (setq sasl-read-passphrase 'read-passwd) + (autoload 'ange-ftp-read-passwd "ange-ftp") + (setq sasl-read-passphrase 'ange-ftp-read-passwd)))) + (funcall sasl-read-passphrase prompt)) + +(defun sasl-unique-id () + "Compute a data string which must be different each time. +It contain at least 64 bits of entropy." + (concat (funcall sasl-unique-id-function)(funcall sasl-unique-id-function))) + +(defvar sasl-unique-id-char nil) + +;; stolen (and renamed) from message.el +(defun sasl-unique-id-function () + ;; Don't use microseconds from (current-time), they may be unsupported. + ;; Instead we use this randomly inited counter. + (setq sasl-unique-id-char + (% (1+ (or sasl-unique-id-char (logand (random t) (1- (lsh 1 20))))) + ;; (current-time) returns 16-bit ints, + ;; and 2^16*25 just fits into 4 digits i base 36. + (* 25 25))) + (let ((tm (current-time))) + (concat + (sasl-unique-id-number-base36 + (+ (car tm) + (lsh (% sasl-unique-id-char 25) 16)) 4) + (sasl-unique-id-number-base36 + (+ (nth 1 tm) + (lsh (/ sasl-unique-id-char 25) 16)) 4)))) + +(defun sasl-unique-id-number-base36 (num len) + (if (if (< len 0) + (<= num 0) + (= len 0)) + "" + (concat (sasl-unique-id-number-base36 (/ num 36) (1- len)) + (char-to-string (aref "zyxwvutsrqponmlkjihgfedcba9876543210" + (% num 36)))))) + +;;; PLAIN (RFC2595 Section 6) +(defconst sasl-plain-steps + '(sasl-plain-response)) + +(defun sasl-plain-response (client step) + (let ((passphrase + (sasl-read-passphrase + (format "PLAIN passphrase for %s: " (sasl-client-name client)))) + (authenticator-name + (sasl-client-property + client 'authenticator-name)) + (name (sasl-client-name client))) + (unwind-protect + (if (and authenticator-name + (not (string= authenticator-name name))) + (concat authenticator-name "\0" name "\0" passphrase) + (concat "\0" name "\0" passphrase)) + (fillarray passphrase 0)))) + +(put 'sasl-plain 'sasl-mechanism + (sasl-make-mechanism "PLAIN" sasl-plain-steps)) + +(provide 'sasl-plain) + +;;; LOGIN (No specification exists) +(defconst sasl-login-steps + '(ignore ;no initial response + sasl-login-response-1 + sasl-login-response-2)) + +(defun sasl-login-response-1 (client step) +;;; (unless (string-match "^Username:" (sasl-step-data step)) +;;; (sasl-error (format "Unexpected response: %s" (sasl-step-data step)))) + (sasl-client-name client)) + +(defun sasl-login-response-2 (client step) +;;; (unless (string-match "^Password:" (sasl-step-data step)) +;;; (sasl-error (format "Unexpected response: %s" (sasl-step-data step)))) + (sasl-read-passphrase + (format "LOGIN passphrase for %s: " (sasl-client-name client)))) + +(put 'sasl-login 'sasl-mechanism + (sasl-make-mechanism "LOGIN" sasl-login-steps)) + +(provide 'sasl-login) + +;;; ANONYMOUS (RFC2245) +(defconst sasl-anonymous-steps + '(ignore ;no initial response + sasl-anonymous-response)) + +(defun sasl-anonymous-response (client step) + (or (sasl-client-property client 'trace) + (sasl-client-name client))) + +(put 'sasl-anonymous 'sasl-mechanism + (sasl-make-mechanism "ANONYMOUS" sasl-anonymous-steps)) + +(provide 'sasl-anonymous) + +(provide 'sasl) + +;;; arch-tag: 8b3326fa-4978-4fda-93e2-cb2c6255f887 +;;; sasl.el ends here diff --git a/lisp/net/snmp-mode.el b/lisp/net/snmp-mode.el index 2721785ce49..2abff98c2df 100644 --- a/lisp/net/snmp-mode.el +++ b/lisp/net/snmp-mode.el @@ -88,6 +88,8 @@ ;;; Code: (eval-when-compile + (require 'cl) + (require 'imenu) ; Need this stuff when compiling for imenu macros, etc. (require 'tempo)) ;;;---------------------------------------------------------------------------- @@ -102,42 +104,42 @@ :version "20.4") (defcustom snmp-special-indent t - "*If non-nil, use a simple heuristic to try to guess the right indentation. + "If non-nil, use a simple heuristic to try to guess the right indentation. If nil, then no special indentation is attempted." :type 'boolean :group 'snmp) (defcustom snmp-indent-level 4 - "*Indentation level for SNMP MIBs." + "Indentation level for SNMP MIBs." :type 'integer :group 'snmp) (defcustom snmp-tab-always-indent nil - "*Non-nil means TAB should always reindent the current line. + "Non-nil means TAB should always reindent the current line. A value of nil means reindent if point is within the initial line indentation; otherwise insert a TAB." :type 'boolean :group 'snmp) (defcustom snmp-completion-ignore-case t - "*Non-nil means that case differences are ignored during completion. + "Non-nil means that case differences are ignored during completion. A value of nil means that case is significant. This is used during Tempo template completion." :type 'boolean :group 'snmp) (defcustom snmp-common-mode-hook nil - "*Hook(s) evaluated when a buffer enters either SNMP or SNMPv2 mode." + "Hook(s) evaluated when a buffer enters either SNMP or SNMPv2 mode." :type 'hook :group 'snmp) (defcustom snmp-mode-hook nil - "*Hook(s) evaluated when a buffer enters SNMP mode." + "Hook(s) evaluated when a buffer enters SNMP mode." :type 'hook :group 'snmp) (defcustom snmpv2-mode-hook nil - "*Hook(s) evaluated when a buffer enters SNMPv2 mode." + "Hook(s) evaluated when a buffer enters SNMPv2 mode." :type 'hook :group 'snmp) @@ -195,26 +197,26 @@ This is used during Tempo template completion." "Predefined types for SYNTAX clauses.") (defvar snmp-rfc1155-types - '(("INTEGER") ("OCTET STRING") ("OBJECT IDENTIFIER") ("NULL") ("IpAddress") - ("NetworkAddress") ("Counter") ("Gauge") ("TimeTicks") ("Opaque")) + '("INTEGER" "OCTET STRING" "OBJECT IDENTIFIER" "NULL" "IpAddress" + "NetworkAddress" "Counter" "Gauge" "TimeTicks" "Opaque") "Types from RFC 1155 v1 SMI.") (defvar snmp-rfc1213-types - '(("DisplayString")) + '("DisplayString") "Types from RFC 1213 MIB-II.") (defvar snmp-rfc1902-types - '(("INTEGER") ("OCTET STRING") ("OBJECT IDENTIFIER") ("Integer32") - ("IpAddress") ("Counter32") ("Gauge32") ("Unsigned32") ("TimeTicks") - ("Opaque") ("Counter64")) + '("INTEGER" "OCTET STRING" "OBJECT IDENTIFIER" "Integer32" + "IpAddress" "Counter32" "Gauge32" "Unsigned32" "TimeTicks" + "Opaque" "Counter64") "Types from RFC 1902 v2 SMI.") (defvar snmp-rfc1903-types - '(("DisplayString") ("PhysAddress") ("MacAddress") ("TruthValue") - ("TestAndIncr") ("AutonomousType") ("InstancePointer") - ("VariablePointer") ("RowPointer") ("RowStatus") ("TimeStamp") - ("TimeInterval") ("DateAndTime") ("StorageType") ("TDomain") - ("TAddress")) + '("DisplayString" "PhysAddress" "MacAddress" "TruthValue" + "TestAndIncr" "AutonomousType" "InstancePointer" + "VariablePointer" "RowPointer" "RowStatus" "TimeStamp" + "TimeInterval" "DateAndTime" "StorageType" "TDomain" + "TAddress") "Types from RFC 1903 Textual Conventions.") @@ -222,12 +224,12 @@ This is used during Tempo template completion." "Predefined values for ACCESS clauses.") (defvar snmp-rfc1155-access - '(("read-only") ("read-write") ("write-only") ("not-accessible")) + '("read-only" "read-write" "write-only" "not-accessible") "ACCESS values from RFC 1155 v1 SMI.") (defvar snmp-rfc1902-access - '(("read-only") ("read-write") ("read-create") ("not-accessible") - ("accessible-for-notify")) + '("read-only" "read-write" "read-create" "not-accessible" + "accessible-for-notify") "ACCESS values from RFC 1155 v1 SMI.") @@ -235,11 +237,11 @@ This is used during Tempo template completion." "Predefined values for STATUS clauses.") (defvar snmp-rfc1212-status - '(("mandatory") ("obsolete") ("deprecated")) + '("mandatory" "obsolete" "deprecated") "STATUS values from RFC 1212 v1 SMI.") (defvar snmp-rfc1902-status - '(("current") ("obsolete") ("deprecated")) + '("current" "obsolete" "deprecated") "STATUS values from RFC 1902 v2 SMI.") @@ -252,13 +254,6 @@ This is used during Tempo template completion." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Need this stuff when compiling for imenu macros, etc. -;; -(eval-when-compile - (require 'cl) - (require 'imenu)) - - ;; Create abbrev table for SNMP MIB mode ;; (defvar snmp-mode-abbrev-table nil @@ -275,33 +270,30 @@ This is used during Tempo template completion." ;; Set up our keymap ;; -(defvar snmp-mode-map (make-sparse-keymap) +(defvar snmp-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\177" 'backward-delete-char-untabify) + (define-key map "\C-c\C-i" 'tempo-complete-tag) + (define-key map "\C-c\C-f" 'tempo-forward-mark) + (define-key map "\C-c\C-b" 'tempo-backward-mark) + map) "Keymap used in SNMP mode.") -(define-key snmp-mode-map "\t" 'snmp-indent-command) -(define-key snmp-mode-map "\177" 'backward-delete-char-untabify) - -(define-key snmp-mode-map "\C-c\C-i" 'tempo-complete-tag) -(define-key snmp-mode-map "\C-c\C-f" 'tempo-forward-mark) -(define-key snmp-mode-map "\C-c\C-b" 'tempo-backward-mark) - ;; Set up our syntax table ;; -(defvar snmp-mode-syntax-table nil +(defvar snmp-mode-syntax-table + (let ((st (make-syntax-table))) + (modify-syntax-entry ?\\ "\\" st) + (modify-syntax-entry ?- "_ 1234" st) + (modify-syntax-entry ?\n ">" st) + (modify-syntax-entry ?\^m ">" st) + (modify-syntax-entry ?_ "." st) + (modify-syntax-entry ?: "." st) + (modify-syntax-entry ?= "." st) + st) "Syntax table used for buffers in SNMP mode.") -(if snmp-mode-syntax-table - () - (setq snmp-mode-syntax-table (make-syntax-table)) - (modify-syntax-entry ?\\ "\\" snmp-mode-syntax-table) - (modify-syntax-entry ?- "_ 1234" snmp-mode-syntax-table) - (modify-syntax-entry ?\n ">" snmp-mode-syntax-table) - (modify-syntax-entry ?\^m ">" snmp-mode-syntax-table) - (modify-syntax-entry ?_ "." snmp-mode-syntax-table) - (modify-syntax-entry ?: "." snmp-mode-syntax-table) - (modify-syntax-entry ?= "." snmp-mode-syntax-table)) - ;; Set up the stuff that's common between snmp-mode and snmpv2-mode ;; (defun snmp-common-mode (name mode abbrev font-keywords imenu-index tempo-tags) @@ -335,10 +327,9 @@ This is used during Tempo template completion." (setq parse-sexp-ignore-comments t) ;; Set up indentation - (make-local-variable 'indent-line-function) - (setq indent-line-function (if snmp-special-indent - 'snmp-indent-line - 'indent-to-left-margin)) + (if snmp-special-indent + (set (make-local-variable 'indent-line-function) 'snmp-indent-line)) + (set (make-local-variable 'tab-always-indent) snmp-tab-always-indent) ;; Font Lock (make-local-variable 'font-lock-defaults) @@ -474,7 +465,7 @@ lines for the purposes of this function." "Indent current line as SNMP MIB code." (let ((indent (snmp-calculate-indent)) (pos (- (point-max) (point))) - shift-amt beg end) + shift-amt beg) (beginning-of-line) (setq beg (point)) (skip-chars-forward " \t") @@ -488,20 +479,6 @@ lines for the purposes of this function." (if (> (- (point-max) pos) (point)) (goto-char (- (point-max) pos))))) -(defun snmp-indent-command () - "Indent current line as SNMP MIB code, or sometimes insert a TAB. -If `snmp-tab-always-indent' is t, always reindent the current line when -this command is run. -If `snmp-tab-always-indent' is nil, reindent the current line if point is -in the initial indentation. Otherwise, insert a TAB." - (interactive) - (if (and (not snmp-tab-always-indent) - (save-excursion - (skip-chars-backward " \t") - (not (bolp)))) - (insert-tab) - (snmp-indent-line))) - ;;;---------------------------------------------------------------------------- ;; @@ -520,7 +497,7 @@ in the initial indentation. Otherwise, insert a TAB." (index-table-alist '()) (index-trap-alist '()) (case-fold-search nil) ; keywords must be uppercase - prev-pos token marker end) + prev-pos token end) (goto-char (point-min)) (imenu-progress-message prev-pos 0) ;; Search for a useful MIB item (that's not in a comment) @@ -529,7 +506,7 @@ in the initial indentation. Otherwise, insert a TAB." (imenu-progress-message prev-pos) (setq end (match-end 0) - token (cons (buffer-substring (match-beginning 1) (match-end 1)) + token (cons (match-string 1) (set-marker (make-marker) (match-beginning 1)))) (goto-char (match-beginning 2)) (cond ((looking-at "OBJECT-TYPE[ \t\n]+SYNTAX") @@ -719,5 +696,5 @@ controls whether case is significant." (provide 'snmp-mode) -;;; arch-tag: eb6cc0f9-1e47-4023-8625-bc9aae6c3527 +;; arch-tag: eb6cc0f9-1e47-4023-8625-bc9aae6c3527 ;;; snmp-mode.el ends here diff --git a/lisp/net/socks.el b/lisp/net/socks.el index f4fba58b663..c02db08d060 100644 --- a/lisp/net/socks.el +++ b/lisp/net/socks.el @@ -247,7 +247,7 @@ If PATTERN is omitted, it defaults to \"[ \\f\\t\\n\\r\\v]+\"." (defun socks-build-auth-list () (let ((num 0) (retval "")) - (mapcar + (mapc (function (lambda (x) (if (fboundp (cdr (cdr x))) @@ -335,6 +335,19 @@ If PATTERN is omitted, it defaults to \"[ \\f\\t\\n\\r\\v]+\"." ) ) +(declare-function socks-original-open-network-stream "socks") ; fset + +(defvar socks-override-functions nil + "*Whether to overwrite the open-network-stream function with the SOCKSified +version.") + +(if (fboundp 'socks-original-open-network-stream) + nil ; Do nothing, we've been here already + (defalias 'socks-original-open-network-stream + (symbol-function 'open-network-stream)) + (if socks-override-functions + (defalias 'open-network-stream 'socks-open-network-stream))) + (defun socks-open-connection (server-info) (interactive) (save-excursion @@ -473,17 +486,6 @@ If PATTERN is omitted, it defaults to \"[ \\f\\t\\n\\r\\v]+\"." (setq noproxy (cdr noproxy))) route)) -(defvar socks-override-functions nil - "*Whether to overwrite the open-network-stream function with the SOCKSified -version.") - -(if (fboundp 'socks-original-open-network-stream) - nil ; Do nothing, we've been here already - (defalias 'socks-original-open-network-stream - (symbol-function 'open-network-stream)) - (if socks-override-functions - (defalias 'open-network-stream 'socks-open-network-stream))) - (defvar socks-services-file "/etc/services") (defvar socks-tcp-services (make-hash-table :size 13 :test 'equal)) (defvar socks-udp-services (make-hash-table :size 13 :test 'equal)) @@ -546,7 +548,9 @@ version.") atype host (if (stringp service) - (socks-find-services-entry service) + (or + (socks-find-services-entry service) + (error "Unknown service: %s" service)) service)) (puthash 'buffer buffer info) (puthash 'host host info) diff --git a/lisp/net/tls.el b/lisp/net/tls.el index 8946b06a3a3..4bd32d75721 100644 --- a/lisp/net/tls.el +++ b/lisp/net/tls.el @@ -51,37 +51,25 @@ (autoload 'format-spec "format-spec") (autoload 'format-spec-make "format-spec")) -(eval-when-compile - (require 'rx)) - (defgroup tls nil "Transport Layer Security (TLS) parameters." :group 'comm) (defcustom tls-end-of-info - (rx - (or - ;; `openssl s_client` regexp - (sequence - ;; see ssl/ssl_txt.c lines 219--220 - line-start - " Verify return code: " - (one-or-more not-newline) - "\n" - ;; according to apps/s_client.c line 1515 this is always the last - ;; line that is printed by s_client before the real data - "---\n") - ;; `gnutls` regexp - (sequence - ;; see src/cli.c lines 721-- - (sequence line-start "- Simple Client Mode:\n") - (zero-or-more - (or - "\n" ; ignore blank lines - ;; XXX: we have no way of knowing if the STARTTLS handshake - ;; sequence has completed successfully, because `gnutls` will - ;; only report failure. - (sequence line-start "\*\*\* Starting TLS handshake\n")))))) + (concat + "\\(" + ;; `openssl s_client' regexp. See ssl/ssl_txt.c lines 219-220. + ;; According to apps/s_client.c line 1515 `---' is always the last + ;; line that is printed by s_client before the real data. + "^ Verify return code: .+\n---\n\\|" + ;; `gnutls' regexp. See src/cli.c lines 721-. + "^- Simple Client Mode:\n" + "\\(\n\\|" ; ignore blank lines + ;; According to GnuTLS v2.1.5 src/cli.c lines 640-650 and 705-715 + ;; in `main' the handshake will start after this message. If the + ;; handshake fails, the programs will abort. + "^\\*\\*\\* Starting TLS handshake\n\\)*" + "\\)") "Regexp matching end of TLS client informational messages. Client data stream begins after the last character matched by this. The default matches `openssl s_client' (version 0.9.8c) @@ -97,26 +85,93 @@ and `gnutls-cli' (version 2.0.1) output." Each entry in the list is tried until a connection is successful. %h is replaced with server hostname, %p with port to connect to. The program should read input on stdin and write output to -stdout. Also see `tls-success' for what the program should output -after successful negotiation." - :type '(repeat string) +stdout. + +See `tls-checktrust' on how to check trusted root certs. + +Also see `tls-success' for what the program should output after +successful negotiation." + :type + '(choice + (list :tag "Choose commands" + :value + ("gnutls-cli -p %p %h" + "gnutls-cli -p %p %h --protocols ssl3" + "openssl s_client -connect %h:%p -no_ssl2") + (set :inline t + ;; FIXME: add brief `:tag "..."' descriptions. + ;; (repeat :inline t :tag "Other" (string)) + ;; See `tls-checktrust': + (const "gnutls-cli --x509cafile /etc/ssl/certs/ca-certificates.crt -p %p %h") + (const "gnutls-cli --x509cafile /etc/ssl/certs/ca-certificates.crt -p %p %h --protocols ssl3") + (const "openssl s_client -connect %h:%p -CAfile /etc/ssl/certs/ca-certificates.crt -no_ssl2") + ;; No trust check: + (const "gnutls-cli -p %p %h") + (const "gnutls-cli -p %p %h --protocols ssl3") + (const "openssl s_client -connect %h:%p -no_ssl2")) + (repeat :inline t :tag "Other" (string))) + (const :tag "Default list of commands" + ("gnutls-cli -p %p %h" + "gnutls-cli -p %p %h --protocols ssl3" + "openssl s_client -connect %h:%p -no_ssl2")) + (list :tag "List of commands" + (repeat :tag "Command" (string)))) :version "22.1" :group 'tls) (defcustom tls-process-connection-type nil - "*Value for `process-connection-type' to use when starting TLS process." + "Value for `process-connection-type' to use when starting TLS process." :version "22.1" :type 'boolean :group 'tls) (defcustom tls-success "- Handshake was completed\\|SSL handshake has read " - "*Regular expression indicating completed TLS handshakes. + "Regular expression indicating completed TLS handshakes. The default is what GNUTLS's \"gnutls-cli\" or OpenSSL's \"openssl s_client\" outputs." :version "22.1" :type 'regexp :group 'tls) +(defcustom tls-checktrust nil + "Indicate if certificates should be checked against trusted root certs. +If this is `ask', the user can decide whether to accept an +untrusted certificate. You may have to adapt `tls-program' in +order to make this feature work properly, i.e., to ensure that +the external program knows about the root certificates you +consider trustworthy, e.g.: + +\(setq tls-program + '(\"gnutls-cli --x509cafile /etc/ssl/certs/ca-certificates.crt -p %p %h\" + \"gnutls-cli --x509cafile /etc/ssl/certs/ca-certificates.crt -p %p %h --protocols ssl3\" + \"openssl s_client -connect %h:%p -CAfile /etc/ssl/certs/ca-certificates.crt -no_ssl2\"))" + :type '(choice (const :tag "Always" t) + (const :tag "Never" nil) + (const :tag "Ask" ask)) + :version "23.0" ;; No Gnus + :group 'tls) + +(defcustom tls-untrusted + "- Peer's certificate is NOT trusted\\|Verify return code: \\([^0] \\|.[^ ]\\)" + "Regular expression indicating failure of TLS certificate verification. +The default is what GNUTLS's \"gnutls-cli\" or OpenSSL's +\"openssl s_client\" return in the event of unsuccessful +verification." + :type 'regexp + :version "23.0" ;; No Gnus + :group 'tls) + +(defcustom tls-hostmismatch + "# The hostname in the certificate does NOT match" + "Regular expression indicating a host name mismatch in certificate. +When the host name specified in the certificate doesn't match the +name of the host you are connecting to, gnutls-cli issues a +warning to this effect. There is no such feature in openssl. Set +this to nil if you want to ignore host name mismatches." + :type 'regexp + :version "23.0" ;; No Gnus + :group 'tls) + (defcustom tls-certtool-program (executable-find "certtool") "Name of GnuTLS certtool. Used by `tls-certificate-information'." @@ -153,7 +208,7 @@ Returns a subprocess-object to represent the connection. Input and output work as for subprocesses; `delete-process' closes it. Args are NAME BUFFER HOST PORT. NAME is name for process. It is modified if necessary to make it unique. -BUFFER is the buffer (or buffer-name) to associate with the process. +BUFFER is the buffer (or buffer name) to associate with the process. Process output goes at end of that buffer, unless you specify an output stream or filter function to handle the output. BUFFER may be also nil, meaning that this process is not associated @@ -165,52 +220,57 @@ Fourth arg PORT is an integer specifying a port to connect to." process cmd done) (if use-temp-buffer (setq buffer (generate-new-buffer " TLS"))) - (save-excursion - (set-buffer buffer) + (with-current-buffer buffer (message "Opening TLS connection to `%s'..." host) - (while (and (not done) (setq cmd (pop cmds))) - (message "Opening TLS connection with `%s'..." cmd) - (let ((process-connection-type tls-process-connection-type) - response) - (setq process (start-process - name buffer shell-file-name shell-command-switch - (format-spec - cmd - (format-spec-make - ?h host - ?p (if (integerp port) - (int-to-string port) - port))))) - (while (and process - (memq (process-status process) '(open run)) + (while (and (not done) (setq cmd (pop cmds))) + (message "Opening TLS connection with `%s'..." cmd) + (let ((process-connection-type tls-process-connection-type) + response) + (setq process (start-process + name buffer shell-file-name shell-command-switch + (format-spec + cmd + (format-spec-make + ?h host + ?p (if (integerp port) + (int-to-string port) + port))))) + (while (and process + (memq (process-status process) '(open run)) + (progn + (goto-char (point-min)) + (not (setq done (re-search-forward tls-success nil t))))) + (unless (accept-process-output process 1) + (sit-for 1))) + (message "Opening TLS connection with `%s'...%s" cmd + (if done "done" "failed")) + (if done + (setq done process) + (delete-process process)))) + (when done + (save-excursion + (set-buffer buffer) + (when + (or + (and tls-checktrust + (progn + (goto-char (point-min)) + (re-search-forward tls-untrusted nil t)) + (or + (and (not (eq tls-checktrust 'ask)) + (message "The certificate presented by `%s' is NOT trusted." host)) + (not (yes-or-no-p + (format "The certificate presented by `%s' is NOT trusted. Accept anyway? " host))))) + (and tls-hostmismatch (progn (goto-char (point-min)) - (not (setq done (re-search-forward tls-success nil t))))) - (unless (accept-process-output process 1) - (sit-for 1))) - (message "Opening TLS connection with `%s'...%s" cmd - (if done "done" "failed")) - (if (not done) - (delete-process process) - ;; advance point to after all informational messages that - ;; `openssl s_client' and `gnutls' print - (let ((start-of-data nil)) - (while - (not (setq start-of-data - ;; the string matching `tls-end-of-info' - ;; might come in separate chunks from - ;; `accept-process-output', so start the - ;; search where `tls-success' ended - (save-excursion - (if (re-search-forward tls-end-of-info nil t) - (match-end 0))))) - (accept-process-output process 1)) - (if start-of-data - ;; move point to start of client data - (goto-char start-of-data))) - (setq done process)))) - (message "Opening TLS connection to `%s'...%s" - host (if done "done" "failed"))) + (re-search-forward tls-hostmismatch nil t)) + (not (yes-or-no-p + (format "Host name in certificate doesn't match `%s'. Connect anyway? " host))))) + (setq done nil) + (delete-process process)))) + (message "Opening TLS connection to `%s'...%s" + host (if done "done" "failed"))) (when use-temp-buffer (if done (set-process-buffer process nil)) (kill-buffer buffer)) diff --git a/lisp/net/tramp-cache.el b/lisp/net/tramp-cache.el new file mode 100644 index 00000000000..b28c20263f4 --- /dev/null +++ b/lisp/net/tramp-cache.el @@ -0,0 +1,333 @@ +;;; tramp-cache.el --- file information caching for Tramp + +;; Copyright (C) 2000, 2005, 2006, 2007 by Free Software Foundation, Inc. + +;; Author: Daniel Pittman <daniel@inanna.danann.net> +;; Michael Albinus <michael.albinus@gmx.de> +;; Keywords: comm, processes + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; An implementation of information caching for remote files. + +;; Each connection, identified by a vector [method user host +;; localname] or by a process, has a unique cache. We distinguish 3 +;; kind of caches, depending on the key: +;; +;; - localname is NIL. This are reusable properties. Examples: +;; "remote-shell" identifies the POSIX shell to be called on the +;; remote host, or "perl" is the command to be called on the remote +;; host, when starting a Perl script. These properties are saved in +;; the file `tramp-persistency-file-name'. +;; +;; - localname is a string. This are temporary properties, which are +;; related to the file localname is referring to. Examples: +;; "file-exists-p" is t or nile, depending on the file existence, or +;; "file-attributes" caches the result of the function +;; `file-attributes'. +;; +;; - The key is a process. This are temporary properties related to +;; an open connection. Examples: "scripts" keeps shell script +;; definitions already sent to the remote shell, "last-cmd-time" is +;; the time stamp a command has been sent to the remote process. + +;;; Code: + +;; Pacify byte-compiler. +(eval-when-compile + (require 'cl) + (autoload 'tramp-message "tramp") + (autoload 'tramp-tramp-file-p "tramp") + ;; We cannot autoload macro `with-parsed-tramp-file-name', it + ;; results in problems of byte-compiled code. + (autoload 'tramp-dissect-file-name "tramp") + (autoload 'tramp-file-name-method "tramp") + (autoload 'tramp-file-name-user "tramp") + (autoload 'tramp-file-name-host "tramp") + (autoload 'tramp-file-name-localname "tramp") + (autoload 'time-stamp-string "time-stamp")) + +;;; -- Cache -- + +(defvar tramp-cache-data (make-hash-table :test 'equal) + "Hash table for remote files properties.") + +(defcustom tramp-persistency-file-name + (cond + ;; GNU Emacs. + ((and (boundp 'user-emacs-directory) + (stringp (symbol-value 'user-emacs-directory)) + (file-directory-p (symbol-value 'user-emacs-directory))) + (expand-file-name "tramp" (symbol-value 'user-emacs-directory))) + ((and (not (featurep 'xemacs)) (file-directory-p "~/.emacs.d/")) + "~/.emacs.d/tramp") + ;; XEmacs. + ((and (boundp 'user-init-directory) + (stringp (symbol-value 'user-init-directory)) + (file-directory-p (symbol-value 'user-init-directory))) + (expand-file-name "tramp" (symbol-value 'user-init-directory))) + ((and (featurep 'xemacs) (file-directory-p "~/.xemacs/")) + "~/.xemacs/tramp") + ;; For users without `~/.emacs.d/' or `~/.xemacs/'. + (t "~/.tramp")) + "File which keeps connection history for Tramp connections." + :group 'tramp + :type 'file) + +(defun tramp-get-file-property (vec file property default) + "Get the PROPERTY of FILE from the cache context of VEC. +Returns DEFAULT if not set." + ;; Unify localname. + (setq vec (copy-sequence vec)) + (aset vec 3 (directory-file-name file)) + (let* ((hash (or (gethash vec tramp-cache-data) + (puthash vec (make-hash-table :test 'equal) + tramp-cache-data))) + (value (if (hash-table-p hash) + (gethash property hash default) + default))) + (tramp-message vec 8 "%s %s %s" file property value) + value)) + +(defun tramp-set-file-property (vec file property value) + "Set the PROPERTY of FILE to VALUE, in the cache context of VEC. +Returns VALUE." + ;; Unify localname. + (setq vec (copy-sequence vec)) + (aset vec 3 (directory-file-name file)) + (let ((hash (or (gethash vec tramp-cache-data) + (puthash vec (make-hash-table :test 'equal) + tramp-cache-data)))) + (puthash property value hash) + (tramp-message vec 8 "%s %s %s" file property value) + value)) + +(defun tramp-flush-file-property (vec file) + "Remove all properties of FILE in the cache context of VEC." + ;; Unify localname. + (setq vec (copy-sequence vec)) + (aset vec 3 (directory-file-name file)) + (tramp-message vec 8 "%s" file) + (remhash vec tramp-cache-data)) + +(defun tramp-flush-directory-property (vec directory) + "Remove all properties of DIRECTORY in the cache context of VEC. +Remove also properties of all files in subdirectories." + (let ((directory (directory-file-name directory))) + (tramp-message vec 8 "%s" directory) + (maphash + '(lambda (key value) + (when (and (stringp key) + (string-match directory (tramp-file-name-localname key))) + (remhash key tramp-cache-data))) + tramp-cache-data))) + +;; Reverting or killing a buffer should also flush file properties. +;; They could have been changed outside Tramp. In eshell, "ls" would +;; not show proper directory contents when a file has been copied or +;; deleted before. +(defun tramp-flush-file-function () + "Flush all Tramp cache properties from buffer-file-name." + (let ((bfn (if (stringp (buffer-file-name)) + (buffer-file-name) + default-directory))) + (when (tramp-tramp-file-p bfn) + (let* ((v (tramp-dissect-file-name bfn)) + (localname (tramp-file-name-localname v))) + (tramp-flush-file-property v localname))))) + +(add-hook 'before-revert-hook 'tramp-flush-file-function) +(add-hook 'eshell-pre-command-hook 'tramp-flush-file-function) +(add-hook 'kill-buffer-hook 'tramp-flush-file-function) +(add-hook 'tramp-cache-unload-hook + '(lambda () + (remove-hook 'before-revert-hook + 'tramp-flush-file-function) + (remove-hook 'eshell-pre-command-hook + 'tramp-flush-file-function) + (remove-hook 'kill-buffer-hook + 'tramp-flush-file-function))) + +;;; -- Properties -- + +(defun tramp-get-connection-property (key property default) + "Get the named PROPERTY for the connection. +KEY identifies the connection, it is either a process or a vector. +If the value is not set for the connection, returns DEFAULT." + ;; Unify key by removing localname from vector. Work with a copy in + ;; order to avoid side effects. + (when (vectorp key) + (setq key (copy-sequence key)) + (aset key 3 nil)) + (let* ((hash (gethash key tramp-cache-data)) + (value (if (hash-table-p hash) + (gethash property hash default) + default))) + (tramp-message key 7 "%s %s" property value) + value)) + +(defun tramp-set-connection-property (key property value) + "Set the named PROPERTY of a connection to VALUE. +KEY identifies the connection, it is either a process or a vector. +PROPERTY is set persistent when KEY is a vector." + ;; Unify key by removing localname from vector. Work with a copy in + ;; order to avoid side effects. + (when (vectorp key) + (setq key (copy-sequence key)) + (aset key 3 nil)) + (let ((hash (or (gethash key tramp-cache-data) + (puthash key (make-hash-table :test 'equal) + tramp-cache-data)))) + (puthash property value hash) + ;; This function is called also during initialization of + ;; tramp-cache.el. `tramp-message´ is not defined yet at this + ;; time, so we ignore the corresponding error. + (condition-case nil + (tramp-message key 7 "%s %s" property value) + (error nil)) + value)) + +(defun tramp-flush-connection-property (key event) + "Remove all properties identified by KEY. +KEY identifies the connection, it is either a process or a +vector. EVENT is not used, it is just applied because this +function is intended to run also as process sentinel." + ;; Unify key by removing localname from vector. Work with a copy in + ;; order to avoid side effects. + (when (vectorp key) + (setq key (copy-sequence key)) + (aset key 3 nil)) +; (tramp-message key 7 "%s" event) + (remhash key tramp-cache-data)) + +(defun tramp-cache-print (table) + "Print hash table TABLE." + (when (hash-table-p table) + (let (result) + (maphash + '(lambda (key value) + (let ((tmp (format + "(%s %s)" + (if (processp key) + (prin1-to-string (prin1-to-string key)) + (prin1-to-string key)) + (if (hash-table-p value) + (tramp-cache-print value) + (if (bufferp value) + (prin1-to-string (prin1-to-string value)) + (prin1-to-string value)))))) + (setq result (if result (concat result " " tmp) tmp)))) + table) + result))) + +(defun tramp-list-connections () + "Return a list of all known connection vectors according to `tramp-cache'." + (let (result) + (maphash + '(lambda (key value) + (when (and (vectorp key) (null (aref key 3))) + (add-to-list 'result key))) + tramp-cache-data) + result)) + +(defun tramp-dump-connection-properties () + "Write persistent connection properties into file `tramp-persistency-file-name'." + ;; We shouldn't fail, otherwise (X)Emacs might not be able to be closed. + (condition-case nil + (when (and (hash-table-p tramp-cache-data) + (not (zerop (hash-table-count tramp-cache-data))) + (stringp tramp-persistency-file-name)) + (let ((cache (copy-hash-table tramp-cache-data))) + ;; Remove temporary data. + (maphash + '(lambda (key value) + (if (and (vectorp key) (not (tramp-file-name-localname key))) + (progn + (remhash "process-name" value) + (remhash "process-buffer" value)) + (remhash key cache))) + cache) + ;; Dump it. + (with-temp-buffer + (insert + ";; -*- emacs-lisp -*-" + ;; `time-stamp-string' might not exist in all (X)Emacs flavors. + (condition-case nil + (progn + (format + " <%s %s>\n" + (time-stamp-string "%02y/%02m/%02d %02H:%02M:%02S") + tramp-persistency-file-name)) + (error "\n")) + ";; Tramp connection history. Don't change this file.\n" + ";; You can delete it, forcing Tramp to reapply the checks.\n\n" + (with-output-to-string + (pp (read (format "(%s)" (tramp-cache-print cache)))))) + (write-region + (point-min) (point-max) tramp-persistency-file-name)))) + (error nil))) + +(add-hook 'kill-emacs-hook 'tramp-dump-connection-properties) +(add-hook 'tramp-cache-unload-hook + '(lambda () + (remove-hook 'kill-emacs-hook + 'tramp-dump-connection-properties))) + +(defun tramp-parse-connection-properties (method) + "Return a list of (user host) tuples allowed to access for METHOD. +This function is added always in `tramp-get-completion-function' +for all methods. Resulting data are derived from connection +history." + (let (res) + (maphash + '(lambda (key value) + (if (and (vectorp key) + (string-equal method (tramp-file-name-method key)) + (not (tramp-file-name-localname key))) + (push (list (tramp-file-name-user key) + (tramp-file-name-host key)) + res))) + tramp-cache-data) + res)) + +;; Read persistent connection history. +(when (and (stringp tramp-persistency-file-name) + (zerop (hash-table-count tramp-cache-data))) + (condition-case err + (with-temp-buffer + (insert-file-contents tramp-persistency-file-name) + (let ((list (read (current-buffer))) + element key item) + (while (setq element (pop list)) + (setq key (pop element)) + (while (setq item (pop element)) + (tramp-set-connection-property key (pop item) (car item)))))) + (file-error + ;; Most likely because the file doesn't exist yet. No message. + (clrhash tramp-cache-data)) + (error + ;; File is corrupted. + (message "Tramp persistency file '%s' is corrupted: %s" + tramp-persistency-file-name (error-message-string err)) + (clrhash tramp-cache-data)))) + +(provide 'tramp-cache) + +;; arch-tag: ee1739b7-7628-408c-9b96-d11a74b05d26 +;;; tramp-cache.el ends here diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el new file mode 100644 index 00000000000..7cf2bf3d923 --- /dev/null +++ b/lisp/net/tramp-cmds.el @@ -0,0 +1,406 @@ +;;; tramp-cmds.el --- Interactive commands for Tramp + +;; Copyright (C) 2007 Free Software Foundation, Inc. + +;; Author: Michael Albinus <michael.albinus@gmx.de> +;; Keywords: comm, processes + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This package provides all interactive commands which are releated +;; to Tramp. + +;;; Code: + +(require 'tramp) + +(defun tramp-list-tramp-buffers () + "Return a list of all Tramp connection buffers." + (append + (all-completions + "*tramp" (mapcar 'list (mapcar 'buffer-name (buffer-list)))) + (all-completions + "*debug tramp" (mapcar 'list (mapcar 'buffer-name (buffer-list)))))) + +(defun tramp-list-remote-buffers () + "Return a list of all buffers with remote default-directory." + (delq + nil + (mapcar + (lambda (x) + (with-current-buffer x + (when (and (stringp default-directory) + (file-remote-p default-directory)) + x))) + (buffer-list)))) + +(defun tramp-cleanup-connection (vec) + "Flush all connection related objects. +This includes password cache, file cache, connection cache, buffers. +When called interactively, a Tramp connection has to be selected." + (interactive + ;; When interactive, select the Tramp remote identification. + ;; Return nil when there is no Tramp connection. + (list + (let ((connections + (mapcar + (lambda (x) + (tramp-make-tramp-file-name + (tramp-file-name-method x) + (tramp-file-name-user x) + (tramp-file-name-host x) + (tramp-file-name-localname x))) + (tramp-list-connections))) + name) + + (when connections + (setq name + (completing-read + "Enter Tramp connection: " connections nil t + (try-completion "" connections))) + (when (and name (file-remote-p name)) + (with-parsed-tramp-file-name name nil v)))))) + + (if (not vec) + ;; Nothing to do. + (message "No Tramp connection found.") + + ;; Flush password cache. + (tramp-clear-passwd vec) + + ;; Flush file cache. + (tramp-flush-directory-property vec "/") + + ;; Flush connection cache. + (tramp-flush-connection-property (tramp-get-connection-process vec) nil) + (tramp-flush-connection-property vec nil) + + ;; Remove buffers. + (dolist + (buf (list (get-buffer (tramp-buffer-name vec)) + (get-buffer (tramp-debug-buffer-name vec)) + (tramp-get-connection-property vec "process-buffer" nil))) + (when (bufferp buf) (kill-buffer buf))))) + +(defun tramp-cleanup-all-connections () + "Flush all Tramp internal objects. +This includes password cache, file cache, connection cache, buffers." + (interactive) + + ;; Flush password cache. + (when (functionp 'password-reset) + (funcall (symbol-function 'password-reset))) + + ;; Flush file and connection cache. + (clrhash tramp-cache-data) + + ;; Remove buffers. + (dolist (name (tramp-list-tramp-buffers)) + (when (bufferp (get-buffer name)) (kill-buffer name)))) + +(defun tramp-cleanup-all-buffers () + "Kill all remote buffers." + (interactive) + + ;; Remove all Tramp related buffers. + (tramp-cleanup-all-connections) + + ;; Remove all buffers with a remote default-directory. + (dolist (name (tramp-list-remote-buffers)) + (when (bufferp (get-buffer name)) (kill-buffer name)))) + +;; Tramp version is useful in a number of situations. + +(defun tramp-version (arg) + "Print version number of tramp.el in minibuffer or current buffer." + (interactive "P") + (if arg (insert tramp-version) (message tramp-version))) + +;; Make the `reporter` functionality available for making bug reports about +;; the package. A most useful piece of code. + +(autoload 'reporter-submit-bug-report "reporter") + +(defun tramp-bug () + "Submit a bug report to the Tramp developers." + (interactive) + (require 'reporter) + (catch 'dont-send + (let ((reporter-prompt-for-summary-p t)) + (reporter-submit-bug-report + tramp-bug-report-address ; to-address + (format "tramp (%s)" tramp-version) ; package name and version + (delq nil + `(;; Current state + tramp-current-method + tramp-current-user + tramp-current-host + + ;; System defaults + tramp-auto-save-directory ; vars to dump + tramp-default-method + tramp-default-method-alist + tramp-default-host + tramp-default-proxies-alist + tramp-default-user + tramp-default-user-alist + tramp-rsh-end-of-line + tramp-default-password-end-of-line + tramp-login-prompt-regexp + ;; Mask non-7bit characters + (tramp-password-prompt-regexp . tramp-reporter-dump-variable) + tramp-wrong-passwd-regexp + tramp-yesno-prompt-regexp + tramp-yn-prompt-regexp + tramp-terminal-prompt-regexp + tramp-temp-name-prefix + tramp-file-name-structure + tramp-file-name-regexp + tramp-methods + tramp-end-of-output + tramp-local-coding-commands + tramp-remote-coding-commands + tramp-actions-before-shell + tramp-actions-copy-out-of-band + tramp-terminal-type + ;; Mask non-7bit characters + (tramp-shell-prompt-pattern . tramp-reporter-dump-variable) + ,(when (boundp 'tramp-backup-directory-alist) + 'tramp-backup-directory-alist) + ,(when (boundp 'tramp-bkup-backup-directory-info) + 'tramp-bkup-backup-directory-info) + ;; Dump cache. + (tramp-cache-data . tramp-reporter-dump-variable) + + ;; Non-tramp variables of interest + ;; Mask non-7bit characters + (shell-prompt-pattern . tramp-reporter-dump-variable) + backup-by-copying + backup-by-copying-when-linked + backup-by-copying-when-mismatch + ,(when (boundp 'backup-by-copying-when-privileged-mismatch) + 'backup-by-copying-when-privileged-mismatch) + ,(when (boundp 'password-cache) + 'password-cache) + ,(when (boundp 'password-cache-expiry) + 'password-cache-expiry) + ,(when (boundp 'backup-directory-alist) + 'backup-directory-alist) + ,(when (boundp 'bkup-backup-directory-info) + 'bkup-backup-directory-info) + file-name-handler-alist)) + + 'tramp-load-report-modules ; pre-hook + 'tramp-append-tramp-buffers ; post-hook + "\ +Enter your bug report in this message, including as much detail +as you possibly can about the problem, what you did to cause it +and what the local and remote machines are. + +If you can give a simple set of instructions to make this bug +happen reliably, please include those. Thank you for helping +kill bugs in Tramp. + +Another useful thing to do is to put + + (setq tramp-verbose 8) + +in the ~/.emacs file and to repeat the bug. Then, include the +contents of the *tramp/foo* buffer and the *debug tramp/foo* +buffer in your bug report. + +--bug report follows this line-- +")))) + +(defun tramp-reporter-dump-variable (varsym mailbuf) + "Pretty-print the value of the variable in symbol VARSYM. +Used for non-7bit chars in strings." + (let* ((reporter-eval-buffer (symbol-value 'reporter-eval-buffer)) + (val (with-current-buffer reporter-eval-buffer + (symbol-value varsym)))) + + (if (hash-table-p val) + ;; Pretty print the cache. + (set varsym (read (format "(%s)" (tramp-cache-print val)))) + ;; There are characters to be masked. + (when (and (boundp 'mm-7bit-chars) + (string-match + (concat "[^" (symbol-value 'mm-7bit-chars) "]") val)) + (with-current-buffer reporter-eval-buffer + (set varsym (format "(base64-decode-string \"%s\"" + (base64-encode-string val)))))) + + ;; Dump variable. + (funcall (symbol-function 'reporter-dump-variable) varsym mailbuf) + + (unless (hash-table-p val) + ;; Remove string quotation. + (forward-line -1) + (when (looking-at + (concat "\\(^.*\\)" "\"" ;; \1 " + "\\((base64-decode-string \\)" "\\\\" ;; \2 \ + "\\(\".*\\)" "\\\\" ;; \3 \ + "\\(\")\\)" "\"$")) ;; \4 " + (replace-match "\\1\\2\\3\\4") + (beginning-of-line) + (insert " ;; variable encoded due to non-printable characters\n")) + (forward-line 1)) + + ;; Reset VARSYM to old value. + (with-current-buffer reporter-eval-buffer + (set varsym val)))) + +(defun tramp-load-report-modules () + "Load needed modules for reporting." + + ;; We load message.el and mml.el from Gnus. + (if (featurep 'xemacs) + (progn + (load "message" 'noerror) + (load "mml" 'noerror)) + (require 'message nil 'noerror) + (require 'mml nil 'noerror)) + (when (functionp 'message-mode) + (funcall (symbol-function 'message-mode))) + (when (functionp 'mml-mode) + (funcall (symbol-function 'mml-mode) t))) + +(defun tramp-append-tramp-buffers () + "Append Tramp buffers and buffer local variables into the bug report." + + (goto-char (point-max)) + + ;; Dump buffer local variables. + (dolist (buffer + (delq nil + (mapcar + '(lambda (b) + (when (string-match "\\*tramp/" (buffer-name b)) b)) + (buffer-list)))) + (let ((reporter-eval-buffer buffer) + (buffer-name (buffer-name buffer)) + (elbuf (get-buffer-create " *tmp-reporter-buffer*"))) + (with-current-buffer elbuf + (emacs-lisp-mode) + (erase-buffer) + (insert "\n(setq\n") + (lisp-indent-line) + (funcall (symbol-function 'reporter-dump-variable) + 'buffer-name (current-buffer)) + (dolist (varsym-or-cons-cell (buffer-local-variables buffer)) + (let ((varsym (or (car-safe varsym-or-cons-cell) + varsym-or-cons-cell))) + (when (string-match "tramp" (symbol-name varsym)) + (funcall + (symbol-function 'reporter-dump-variable) + varsym (current-buffer))))) + (lisp-indent-line) + (insert ")\n")) + (insert-buffer-substring elbuf))) + + ;; Append buffers only when we are in message mode. + (when (and + (eq major-mode 'message-mode) + (boundp 'mml-mode) + (symbol-value 'mml-mode)) + + (let ((tramp-buf-regexp "\\*\\(debug \\)?tramp/") + (buffer-list (funcall (symbol-function 'tramp-list-tramp-buffers))) + (curbuf (current-buffer))) + + ;; There is at least one Tramp buffer. + (when buffer-list + (switch-to-buffer (list-buffers-noselect nil)) + (delete-other-windows) + (setq buffer-read-only nil) + (goto-char (point-min)) + (while (not (eobp)) + (if (re-search-forward + tramp-buf-regexp (tramp-compat-line-end-position) t) + (forward-line 1) + (forward-line 0) + (let ((start (point))) + (forward-line 1) + (kill-region start (point))))) + (insert " +The buffer(s) above will be appended to this message. If you +don't want to append a buffer because it contains sensitive data, +or because the buffer is too large, you should delete the +respective buffer. The buffer(s) will contain user and host +names. Passwords will never be included there.") + + (when (>= tramp-verbose 6) + (insert "\n\n") + (let ((start (point))) + (insert "\ +Please note that you have set `tramp-verbose' to a value of at +least 6. Therefore, the contents of files might be included in +the debug buffer(s).") + (add-text-properties start (point) (list 'face 'italic)))) + + (set-buffer-modified-p nil) + (setq buffer-read-only t) + (goto-char (point-min)) + + (if (y-or-n-p "Do you want to append the buffer(s)? ") + ;; OK, let's send. First we delete the buffer list. + (progn + (kill-buffer nil) + (switch-to-buffer curbuf) + (goto-char (point-max)) + (insert "\n\ +This is a special notion of the `gnus/message' package. If you +use another mail agent (by copying the contents of this buffer) +please ensure that the buffers are attached to your email.\n\n") + (dolist (buffer buffer-list) + (funcall (symbol-function 'mml-insert-empty-tag) + 'part 'type "text/plain" 'encoding "base64" + 'disposition "attachment" 'buffer buffer + 'description buffer)) + (set-buffer-modified-p nil)) + + ;; Don't send. Delete the message buffer. + (set-buffer curbuf) + (set-buffer-modified-p nil) + (kill-buffer nil) + (throw 'dont-send nil)))))) + +(defalias 'tramp-submit-bug 'tramp-bug) + +(provide 'tramp-cmds) + +;;; TODO: + +;; * Clean up unused *tramp/foo* buffers after a while. (Pete Forman) +;; * WIBNI there was an interactive command prompting for Tramp +;; method, hostname, username and filename and translates the user +;; input into the correct filename syntax (depending on the Emacs +;; flavor) (Reiner Steib) +;; * Let the user edit the connection properties interactively. +;; Something like `gnus-server-edit-server' in Gnus' *Server* buffer. +;; * It's just that when I come to Customize `tramp-default-user-alist' +;; I'm presented with a mismatch and raw lisp for a value. It is my +;; understanding that a variable declared with defcustom is a User +;; Option and should not be modified by the code. add-to-list is +;; called in several places. One way to handle that is to have a new +;; ordinary variable that gets its initial value from +;; tramp-default-user-alist and then is added to. (Pete Forman) + +;; arch-tag: 190d4c33-76bb-4e99-8b6f-71741f23d98c +;;; tramp-cmds.el ends here diff --git a/lisp/net/tramp-compat.el b/lisp/net/tramp-compat.el new file mode 100644 index 00000000000..b63b8c1e2fb --- /dev/null +++ b/lisp/net/tramp-compat.el @@ -0,0 +1,240 @@ +;;; tramp-compat.el --- Tramp compatibility functions + +;; Copyright (C) 2007 Free Software Foundation, Inc. + +;; Author: Michael Albinus <michael.albinus@gmx.de> +;; Keywords: comm, processes + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tramp's main Emacs version for development is GNU Emacs 23. This +;; package provides compatibility functions for GNU Emacs 21, GNU +;; Emacs 22 and XEmacs 21.4+. + +;;; Code: + +(eval-when-compile + + ;; Pacify byte-compiler. + (require 'cl)) + +(eval-and-compile + + (require 'custom) + + ;; Load the appropriate timer package. + (if (featurep 'xemacs) + (require 'timer-funcs) + (require 'timer)) + + ;; tramp-util offers integration into other (X)Emacs packages like + ;; compile.el, gud.el etc. Not necessary in Emacs 23. + (eval-after-load "tramp" + ;; We check whether `start-file-process' is an alias. + '(when (or (not (fboundp 'start-file-process)) + (symbolp (symbol-function 'start-file-process))) + (require 'tramp-util) + (add-hook 'tramp-unload-hook + '(lambda () + (when (featurep 'tramp-util) + (unload-feature 'tramp-util 'force)))))) + + ;; Make sure that we get integration with the VC package. When it + ;; is loaded, we need to pull in the integration module. Not + ;; necessary in Emacs 23. + (eval-after-load "vc" + (eval-after-load "tramp" + ;; We check whether `start-file-process' is an alias. + '(when (or (not (fboundp 'start-file-process)) + (symbolp (symbol-function 'start-file-process))) + (require 'tramp-vc) + (add-hook 'tramp-unload-hook + '(lambda () + (when (featurep 'tramp-vc) + (unload-feature 'tramp-vc 'force))))))) + + ;; Avoid byte-compiler warnings if the byte-compiler supports this. + ;; Currently, XEmacs supports this. + (when (featurep 'xemacs) + (unless (boundp 'byte-compile-default-warnings) + (defvar byte-compile-default-warnings nil)) + (delq 'unused-vars byte-compile-default-warnings)) + + ;; `last-coding-system-used' is unknown in XEmacs. + (unless (boundp 'last-coding-system-used) + (defvar last-coding-system-used nil)) + + ;; `directory-sep-char' is an obsolete variable in Emacs. But it is + ;; used in XEmacs, so we set it here and there. The following is + ;; needed to pacify Emacs byte-compiler. + (unless (boundp 'byte-compile-not-obsolete-var) + (defvar byte-compile-not-obsolete-var nil)) + (setq byte-compile-not-obsolete-var 'directory-sep-char) + + ;; `with-temp-message' does not exists in XEmacs. + (condition-case nil + (with-temp-message (current-message) nil) + (error (defmacro with-temp-message (message &rest body) `(progn ,@body)))) + + ;; `set-buffer-multibyte' comes from Emacs Leim. + (unless (fboundp 'set-buffer-multibyte) + (defalias 'set-buffer-multibyte 'ignore)) + + ;; `font-lock-add-keywords' does not exist in XEmacs. + (unless (fboundp 'font-lock-add-keywords) + (defalias 'font-lock-add-keywords 'ignore)) + + ;; `file-remote-p' has been introduced with Emacs 22. The version + ;; of XEmacs is not a magic file name function (yet); this is + ;; corrected in tramp-util.el. Here it is sufficient if the + ;; function exists. + (unless (fboundp 'file-remote-p) + (defalias 'file-remote-p 'tramp-handle-file-remote-p)) + + ;; `process-file' exists since Emacs 22. + (unless (fboundp 'process-file) + (defalias 'process-file 'tramp-handle-process-file)) + + ;; `start-file-process' is new in Emacs 23. + (unless (fboundp 'start-file-process) + (defalias 'start-file-process 'tramp-handle-start-file-process)) + + ;; `set-file-times' is also new in Emacs 23. + (unless (fboundp 'set-file-times) + (defalias 'set-file-times 'tramp-handle-set-file-times))) + +(defsubst tramp-compat-line-end-position () + "Return point at end of line (compat function). +Calls `line-end-position' or `point-at-eol' if defined, else +own implementation." + (cond + ((fboundp 'line-end-position) (funcall (symbol-function 'line-end-position))) + ((fboundp 'point-at-eol) (funcall (symbol-function 'point-at-eol))) + (t (save-excursion (end-of-line) (point))))) + +(defsubst tramp-compat-temporary-file-directory () + "Return name of directory for temporary files (compat function). +For Emacs, this is the variable `temporary-file-directory', for XEmacs +this is the function `temp-directory'." + (cond + ((boundp 'temporary-file-directory) (symbol-value 'temporary-file-directory)) + ((fboundp 'temp-directory) (funcall (symbol-function 'temp-directory))) + ((let ((d (getenv "TEMP"))) (and d (file-directory-p d))) + (file-name-as-directory (getenv "TEMP"))) + ((let ((d (getenv "TMP"))) (and d (file-directory-p d))) + (file-name-as-directory (getenv "TMP"))) + ((let ((d (getenv "TMPDIR"))) (and d (file-directory-p d))) + (file-name-as-directory (getenv "TMPDIR"))) + ((file-exists-p "c:/temp") (file-name-as-directory "c:/temp")) + (t (message (concat "Neither `temporary-file-directory' nor " + "`temp-directory' is defined -- using /tmp.")) + (file-name-as-directory "/tmp")))) + +;; `make-temp-file' exists in Emacs only. The third parameter SUFFIX +;; has been introduced with Emacs 22. We try it, if it fails, we fall +;; back to `make-temp-name', creating the temporary file immediately +;; in order to avoid a security hole. +(defsubst tramp-compat-make-temp-file (filename) + "Create a temporary file (compat function). +Add the extension of FILENAME, if existing." + (let ((prefix (expand-file-name + (symbol-value 'tramp-temp-name-prefix) + (tramp-compat-temporary-file-directory))) + (extension (file-name-extension filename t)) + result) + (condition-case nil + (setq result + (funcall (symbol-function 'make-temp-file) prefix nil extension)) + (error + ;; We use our own implementation, taken from files.el. + (while + (condition-case () + (progn + (setq result (concat (make-temp-name prefix) extension)) + (write-region + "" nil result nil 'silent nil + ;; 7th parameter is MUSTBENEW in Emacs, and + ;; CODING-SYSTEM in XEmacs. It is not a security + ;; hole in XEmacs if we cannot use this parameter, + ;; because XEmacs uses a user-specific subdirectory + ;; with 0700 permissions. + (when (not (featurep 'xemacs)) 'excl)) + nil) + (file-already-exists t)) + ;; The file was somehow created by someone else between + ;; `make-temp-name' and `write-region', let's try again. + nil))) + result)) + +;; `most-positive-fixnum' arrived in Emacs 22. Before, and in XEmacs, +;; it is a fixed value. +(defsubst tramp-compat-most-positive-fixnum () + "Return largest positive integer value (compat function)." + (cond + ((boundp 'most-positive-fixnum) (symbol-value 'most-positive-fixnum)) + ;; Default value in XEmacs and Emacs 21. + (t 134217727))) + +;; ID-FORMAT exists since Emacs 22. +(defun tramp-compat-file-attributes (filename &optional id-format) + "Like `file-attributes' for Tramp files (compat function)." + (cond + ((or (null id-format) (eq id-format 'integer)) + (file-attributes filename)) + ((file-remote-p filename) + (funcall (symbol-function 'tramp-handle-file-attributes) + filename id-format)) + (t (condition-case nil + (funcall (symbol-function 'file-attributes) filename id-format) + (error (file-attributes filename)))))) + +;; PRESERVE-UID-GID has been introduced with Emacs 23. It does not +;; hurt to ignore it for other (X)Emacs versions. +(defun tramp-compat-copy-file + (filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) + "Like `copy-file' for Tramp files (compat function)." + (if preserve-uid-gid + (funcall + (symbol-function 'copy-file) + filename newname ok-if-already-exists keep-date preserve-uid-gid) + (copy-file filename newname ok-if-already-exists keep-date))) + +;; `copy-tree' is a built-in function in XEmacs. In Emacs 21, it is +;; an auoloaded function in cl-extra.el. Since Emacs 22, it is part +;; of subr.el. There are problems when autoloading, therefore we test +;; for for `subrp' and `symbol-file'. Implementation is taken from Emacs23. +(defun tramp-compat-copy-tree (tree) + "Make a copy of TREE (compat function)." + (if (or (subrp 'copy-tree) (symbol-file 'copy-tree)) + (funcall (symbol-function 'copy-tree) tree) + (let (result) + (while (consp tree) + (let ((newcar (car tree))) + (if (consp (car tree)) + (setq newcar (tramp-compat-copy-tree (car tree)))) + (push newcar result)) + (setq tree (cdr tree))) + (nconc (nreverse result) tree)))) + +(provide 'tramp-compat) + +;;; TODO: + +;; arch-tag: 0e724b18-6699-4f87-ad96-640b272e5c85 +;;; tramp-compat.el ends here diff --git a/lisp/net/tramp-fish.el b/lisp/net/tramp-fish.el new file mode 100644 index 00000000000..95091c276bc --- /dev/null +++ b/lisp/net/tramp-fish.el @@ -0,0 +1,1166 @@ +;;; tramp-fish.el --- Tramp access functions for FISH protocol + +;; Copyright (C) 2006, 2007 Free Software Foundation, Inc. + +;; Author: Michael Albinus <michael.albinus@gmx.de> +;; Keywords: comm, processes + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Access functions for FIles transferred over SHell protocol from Tramp. + +;; FISH is a protocol developped for the GNU Midnight Commander +;; <https://savannah.gnu.org/projects/mc>. A client connects to a +;; remote host via ssh (or rsh, shall be configurable), and starts +;; there a fish server via the command "start_fish_server". All +;; commands from the client have the form "#FISH_COMMAND\n" (always +;; one line), followed by equivalent shell commands in case there is +;; no fish server running. + +;; The fish server (or the equivalent shell commands) must return the +;; response, which is finished by a line "### xxx <optional text>\n". +;; "xxx" stands for 3 digits, representing a return code. Return +;; codes "# 000" and "# 001" are reserved for fallback implementation +;; with native shell commands; they are not used inside the server. See +;; <http://cvs.savannah.gnu.org/viewcvs/mc/vfs/README.fish?root=mc&view=markup> +;; for details of original specification. + +;; The GNU Midnight Commander implements the original fish protocol +;; version 0.0.2. The KDE Konqueror has its own implementation, which +;; can be found at +;; <http://websvn.kde.org/branches/KDE/3.5/kdebase/kioslave/fish>. It +;; implements an extended protocol version 0.0.3. Additionally, it +;; provides a fish server implementation in Perl (which is the only +;; implementation I've heard of). The following command reference is +;; based on that implementation. + +;; All commands return either "### 2xx\n" (OK) or "### 5xx <optional text>\n" +;; (NOK). Return codes are mentioned only if they are different from this. +;; Spaces in any parameter must be escaped by "\ ". + +;; Command/Return Code Comment +;; +;; #FISH initial connection, not used +;; in .fishsrv.pl +;; ### 100 transfer fish server missing server, or wrong checksum +;; version 0.0.3 only + +;; #VER a.b.c <commands requested> +;; VER x.y.z <commands offered> .fishsrv.pl response is not uptodate + +;; #PWD +;; /path/to/file + +;; #CWD /some/path + +;; #COPY /path/a /path/b version 0.0.3 only + +;; #RENAME /path/a /path/b + +;; #SYMLINK /path/a /path/b + +;; #LINK /path/a /path/b + +;; #DELE /some/path + +;; #MKD /some/path + +;; #RMD /some/path + +;; #CHOWN user /file/name + +;; #CHGRP group /file/name + +;; #CHMOD 1234 file + +;; #READ <offset> <size> /path/and/filename +;; ### 291 successful exit when reading +;; ended at eof +;; ### 292 successful exit when reading +;; did not end at eof + +;; #WRITE <offset> <size> /path/and/filename + +;; #APPEND <size> /path/and/filename version 0.0.3 only + +;; #LIST /directory +;; <number of entries> version 0.0.3 only +;; ### 100 version 0.0.3 only +;; P<unix permissions> <owner>.<group> +;; S<size> +;; d<3-letters month name> <day> <year or HH:MM> +;; D<year> <month> <day> <hour> <minute> <second>[.1234] +;; E<major-of-device>,<minor> +;; :<filename> +;; L<filename symlink points to> +;; M<mimetype> version 0.0.3 only +;; <blank line to separate items> + +;; #STAT /file version 0.0.3 only +;; like #LIST except for directories +;; <number of entries> +;; ### 100 +;; P<unix permissions> <owner>.<group> +;; S<size> +;; d<3-letters month name> <day> <year or HH:MM> +;; D<year> <month> <day> <hour> <minute> <second>[.1234] +;; E<major-of-device>,<minor> +;; :<filename> +;; L<filename symlink points to> +;; <blank line to separate items> + +;; #RETR /some/name +;; <filesize> +;; ### 100 +;; <binary data> exactly filesize bytes +;; ### 200 with no preceding newline + +;; #STOR <size> /file/name +;; ### 100 +;; <data> exactly size bytes +;; ### 001 partial success + +;; #EXEC <command> <tmpfile> version 0.0.3 only +;; <tmpfile> must not exists. It contains the output of <command>. +;; It can be retrieved afterwards. Last line is +;; ###RESULT: <returncode> + +;; This implementation is meant as proof of the concept, whether there +;; is a better performance compared with the native ssh method. It +;; looks like the file information retrieval is slower, especially the +;; #LIST command. On the other hand, the file contents transmission +;; seems to perform better than other inline methods, because there is +;; no need for data encoding/decoding, and it supports the APPEND +;; parameter of `write-region'. Transfer of binary data fails due to +;; Emacs' process input/output handling. + + +;;; Code: + +(require 'tramp) +(require 'tramp-cache) +(require 'tramp-compat) + +;; Define FISH method ... +(defcustom tramp-fish-method "fish" + "*Method to connect via FISH protocol." + :group 'tramp + :type 'string) + +;; ... and add it to the method list. +(add-to-list 'tramp-methods (cons tramp-fish-method nil)) + +;; Add a default for `tramp-default-user-alist'. Default is the local user. +(add-to-list 'tramp-default-user-alist + `(,tramp-fish-method nil ,(user-login-name))) + +;; Add completion function for FISH method. +(tramp-set-completion-function + tramp-fish-method tramp-completion-function-alist-ssh) + +(defconst tramp-fish-continue-prompt-regexp "^### 100.*\n" + "FISH return code OK.") + +;; It cannot be a defconst, occasionally we bind it locally. +(defvar tramp-fish-ok-prompt-regexp "^### 200\n" + "FISH return code OK.") + +(defconst tramp-fish-error-prompt-regexp "^### \\(4\\|5\\)[0-9]+.*\n" + "Regexp for possible error strings of FISH servers. +Used instead of analyzing error codes of commands.") + +(defcustom tramp-fish-start-fish-server-command + (concat "stty intr \"\" quit \"\" erase \"\" kill \"\" eof \"\" eol \"\" eol2 \"\" swtch \"\" start \"\" stop \"\" susp \"\" rprnt \"\" werase \"\" lnext \"\" flush \"\"; " + "perl .fishsrv.pl " + "`grep 'ARGV\\[0\\]' .fishsrv.pl | " + "sed -e 's/^[^\"]*\"//' -e 's/\"[^\"]*$//'`; " + "exit") + "*Command to connect via FISH protocol." + :group 'tramp + :type 'string) + +;; New handlers should be added here. +(defconst tramp-fish-file-name-handler-alist + '( + ;; `access-file' performed by default handler + (add-name-to-file . tramp-fish-handle-add-name-to-file) + ;; `byte-compiler-base-file-name' performed by default handler + (copy-file . tramp-fish-handle-copy-file) + (delete-directory . tramp-fish-handle-delete-directory) + (delete-file . tramp-fish-handle-delete-file) + ;; `diff-latest-backup-file' performed by default handler + (directory-file-name . tramp-handle-directory-file-name) + (directory-files . tramp-handle-directory-files) + (directory-files-and-attributes . tramp-fish-handle-directory-files-and-attributes) + ;; `dired-call-process' performed by default handler + ;; `dired-compress-file' performed by default handler + ;; `dired-uncache' performed by default handler + (expand-file-name . tramp-fish-handle-expand-file-name) + ;; `file-accessible-directory-p' performed by default handler + (file-attributes . tramp-fish-handle-file-attributes) + (file-directory-p . tramp-fish-handle-file-directory-p) + (file-executable-p . tramp-fish-handle-file-executable-p) + (file-exists-p . tramp-fish-handle-file-exists-p) + (file-local-copy . tramp-fish-handle-file-local-copy) + (file-remote-p . tramp-handle-file-remote-p) + (file-modes . tramp-handle-file-modes) + (file-name-all-completions . tramp-fish-handle-file-name-all-completions) + ;; `file-name-as-directory' performed by default handler + (file-name-completion . tramp-handle-file-name-completion) + (file-name-directory . tramp-handle-file-name-directory) + (file-name-nondirectory . tramp-handle-file-name-nondirectory) + ;; `file-name-sans-versions' performed by default handler + (file-newer-than-file-p . tramp-fish-handle-file-newer-than-file-p) + (file-ownership-preserved-p . ignore) + (file-readable-p . tramp-fish-handle-file-readable-p) + (file-regular-p . tramp-handle-file-regular-p) + (file-symlink-p . tramp-handle-file-symlink-p) + ;; `file-truename' performed by default handler + (file-writable-p . tramp-fish-handle-file-writable-p) + (find-backup-file-name . tramp-handle-find-backup-file-name) + ;; `find-file-noselect' performed by default handler + ;; `get-file-buffer' performed by default handler + (insert-directory . tramp-fish-handle-insert-directory) + (insert-file-contents . tramp-fish-handle-insert-file-contents) + (load . tramp-handle-load) + (make-directory . tramp-fish-handle-make-directory) + (make-directory-internal . tramp-fish-handle-make-directory-internal) + (make-symbolic-link . tramp-fish-handle-make-symbolic-link) + (rename-file . tramp-fish-handle-rename-file) + (set-file-modes . tramp-fish-handle-set-file-modes) + (set-file-times . tramp-fish-handle-set-file-times) + (set-visited-file-modtime . ignore) + (shell-command . tramp-handle-shell-command) + (substitute-in-file-name . tramp-handle-substitute-in-file-name) + (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory) + (vc-registered . ignore) + (verify-visited-file-modtime . ignore) + (write-region . tramp-fish-handle-write-region) + (executable-find . tramp-fish-handle-executable-find) + (start-file-process . ignore) + (process-file . tramp-fish-handle-process-file) +) + "Alist of handler functions for Tramp FISH method. +Operations not mentioned here will be handled by the default Emacs primitives.") + +(defun tramp-fish-file-name-p (filename) + "Check if it's a filename for FISH protocol." + (let ((v (tramp-dissect-file-name filename))) + (string= (tramp-file-name-method v) tramp-fish-method))) + +(defun tramp-fish-file-name-handler (operation &rest args) + "Invoke the FISH related OPERATION. +First arg specifies the OPERATION, second arg is a list of arguments to +pass to the OPERATION." + (let ((fn (assoc operation tramp-fish-file-name-handler-alist))) + (if fn + (save-match-data (apply (cdr fn) args)) + (tramp-run-real-handler operation args)))) + +(add-to-list 'tramp-foreign-file-name-handler-alist + (cons 'tramp-fish-file-name-p 'tramp-fish-file-name-handler)) + + +;; File name primitives + +(defun tramp-fish-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 + (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 v2-localname) + (unless (tramp-fish-send-command-and-check + v1 (format "#LINK %s %s" v1-localname v2-localname)) + (tramp-error + v1 'file-error "Error with add-name-to-file %s" newname))))) + +(defun tramp-fish-handle-copy-file + (filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) + "Like `copy-file' for Tramp files." + (tramp-fish-do-copy-or-rename-file + 'copy filename newname ok-if-already-exists keep-date preserve-uid-gid)) + +(defun tramp-fish-handle-delete-directory (directory) + "Like `delete-directory' for Tramp files." + (when (file-exists-p directory) + (with-parsed-tramp-file-name + (directory-file-name (expand-file-name directory)) nil + (tramp-flush-directory-property v localname) + (tramp-fish-send-command-and-check v (format "#RMD %s" localname))))) + +(defun tramp-fish-handle-delete-file (filename) + "Like `delete-file' for Tramp files." + (when (file-exists-p filename) + (with-parsed-tramp-file-name (expand-file-name filename) nil + (tramp-flush-file-property v localname) + (tramp-fish-send-command-and-check v (format "#DELE %s" localname))))) + +(defun tramp-fish-handle-directory-files-and-attributes + (directory &optional full match nosort id-format) + "Like `directory-files-and-attributes' for Tramp files." + (mapcar + (lambda (x) + ;; We cannot call `file-attributes' for backward compatibility reasons. + ;; Its optional parameter ID-FORMAT is introduced with Emacs 22. + (cons x (tramp-fish-handle-file-attributes + (if full x (expand-file-name x directory)) id-format))) + (directory-files directory full match nosort))) + +(defun tramp-fish-handle-expand-file-name (name &optional dir) + "Like `expand-file-name' for Tramp files." + ;; If DIR is not given, use DEFAULT-DIRECTORY or "/". + (setq dir (or dir default-directory "/")) + ;; Unless NAME is absolute, concat DIR and NAME. + (unless (file-name-absolute-p name) + (setq name (concat (file-name-as-directory dir) name))) + ;; If NAME is not a Tramp file, run the real handler, + (if (or (tramp-completion-mode-p) (not (tramp-tramp-file-p name))) + (tramp-drop-volume-letter + (tramp-run-real-handler 'expand-file-name (list name nil))) + ;; Dissect NAME. + (with-parsed-tramp-file-name name nil + (unless (file-name-absolute-p localname) + (setq localname (concat "~/" localname))) + ;; Tilde expansion if necessary. + (when (string-match "\\`\\(~[^/]*\\)\\(.*\\)\\'" localname) + (let ((uname (match-string 1 localname)) + (fname (match-string 2 localname))) + ;; We cannot apply "~user/", because this is not supported + ;; by the FISH protocol. + (unless (string-equal uname "~") + (tramp-error + v 'file-error "Tilde expansion not supported for %s" name)) + (setq uname + (with-connection-property v uname + (tramp-fish-send-command-and-check v "#PWD") + (with-current-buffer (tramp-get-buffer v) + (goto-char (point-min)) + (buffer-substring (point) (tramp-compat-line-end-position))))) + (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. + (tramp-let-maybe directory-sep-char ?/ + (let ((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))))))))) + +(defun tramp-fish-handle-file-attributes (filename &optional id-format) + "Like `file-attributes' for Tramp files." + (with-parsed-tramp-file-name (expand-file-name filename) nil + (with-file-property v localname (format "file-attributes-%s" id-format) + (cdr (car (tramp-fish-get-file-entries v localname nil)))))) + +(defun tramp-fish-handle-file-directory-p (filename) + "Like `file-directory-p' for Tramp files." + (let ((attributes (file-attributes filename))) + (and attributes + (or (string-match "d" (nth 8 attributes)) + (and (file-symlink-p filename) + (with-parsed-tramp-file-name filename nil + (file-directory-p + (tramp-make-tramp-file-name + method user host (nth 0 attributes)))))) + t))) + +(defun tramp-fish-handle-file-exists-p (filename) + "Like `file-exists-p' for Tramp files." + (and (file-attributes filename) t)) + +(defun tramp-fish-handle-file-executable-p (filename) + "Like `file-executable-p' for Tramp files." + (with-parsed-tramp-file-name (expand-file-name filename) nil + (with-file-property v localname "file-executable-p" + (when (file-exists-p filename) + (let ((mode-chars (string-to-vector (nth 8 (file-attributes filename)))) + (home-directory + (tramp-make-tramp-file-name + method user host + (tramp-get-connection-property v "home-directory" nil)))) + (or (and (char-equal (aref mode-chars 3) ?x) + (equal (nth 2 (file-attributes filename)) + (nth 2 (file-attributes home-directory)))) + (and (char-equal (aref mode-chars 6) ?x) + (equal (nth 3 (file-attributes filename)) + (nth 3 (file-attributes home-directory)))) + (char-equal (aref mode-chars 9) ?x))))))) + +(defun tramp-fish-handle-file-readable-p (filename) + "Like `file-readable-p' for Tramp files." + (with-parsed-tramp-file-name (expand-file-name filename) nil + (with-file-property v localname "file-readable-p" + (when (file-exists-p filename) + (let ((mode-chars (string-to-vector (nth 8 (file-attributes filename)))) + (home-directory + (tramp-make-tramp-file-name + method user host + (tramp-get-connection-property v "home-directory" nil)))) + (or (and (char-equal (aref mode-chars 1) ?r) + (equal (nth 2 (file-attributes filename)) + (nth 2 (file-attributes home-directory)))) + (and (char-equal (aref mode-chars 4) ?r) + (equal (nth 3 (file-attributes filename)) + (nth 3 (file-attributes home-directory)))) + (char-equal (aref mode-chars 7) ?r))))))) + +(defun tramp-fish-handle-file-writable-p (filename) + "Like `file-writable-p' for Tramp files." + (with-parsed-tramp-file-name (expand-file-name filename) nil + (with-file-property v localname "file-writable-p" + (if (not (file-exists-p filename)) + ;; If file doesn't exist, check if directory is writable. + (and (file-directory-p (file-name-directory filename)) + (file-writable-p (file-name-directory filename))) + ;; Existing files must be writable. + (let ((mode-chars (string-to-vector (nth 8 (file-attributes filename)))) + (home-directory + (tramp-make-tramp-file-name + method user host + (tramp-get-connection-property v "home-directory" nil)))) + (or (and (char-equal (aref mode-chars 2) ?w) + (equal (nth 2 (file-attributes filename)) + (nth 2 (file-attributes home-directory)))) + (and (char-equal (aref mode-chars 5) ?w) + (equal (nth 3 (file-attributes filename)) + (nth 3 (file-attributes home-directory)))) + (char-equal (aref mode-chars 8) ?w))))))) + +(defun tramp-fish-handle-file-local-copy (filename) + "Like `file-local-copy' for Tramp files." + (with-parsed-tramp-file-name (expand-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 ((tmpfile (tramp-compat-make-temp-file filename))) + (tramp-message v 4 "Fetching %s to tmp file %s..." filename tmpfile) + (when (tramp-fish-retrieve-data v) + ;; Save file + (with-current-buffer (tramp-get-buffer v) + (write-region (point-min) (point-max) tmpfile)) + (tramp-message v 4 "Fetching %s to tmp file %s...done" filename tmpfile) + tmpfile)))) + +;; This function should return "foo/" for directories and "bar" for +;; files. +(defun tramp-fish-handle-file-name-all-completions (filename directory) + "Like `file-name-all-completions' for Tramp files." + (all-completions + filename + (with-parsed-tramp-file-name (expand-file-name directory) nil + (with-file-property v localname "file-name-all-completions" + (save-match-data + (let ((entries + (with-file-property v localname "file-entries" + (tramp-fish-get-file-entries v localname t)))) + (mapcar + (lambda (x) + (list + (if (string-match "d" (nth 9 x)) + (file-name-as-directory (nth 0 x)) + (nth 0 x)))) + entries))))))) + +(defun tramp-fish-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) + (t (tramp-time-less-p (nth 5 (file-attributes file2)) + (nth 5 (file-attributes file1)))))) + +(defun tramp-fish-handle-insert-directory + (filename switches &optional wildcard full-directory-p) + "Like `insert-directory' for Tramp files. +WILDCARD and FULL-DIRECTORY-P are not handled." + (setq filename (expand-file-name filename)) + (when (file-directory-p filename) + ;; This check is a little bit strange, but in `dired-add-entry' + ;; this function is called with a non-directory ... + (setq filename (file-name-as-directory filename))) + + (with-parsed-tramp-file-name filename nil + (tramp-flush-file-property v localname) + (save-match-data + (let ((entries + (with-file-property v localname "file-entries" + (tramp-fish-get-file-entries v localname t)))) + + ;; Sort entries + (setq entries + (sort + entries + (lambda (x y) + (if (string-match "t" switches) + ;; Sort by date. + (tramp-time-less-p (nth 6 y) (nth 6 x)) + ;; Sort by name. + (string-lessp (nth 0 x) (nth 0 y)))))) + + ;; Print entries. + (mapcar + (lambda (x) + (insert + (format + "%10s %3d %-8s %-8s %8s %s %s%s\n" + (nth 9 x) ; mode + 1 ; hardlinks + (nth 3 x) ; uid + (nth 4 x) ; gid + (nth 8 x) ; size + (format-time-string + (if (tramp-time-less-p + (tramp-time-subtract (current-time) (nth 6 x)) + tramp-half-a-year) + "%b %e %R" + "%b %e %Y") + (nth 6 x)) ; date + (nth 0 x) ; file name + (if (stringp (nth 1 x)) (format " -> %s" (nth 1 x)) ""))) + (forward-line) + (beginning-of-line)) + entries))))) + +(defun tramp-fish-handle-insert-file-contents + (filename &optional visit beg end replace) + "Like `insert-file-contents' for Tramp files." + (barf-if-buffer-read-only) + (when visit + (setq buffer-file-name (expand-file-name filename)) + (set-visited-file-modtime) + (set-buffer-modified-p nil)) + + (with-parsed-tramp-file-name filename nil + (if (not (file-exists-p filename)) + (tramp-error + v 'file-error "File %s not found on remote host" filename) + + (let ((point (point)) + size) + (tramp-message v 4 "Fetching file %s..." filename) + (when (tramp-fish-retrieve-data v) + ;; Insert file + (insert + (with-current-buffer (tramp-get-buffer v) + (let ((beg (or beg (point-min))) + (end (min (or end (point-max)) (point-max)))) + (setq size (- end beg)) + (buffer-substring beg end)))) + (goto-char point)) + (tramp-message v 4 "Fetching file %s...done" filename) + + (list (expand-file-name filename) size))))) + +(defun tramp-fish-handle-make-directory (dir &optional parents) + "Like `make-directory' for Tramp files." + (setq dir (directory-file-name (expand-file-name dir))) + (unless (file-name-absolute-p dir) + (setq dir (expand-file-name dir default-directory))) + (with-parsed-tramp-file-name dir nil + (save-match-data + (let ((ldir (file-name-directory dir))) + ;; Make missing directory parts + (when (and parents (not (file-directory-p ldir))) + (make-directory ldir parents)) + ;; Just do it + (when (file-directory-p ldir) + (make-directory-internal dir)) + (unless (file-directory-p dir) + (tramp-error v 'file-error "Couldn't make directory %s" dir)))))) + +(defun tramp-fish-handle-make-directory-internal (directory) + "Like `make-directory-internal' for Tramp files." + (setq directory (directory-file-name (expand-file-name directory))) + (unless (file-name-absolute-p directory) + (setq directory (expand-file-name directory default-directory))) + (when (file-directory-p (file-name-directory directory)) + (with-parsed-tramp-file-name directory nil + (save-match-data + (unless + (tramp-fish-send-command-and-check v (format "#MKD %s" localname)) + (tramp-error + v 'file-error "Couldn't make directory %s" directory)))))) + +(defun tramp-fish-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 nil + ;; Do the 'confirm if exists' thing. + (when (file-exists-p linkname) + ;; What to do? + (if (or (null ok-if-already-exists) ; not allowed to exist + (and (numberp ok-if-already-exists) + (not (yes-or-no-p + (format + "File %s already exists; make it a link anyway? " + localname))))) + (tramp-error + v 'file-already-exists "File %s already exists" 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))))) + + ;; 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. + (unless + (tramp-fish-send-command-and-check + v (format "#SYMLINK %s %s" filename localname)) + (tramp-error v 'file-error "Error creating symbolic link %s" linkname)))) + +(defun tramp-fish-handle-rename-file + (filename newname &optional ok-if-already-exists) + "Like `rename-file' for Tramp files." + (tramp-fish-do-copy-or-rename-file + 'rename filename newname ok-if-already-exists t)) + +(defun tramp-fish-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) + (unless (tramp-fish-send-command-and-check + v (format "#CHMOD %s %s" + (tramp-decimal-to-octal mode) + (tramp-shell-quote-argument localname))) + (tramp-error + v 'file-error "Error while changing file's mode %s" filename)))) + +(defun tramp-fish-handle-set-file-times (filename &optional time) + "Like `set-file-times' for Tramp files." + (with-parsed-tramp-file-name filename nil + (let ((time (if (or (null time) (equal time '(0 0))) (current-time) time))) + (zerop (process-file + "touch" nil nil nil "-t" + (format-time-string "%Y%m%d%H%M.%S" time) + (tramp-shell-quote-argument localname)))))) + +(defun tramp-fish-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 + ;; 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"))) + + (tramp-flush-file-property v localname) + + ;; Send command + (let ((tramp-fish-ok-prompt-regexp + (concat + tramp-fish-ok-prompt-regexp "\\|" + tramp-fish-continue-prompt-regexp))) + (tramp-fish-send-command + v (format "%s %d %s\n### 100" + (if append "#APPEND" "#STOR") (- end start) localname))) + + ;; Send data, if there are any. + (when (> end start) + (tramp-fish-send-command v (buffer-substring-no-properties start end))) + + (when (eq visit t) + (set-visited-file-modtime)))) + +(defun tramp-fish-handle-executable-find (command) + "Like `executable-find' for Tramp files." + (with-temp-buffer + (if (zerop (process-file "which" nil t nil command)) + (progn + (goto-char (point-min)) + (buffer-substring (point-min) (tramp-compat-line-end-position)))))) + +(defun tramp-fish-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 output tmpoutput stderr tmpstderr + outbuf tmpfile 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. + (setq output (tramp-make-tramp-temp-file v) + tmpoutput (tramp-make-tramp-file-name method user host 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))))) + ;; 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))) + + ;; Goto working directory. + (unless + (tramp-fish-send-command-and-check + v (format "#CWD %s" (tramp-shell-quote-argument localname))) + (tramp-error v 'file-error "No such directory: %s" default-directory)) + ;; Send the command. It might not return in time, so we protect it. + (condition-case nil + (unwind-protect + (unless (tramp-fish-send-command-and-check + v (format + "#EXEC %s %s" + (tramp-shell-quote-argument command) output)) + (error nil)) + ;; Check return code. + (setq tmpfile + (file-local-copy + (tramp-make-tramp-file-name method user host output))) + (with-temp-buffer + (insert-file-contents tmpfile) + (goto-char (point-max)) + (forward-line -1) + (looking-at "^###RESULT: \\([0-9]+\\)") + (setq ret (string-to-number (match-string 1))) + (delete-region (point) (point-max)) + (write-region (point-min) (point-max) tmpfile)) + ;; We should show the output anyway. + (when outbuf + (with-current-buffer outbuf (insert-file-contents tmpfile)) + (when display (display-buffer outbuf)))) + ;; When the user did interrupt, we should do it also. + (error (setq ret 1))) + + ;; Provide error file. + (when tmpstderr (rename-file tmpstderr (cadr destination) t)) + ;; Cleanup. + (when tmpinput (delete-file tmpinput)) + (when tmpoutput (delete-file tmpoutput)) + ;; Return exit status. + ret))) + + +;; Internal file name functions + +(defun tramp-fish-do-copy-or-rename-file + (op filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) + "Copy or rename a remote file. +OP must be `copy' or `rename' and indicates the operation to +perform. FILENAME specifies the file to copy or rename, NEWNAME +is the name of the new file (for copy) or the new name of the +file (for rename). OK-IF-ALREADY-EXISTS means don't barf if +NEWNAME exists already. KEEP-DATE means to make sure that +NEWNAME has the same timestamp as FILENAME. + +This function is invoked by `tramp-fish-handle-copy-file' and +`tramp-fish-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))) + + (unless ok-if-already-exists + (when (and t2 (file-exists-p newname)) + (with-parsed-tramp-file-name newname nil + (tramp-error + v 'file-already-exists "File %s already exists" newname)))) + + (prog1 + (cond + ;; Both are Tramp files. + ((and t1 t2) + (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-fish-do-copy-or-rename-file-directly + op filename newname keep-date preserve-uid-gid)) + ;; 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) + ;; Use the generic method via a Tramp buffer. + (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"))) + ;; When newname did exist, we have wrong cached values. + (when t2 + (with-parsed-tramp-file-name newname nil + (tramp-flush-file-property v localname) + (tramp-flush-file-property v (file-name-directory localname))))))) + +(defun tramp-fish-do-copy-or-rename-file-directly + (op filename newname keep-date preserve-uid-gid) + "Invokes `COPY' or `RENAME' on the remote system. +OP must be one of `copy' or `rename', indicating `cp' or `mv', +respectively. VEC specifies the connection. LOCALNAME1 and +LOCALNAME2 specify the two arguments of `cp' or `mv'. If +KEEP-DATE is non-nil, preserve the time stamp when copying. +PRESERVE-UID-GID is completely ignored." + (with-parsed-tramp-file-name filename v1 + (with-parsed-tramp-file-name newname v2 + (tramp-fish-send-command + v1 + (format "%s %s %s" + (if (eq op 'copy) "#COPY" "#RENAME") + (tramp-shell-quote-argument v1-localname) + (tramp-shell-quote-argument v2-localname))))) + ;; KEEP-DATE handling. + (when (and keep-date (functionp 'set-file-times)) + (set-file-times newname (nth 5 (file-attributes filename)))) + ;; Set the mode. + (set-file-modes newname (file-modes filename))) + +(defun tramp-fish-get-file-entries (vec localname list) + "Read entries returned by FISH server. +When LIST is true, a #LIST command will be sent, including all entries +of a directory. Otherwise, #STAT is sent for just one entry. +Result is a list of (LOCALNAME LINK COUNT UID GID ATIME MTIME CTIME +SIZE MODE WEIRD INODE DEVICE)." + (block nil + (with-current-buffer (tramp-get-buffer vec) + ;; #LIST does not work properly with trailing "/", at least in + ;; .fishsrv.pl. + (when (string-match "/$" localname) + (setq localname (concat localname "."))) + + (let ((command (format "%s %s" (if list "#LIST" "#STAT") localname)) + buffer-read-only num res) + + ;; Send command + (tramp-fish-send-command vec command) + + ;; Read number of entries + (goto-char (point-min)) + (condition-case nil + (unless (integerp (setq num (read (current-buffer)))) (error nil)) + (error (return nil))) + (forward-line) + (delete-region (point-min) (point)) + + ;; Read return code + (goto-char (point-min)) + (condition-case nil + (unless (looking-at tramp-fish-continue-prompt-regexp) (error nil)) + (error (return nil))) + (forward-line) + (delete-region (point-min) (point)) + + ;; Loop the listing + (dotimes (i num) + (let ((item (tramp-fish-read-file-entry))) + ;; Add inode and device. + (add-to-list + 'res (append item + (list (tramp-get-inode vec) + (tramp-get-device vec)))))) + + ;; Read return code + (goto-char (point-min)) + (condition-case nil + (unless (looking-at tramp-fish-ok-prompt-regexp) (error nil)) + (error (tramp-error + vec 'file-error + "`%s' does not return a valid Lisp expression: `%s'" + command (buffer-string)))) + (forward-line) + (delete-region (point-min) (point)) + + res)))) + +(defun tramp-fish-read-file-entry () + "Parse entry in output buffer. +Result is the list (LOCALNAME LINK COUNT UID GID ATIME MTIME CTIME +SIZE MODE WEIRD)." + ;; We are called from `tramp-fish-get-file-entries', which sets the + ;; current buffer. + (let (buffer-read-only localname link uid gid mtime size mode) + (block nil + (while t + (cond + ;; P<unix permissions> <owner>.<group> + ((looking-at "^P\\(.+\\)\\s-\\(.+\\)\\.\\(.+\\)$") + (setq mode (match-string 1)) + (setq uid (match-string 2)) + (setq gid (match-string 3)) + (when (string-match "^d" mode) (setq link t))) + ;; S<size> + ((looking-at "^S\\([0-9]+\\)$") + (setq size (string-to-number (match-string 1)))) + ;; D<year> <month> <day> <hour> <minute> <second>[.1234] + ((looking-at + "^D\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\(\\S-+\\)$") + (setq mtime + (encode-time + (string-to-number (match-string 6)) + (string-to-number (match-string 5)) + (string-to-number (match-string 4)) + (string-to-number (match-string 3)) + (string-to-number (match-string 2)) + (string-to-number (match-string 1))))) + ;; d<3-letters month name> <day> <year or HH:MM> + ((looking-at "^d") nil) + ;; E<major-of-device>,<minor> + ((looking-at "^E") nil) + ;; :<filename> + ((looking-at "^:\\(.+\\)$") + (setq localname (match-string 1))) + ;; L<filename symlink points to> + ((looking-at "^L\\(.+\\)$") + (setq link (match-string 1))) + ;; M<mimetype> + ((looking-at "^M\\(.+\\)$") nil) + ;; last line + ((looking-at "^$") + (return))) + ;; delete line + (forward-line) + (delete-region (point-min) (point)))) + + ;; delete trailing empty line + (forward-line) + (delete-region (point-min) (point)) + + ;; Return entry in file-attributes format + (list localname link -1 uid gid '(0 0) mtime '(0 0) size mode nil))) + +(defun tramp-fish-retrieve-data (vec) + "Reads remote data for FISH protocol. +The data are left in the connection buffer of VEC for further processing. +Returns the size of the data." + (block nil + (with-current-buffer (tramp-get-buffer vec) + ;; The retrieved data might be in binary format, without + ;; trailing newline. Therefore, the OK prompt might not start + ;; at the beginning of a line. + (let ((tramp-fish-ok-prompt-regexp "### 200\n") + size) + + ;; Send command + (tramp-fish-send-command + vec (format "#RETR %s" (tramp-file-name-localname vec))) + + ;; Read filesize + (goto-char (point-min)) + (condition-case nil + (unless (integerp (setq size (read (current-buffer)))) (error nil)) + (error (return nil))) + (forward-line) + (delete-region (point-min) (point)) + + ;; Read return code + (goto-char (point-min)) + (condition-case nil + (unless (looking-at tramp-fish-continue-prompt-regexp) (error nil)) + (error (return nil))) + (forward-line) + (delete-region (point-min) (point)) + + ;; The received data might contain the OK prompt already, so + ;; there might be outstanding data. + (while (/= (+ size (length tramp-fish-ok-prompt-regexp)) + (- (point-max) (point-min))) + (tramp-wait-for-regexp + (tramp-get-connection-process vec) nil + (concat tramp-fish-ok-prompt-regexp "$"))) + + ;; Read return code + (goto-char (+ (point-min) size)) + (condition-case nil + (unless (looking-at tramp-fish-ok-prompt-regexp) (error nil)) + (error (return nil))) + (delete-region (+ (point-min) size) (point-max)) + size)))) + + +;; Connection functions + +(defun tramp-fish-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." + (let ((process-connection-type tramp-process-connection-type) + (p (get-buffer-process (tramp-get-buffer vec)))) + + ;; New connection must be opened. + (unless (and p (processp p) (memq (process-status p) '(run open))) + + ;; Set variables for computing the prompt for reading password. + (setq tramp-current-method (tramp-file-name-method vec) + tramp-current-user (tramp-file-name-user vec) + tramp-current-host (tramp-file-name-host vec)) + + ;; Start new process. + (when (and p (processp p)) + (delete-process p)) + (setenv "TERM" tramp-terminal-type) + (setenv "PS1" "$ ") + (tramp-message + vec 3 "Opening connection for %s@%s using %s..." + tramp-current-user tramp-current-host tramp-current-method) + + (let* ((process-connection-type tramp-process-connection-type) + (inhibit-eol-conversion nil) + (coding-system-for-read 'binary) + (coding-system-for-write 'binary) + ;; This must be done in order to avoid our file name handler. + (p (let ((default-directory + (tramp-compat-temporary-file-directory))) + (start-process + (or (tramp-get-connection-property vec "process-name" nil) + (tramp-buffer-name vec)) + (tramp-get-connection-buffer vec) + "ssh" "-l" + (tramp-file-name-user vec) + (tramp-file-name-host vec))))) + (tramp-message vec 6 "%s" (mapconcat 'identity (process-command p) " ")) + + ;; Check whether process is alive. + (set-process-sentinel p 'tramp-flush-connection-property) + (tramp-set-process-query-on-exit-flag p nil) + + (tramp-process-actions p vec tramp-actions-before-shell 60) + (tramp-fish-send-command vec tramp-fish-start-fish-server-command) + (tramp-message + vec 3 + "Found remote shell prompt on `%s'" (tramp-file-name-host vec)))))) + +(defun tramp-fish-send-command (vec command) + "Send the COMMAND to connection VEC." + (tramp-fish-maybe-open-connection vec) + (tramp-message vec 6 "%s" command) + (tramp-send-string vec command) + (tramp-wait-for-regexp + (tramp-get-connection-process vec) nil + (concat tramp-fish-ok-prompt-regexp "\\|" tramp-fish-error-prompt-regexp))) + +(defun tramp-fish-send-command-and-check (vec command) + "Send the COMMAND to connection VEC. +Returns nil if there has been an error message." + + ;; Send command. + (tramp-fish-send-command vec command) + + ;; Read return code. + (with-current-buffer (tramp-get-buffer vec) + (goto-char (point-min)) + (looking-at tramp-fish-ok-prompt-regexp))) + +(provide 'tramp-fish) +; +;;;; TODO: +; +;; * Evaluate the MIME information with #LIST or #STAT. +; + +;; arch-tag: a66df7df-5f29-42a7-a921-643ceb29db49 +;;;; tramp-fish.el ends here diff --git a/lisp/net/tramp-ftp.el b/lisp/net/tramp-ftp.el index f6985736b82..20a2b000f7e 100644 --- a/lisp/net/tramp-ftp.el +++ b/lisp/net/tramp-ftp.el @@ -1,4 +1,4 @@ -;;; tramp-ftp.el --- Tramp convenience functions for Ange-FTP -*- coding: iso-8859-1; -*- +;;; tramp-ftp.el --- Tramp convenience functions for Ange-FTP ;; Copyright (C) 2002, 2003, 2004, 2005, 2006, ;; 2007, 2008 Free Software Foundation, Inc. @@ -19,9 +19,8 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. ;;; Commentary: @@ -31,6 +30,7 @@ ;;; Code: (require 'tramp) +(autoload 'tramp-set-connection-property "tramp-cache") (eval-when-compile (require 'custom)) @@ -110,10 +110,13 @@ present for backward compatibility." (list "" "\\`\\(anonymous\\|ftp\\)\\'" tramp-ftp-method)) ;; Add completion function for FTP method. -(unless (memq system-type '(windows-nt)) - (tramp-set-completion-function - tramp-ftp-method - '((tramp-parse-netrc "~/.netrc")))) +(tramp-set-completion-function + tramp-ftp-method + '((tramp-parse-netrc "~/.netrc"))) + +;; If there is URL syntax, `substitute-in-file-name' needs special +;; handling. +(put 'substitute-in-file-name 'ange-ftp 'tramp-handle-substitute-in-file-name) (defun tramp-ftp-file-name-handler (operation &rest args) "Invoke the Ange-FTP handler for OPERATION. @@ -135,30 +138,55 @@ pass to the OPERATION." (ange-ftp-ftp-name-arg "") (ange-ftp-ftp-name-res nil)) (cond - ;; If argument is a symlink, `file-directory-p' and `file-exists-p' - ;; call the traversed file recursively. So we cannot disable the - ;; file-name-handler this case. + ;; If argument is a symlink, `file-directory-p' and + ;; `file-exists-p' call the traversed file recursively. So we + ;; cannot disable the file-name-handler this case. We set the + ;; connection property "started" in order to put the remote + ;; location into the cache, which is helpful for further + ;; completion. We don't use `with-parsed-tramp-file-name', + ;; because this returns another user but the one declared in + ;; "~/.netrc". ((memq operation '(file-directory-p file-exists-p)) - (apply 'ange-ftp-hook-function operation args)) - ;; Normally, the handlers must be discarded - (t (let* ((inhibit-file-name-handlers - (list 'tramp-file-name-handler - 'tramp-completion-file-name-handler - (and (eq inhibit-file-name-operation operation) - inhibit-file-name-handlers))) - (inhibit-file-name-operation operation)) - (apply 'ange-ftp-hook-function operation args))))))) + (if (apply 'ange-ftp-hook-function operation args) + (let ((v (tramp-dissect-file-name (car args) t))) + (aset v 0 tramp-ftp-method) + (tramp-set-connection-property v "started" t)) + nil)) + + ;; If the second argument of `copy-file' or `rename-file' is a + ;; remote file name but via FTP, ange-ftp doesn't check this. + ;; We must copy it locally first, because there is no place in + ;; ange-ftp for correct handling. + ((and (memq operation '(copy-file rename-file)) + (file-remote-p (cadr args)) + (not (tramp-ftp-file-name-p (cadr args)))) + (let* ((filename (car args)) + (newname (cadr args)) + (tmpfile (tramp-compat-make-temp-file filename)) + (args (cddr args))) + ;; We must set `ok-if-already-exists' to t in the first + ;; step, because the temp file has been created already. + (if (eq operation 'copy-file) + (apply operation filename tmpfile t (cdr args)) + (apply operation filename tmpfile t)) + (unwind-protect + (rename-file tmpfile newname (car args)) + ;; Cleanup. + (ignore-errors (delete-file tmpfile))))) + + ;; Normally, the handlers must be discarded. + (t (let* ((inhibit-file-name-handlers + (list 'tramp-file-name-handler + 'tramp-completion-file-name-handler + (and (eq inhibit-file-name-operation operation) + inhibit-file-name-handlers))) + (inhibit-file-name-operation operation)) + (apply 'ange-ftp-hook-function operation args))))))) (defun tramp-ftp-file-name-p (filename) "Check if it's a filename that should be forwarded to Ange-FTP." (let ((v (tramp-dissect-file-name filename))) - (string= - (tramp-find-method - (tramp-file-name-multi-method v) - (tramp-file-name-method v) - (tramp-file-name-user v) - (tramp-file-name-host v)) - tramp-ftp-method))) + (string= (tramp-file-name-method v) tramp-ftp-method))) (add-to-list 'tramp-foreign-file-name-handler-alist (cons 'tramp-ftp-file-name-p 'tramp-ftp-file-name-handler)) @@ -172,8 +200,6 @@ pass to the OPERATION." ;; pretended in `tramp-file-name-handler' otherwise. ;; Furthermore, there are no backup files on FTP hosts. ;; Worth further investigations. -;; * Map /multi:ssh:out@gate:ftp:kai@real.host:/path/to.file -;; on Ange-FTP gateways. ;;; arch-tag: 759fb338-5c63-4b99-bd36-b4d59db91cff ;;; tramp-ftp.el ends here diff --git a/lisp/net/tramp-gw.el b/lisp/net/tramp-gw.el new file mode 100644 index 00000000000..fa2e9ba68b0 --- /dev/null +++ b/lisp/net/tramp-gw.el @@ -0,0 +1,322 @@ +;;; tramp-gw.el --- Tramp utility functions for HTTP tunnels and SOCKS gateways + +;; Copyright (C) 2007 Free Software Foundation, Inc. + +;; Author: Michael Albinus <michael.albinus@gmx.de> +;; Keywords: comm, processes + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Access functions for HTTP tunnels and SOCKS gateways from Tramp. +;; SOCKS functionality is implemented by socks.el from the w3 package. +;; HTTP tunnels are partly implemented in socks.el and url-http.el; +;; both implementations are not complete. Therefore, it is +;; implemented in this package. + +;;; Code: + +(require 'tramp) + +;; Pacify byte-compiler +(eval-when-compile + (require 'cl) + (require 'custom)) + +;; Autoload the socks library. It is used only when we access a SOCKS server. +(autoload 'socks-open-network-stream "socks") +(defvar socks-username (user-login-name)) +(defvar socks-server (list "Default server" "socks" 1080 5)) + +;; Avoid byte-compiler warnings if the byte-compiler supports this. +;; Currently, XEmacs supports this. +(eval-when-compile + (when (featurep 'xemacs) + (byte-compiler-options (warnings (- unused-vars))))) + +;; Define HTTP tunnel method ... +(defvar tramp-gw-tunnel-method "tunnel" + "*Method to connect HTTP gateways.") + +;; ... and port. +(defvar tramp-gw-default-tunnel-port 8080 + "*Default port for HTTP gateways.") + +;; Define SOCKS method ... +(defvar tramp-gw-socks-method "socks" + "*Method to connect SOCKS servers.") + +;; ... and port. +(defvar tramp-gw-default-socks-port 1080 + "*Default port for SOCKS servers.") + +;; Add a default for `tramp-default-user-alist'. Default is the local user. +(add-to-list 'tramp-default-user-alist + `(,tramp-gw-tunnel-method nil ,(user-login-name))) +(add-to-list 'tramp-default-user-alist + `(,tramp-gw-socks-method nil ,(user-login-name))) + +;; Internal file name functions and variables. + +(defvar tramp-gw-vector nil + "Keeps the remote host identification. Needed for Tramp messages.") + +(defvar tramp-gw-gw-vector nil + "Current gateway identification vector.") + +(defvar tramp-gw-gw-proc nil + "Current gateway process.") + +;; This variable keeps the listening process, in order to reuse it for +;; new processes. +(defvar tramp-gw-aux-proc nil + "Process listening on local port, as mediation between SSH and the gateway.") + +(defun tramp-gw-gw-proc-sentinel (proc event) + "Delete auxiliary process when we are deleted." + (unless (memq (process-status proc) '(run open)) + (tramp-message + tramp-gw-vector 4 "Deleting auxiliary process `%s'" tramp-gw-gw-proc) + (let* (tramp-verbose + (p (tramp-get-connection-property proc "process" nil))) + (when (processp p) (delete-process p))))) + +(defun tramp-gw-aux-proc-sentinel (proc event) + "Activate the different filters for involved gateway and auxiliary processes." + (when (memq (process-status proc) '(run open)) + ;; A new process has been spawned from `tramp-gw-aux-proc'. + (tramp-message + tramp-gw-vector 4 + "Opening auxiliary process `%s', speaking with process `%s'" + proc tramp-gw-gw-proc) + (tramp-set-process-query-on-exit-flag proc nil) + ;; We don't want debug messages, because the corresponding debug + ;; buffer might be undecided. + (let (tramp-verbose) + (tramp-set-connection-property tramp-gw-gw-proc "process" proc) + (tramp-set-connection-property proc "process" tramp-gw-gw-proc)) + ;; Set the process-filter functions for both processes. + (set-process-filter proc 'tramp-gw-process-filter) + (set-process-filter tramp-gw-gw-proc 'tramp-gw-process-filter) + ;; There might be already some output from the gateway process. + (with-current-buffer (process-buffer tramp-gw-gw-proc) + (unless (= (point-min) (point-max)) + (let ((s (buffer-string))) + (delete-region (point) (point-max)) + (tramp-gw-process-filter tramp-gw-gw-proc s)))))) + +(defun tramp-gw-process-filter (proc string) + (let (tramp-verbose) + (process-send-string + (tramp-get-connection-property proc "process" nil) string))) + +(defun tramp-gw-open-connection (vec gw-vec target-vec) + "Open a remote connection to VEC (see `tramp-file-name' structure). +Take GW-VEC as SOCKS or HTTP gateway, i.e. its method must be a +gateway method. TARGET-VEC identifies where to connect to via +the gateway, it can be different from VEC when there are more +hops to be applied. + +It returns a string like \"localhost#port\", which must be used +instead of the host name declared in TARGET-VEC." + + ;; Remember vectors for property retrieval. + (setq tramp-gw-vector vec + tramp-gw-gw-vector gw-vec) + + ;; Start listening auxiliary process. + (unless (and (processp tramp-gw-aux-proc) + (memq (process-status tramp-gw-aux-proc) '(listen))) + (let ((aux-vec + (vector "aux" (tramp-file-name-user gw-vec) + (tramp-file-name-host gw-vec) nil))) + (setq tramp-gw-aux-proc + (make-network-process + :name (tramp-buffer-name aux-vec) :buffer nil :host 'local + :server t :noquery t :service t :coding 'binary)) + (set-process-sentinel tramp-gw-aux-proc 'tramp-gw-aux-proc-sentinel) + (tramp-set-process-query-on-exit-flag tramp-gw-aux-proc nil) + (tramp-message + vec 4 "Opening auxiliary process `%s', listening on port %d" + tramp-gw-aux-proc (process-contact tramp-gw-aux-proc :service)))) + + (let* ((gw-method + (intern + (tramp-find-method + (tramp-file-name-method gw-vec) + (tramp-file-name-user gw-vec) + (tramp-file-name-host gw-vec)))) + (socks-username + (tramp-find-user + (tramp-file-name-method gw-vec) + (tramp-file-name-user gw-vec) + (tramp-file-name-host gw-vec))) + ;; Declare the SOCKS server to be used. + (socks-server + (list "Tramp tempory socks server list" + ;; Host name. + (tramp-file-name-real-host gw-vec) + ;; Port number. + (or (tramp-file-name-port gw-vec) + (case gw-method + (tunnel tramp-gw-default-tunnel-port) + (socks tramp-gw-default-socks-port))) + ;; Type. We support only http and socks5, NO socks4. + ;; 'http could be used when HTTP tunnel works in socks.el. + 5)) + ;; The function to be called. + (socks-function + (case gw-method + (tunnel 'tramp-gw-open-network-stream) + (socks 'socks-open-network-stream))) + socks-noproxy) + + ;; Open SOCKS process. + (setq tramp-gw-gw-proc + (funcall + socks-function + (tramp-buffer-name gw-vec) + (tramp-get-buffer gw-vec) + (tramp-file-name-real-host target-vec) + (tramp-file-name-port target-vec))) + (set-process-sentinel tramp-gw-gw-proc 'tramp-gw-gw-proc-sentinel) + (tramp-set-process-query-on-exit-flag tramp-gw-gw-proc nil) + (tramp-message + vec 4 "Opened %s process `%s'" + (case gw-method ('tunnel "HTTP tunnel") ('socks "SOCKS")) + tramp-gw-gw-proc) + + ;; Return the new host for gateway access. + (format "localhost#%d" (process-contact tramp-gw-aux-proc :service)))) + +(defun tramp-gw-open-network-stream (name buffer host service) + "Open stream to proxy server HOST:SERVICE. +Resulting process has name NAME and buffer BUFFER. If +authentication is requested from proxy server, provide it." + (let ((command (format (concat + "CONNECT %s:%d HTTP/1.1\r\n" + "Host: %s:%d\r\n" + "Connection: keep-alive\r\n" + "User-Agent: Tramp/%s\r\n") + host service host service tramp-version)) + (authentication "") + (first t) + found proc) + + (while (not found) + ;; Clean up. + (when (processp proc) (delete-process proc)) + (with-current-buffer buffer (erase-buffer)) + ;; Open network stream. + (setq proc (open-network-stream + name buffer (nth 1 socks-server) (nth 2 socks-server))) + (set-process-coding-system proc 'binary 'binary) + (tramp-set-process-query-on-exit-flag proc nil) + ;; Send CONNECT command. + (process-send-string proc (format "%s%s\r\n" command authentication)) + (tramp-message + tramp-gw-vector 6 "\n%s" + (format + "%s%s\r\n" command + (replace-regexp-in-string ;; no password in trace! + "Basic [^\r\n]+" "Basic xxxxx" authentication t))) + (with-current-buffer buffer + ;; Trap errors to be traced in the right trace buffer. Often, + ;; proxies have a timeout of 60". We wait 65" in order to + ;; receive an answer this case. + (condition-case nil + (let (tramp-verbose) + (tramp-wait-for-regexp proc 65 "\r?\n\r?\n")) + (error nil)) + ;; Check return code. + (goto-char (point-min)) + (narrow-to-region + (point-min) + (or (search-forward-regexp "\r?\n\r?\n" nil t) (point-max))) + (tramp-message tramp-gw-vector 6 "\n%s" (buffer-string)) + (goto-char (point-min)) + (search-forward-regexp "^HTTP/[1-9]\\.[0-9]" nil t) + (case (condition-case nil (read (current-buffer)) (error)) + ;; Connected. + (200 (setq found t)) + ;; We need basic authentication. + (401 (setq authentication (tramp-gw-basic-authentication nil first))) + ;; Target host not found. + (404 (tramp-error-with-buffer + (current-buffer) tramp-gw-vector 'file-error + "Host %s not found." host)) + ;; We need basic proxy authentication. + (407 (setq authentication (tramp-gw-basic-authentication t first))) + ;; Connection failed. + (503 (tramp-error-with-buffer + (current-buffer) tramp-gw-vector 'file-error + "Connection to %s:%d failed." host service)) + ;; That doesn't work at all. + (t (tramp-error-with-buffer + (current-buffer) tramp-gw-vector 'file-error + "Access to HTTP server %s:%d failed." + (nth 1 socks-server) (nth 2 socks-server)))) + ;; Remove HTTP headers. + (delete-region (point-min) (point-max)) + (widen) + (setq first nil))) + ;; Return the process. + proc)) + +(defun tramp-gw-basic-authentication (proxy pw-cache) + "Return authentication header for CONNECT, based on server request. +PROXY is an indication whether we need a Proxy-Authorization header +or an Authorization header. If PW-CACHE is non-nil, check for +password in password cache. This is done for the first try only." + + ;; `tramp-current-*' must be set for `tramp-read-passwd'. + (let ((tramp-current-method (tramp-file-name-method tramp-gw-gw-vector)) + (tramp-current-user (tramp-file-name-user tramp-gw-gw-vector)) + (tramp-current-host (tramp-file-name-host tramp-gw-gw-vector))) + (unless pw-cache (tramp-clear-passwd tramp-gw-gw-vector)) + ;; We are already in the right buffer. + (tramp-message + tramp-gw-vector 5 "%s required" + (if proxy "Proxy authentication" "Authentication")) + ;; Search for request header. We accept only basic authentication. + (goto-char (point-min)) + (search-forward-regexp + "^\\(Proxy\\|WWW\\)-Authenticate:\\s-*Basic\\s-+realm=") + ;; Return authentication string. + (format + "%s: Basic %s\r\n" + (if proxy "Proxy-Authorization" "Authorization") + (base64-encode-string + (format + "%s:%s" + socks-username + (tramp-read-passwd + nil + (format + "Password for %s@[%s]: " socks-username (read (current-buffer))))))))) + + +(provide 'tramp-gw) + +;;; TODO: + +;; * Provide descriptive Commentary. +;; * Enable it for several gateway processes in parallel. + +;; arch-tag: 277e3a81-fdee-40cf-9e6b-59626292a5e0 +;;; tramp-gw.el ends here diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el index 9ea2c9759df..e9fc92cbc0a 100644 --- a/lisp/net/tramp-smb.el +++ b/lisp/net/tramp-smb.el @@ -1,6 +1,7 @@ -;;; tramp-smb.el --- Tramp access functions for SMB servers -*- coding: iso-8859-1; -*- +;;; tramp-smb.el --- Tramp access functions for SMB servers -;; Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +;; Copyright (C) 2002, 2003, 2004, 2005, 2006, +;; 2007, 2008 Free Software Foundation, Inc. ;; Author: Michael Albinus <michael.albinus@gmx.de> ;; Keywords: comm, processes @@ -18,9 +19,8 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. ;;; Commentary: @@ -29,17 +29,8 @@ ;;; Code: (require 'tramp) - -;; Pacify byte-compiler -(eval-when-compile (require 'custom)) - -;; Avoid byte-compiler warnings if the byte-compiler supports this. -;; Currently, XEmacs supports this. -(eval-when-compile - (when (fboundp 'byte-compiler-options) - (let (unused-vars) ; Pacify Emacs byte-compiler - (defalias 'warnings 'identity) ; Pacify Emacs byte-compiler - (byte-compiler-options (warnings (- unused-vars)))))) +(require 'tramp-cache) +(require 'tramp-compat) ;; Define SMB method ... (defcustom tramp-smb-method "smb" @@ -53,7 +44,12 @@ ;; Add a default for `tramp-default-method-alist'. Rule: If there is ;; a domain in USER, it must be the SMB method. (add-to-list 'tramp-default-method-alist - (list "" "%" tramp-smb-method)) + `(nil "%" ,tramp-smb-method)) + +;; Add a default for `tramp-default-user-alist'. Rule: For the SMB method, +;; the anonymous user is chosen. +(add-to-list 'tramp-default-user-alist + `(,tramp-smb-method nil "")) ;; Add completion function for SMB method. (tramp-set-completion-function @@ -69,11 +65,14 @@ "Regexp used as prompt in smbclient.") (defconst tramp-smb-errors + ;; `regexp-opt' not possible because of first string. (mapconcat 'identity - '(; Connection error + '(;; Connection error / timeout "Connection to \\S-+ failed" - ; Samba + "Read from server failed, maybe it closed the connection" + "Call timed out: server did not respond" + ;; Samba "ERRDOS" "ERRSRV" "ERRbadfile" @@ -82,34 +81,48 @@ "ERRnoaccess" "ERRnomem" "ERRnosuchshare" - ; Windows NT 4.0, Windows 5.0 (Windows 2000), Windows 5.1 (Windows XP) + ;; Windows 4.0 (Windows NT), Windows 5.0 (Windows 2000), + ;; Windows 5.1 (Windows XP), Windows 5.2 (Windows Server 2003) "NT_STATUS_ACCESS_DENIED" "NT_STATUS_ACCOUNT_LOCKED_OUT" "NT_STATUS_BAD_NETWORK_NAME" "NT_STATUS_CANNOT_DELETE" + "NT_STATUS_DIRECTORY_NOT_EMPTY" + "NT_STATUS_DUPLICATE_NAME" + "NT_STATUS_FILE_IS_A_DIRECTORY" "NT_STATUS_LOGON_FAILURE" "NT_STATUS_NETWORK_ACCESS_DENIED" "NT_STATUS_NO_SUCH_FILE" + "NT_STATUS_OBJECT_NAME_COLLISION" "NT_STATUS_OBJECT_NAME_INVALID" "NT_STATUS_OBJECT_NAME_NOT_FOUND" "NT_STATUS_SHARING_VIOLATION" + "NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE" "NT_STATUS_WRONG_PASSWORD") "\\|") "Regexp for possible error strings of SMB servers. Used instead of analyzing error codes of commands.") -(defvar tramp-smb-share nil - "Holds the share name for the current buffer. -This variable is local to each buffer.") -(make-variable-buffer-local 'tramp-smb-share) +(defconst tramp-smb-actions-with-share + '((tramp-smb-prompt tramp-action-succeed) + (tramp-password-prompt-regexp tramp-action-password) + (tramp-wrong-passwd-regexp tramp-action-permission-denied) + (tramp-smb-errors tramp-action-permission-denied) + (tramp-process-alive-regexp tramp-action-process-alive)) + "List of pattern/action pairs. +This list is used for login to SMB servers. + +See `tramp-actions-before-shell' for more info.") -(defvar tramp-smb-share-cache nil - "Caches the share names accessible to host related to the current buffer. -This variable is local to each buffer.") -(make-variable-buffer-local 'tramp-smb-share-cache) +(defconst tramp-smb-actions-without-share + '((tramp-password-prompt-regexp tramp-action-password) + (tramp-wrong-passwd-regexp tramp-action-permission-denied) + (tramp-smb-errors tramp-action-permission-denied) + (tramp-process-alive-regexp tramp-action-out-of-band)) + "List of pattern/action pairs. +This list is used for login to SMB servers. -(defvar tramp-smb-inodes nil - "Keeps virtual inodes numbers for SMB files.") +See `tramp-actions-before-shell' for more info.") ;; New handlers should be added here. (defconst tramp-smb-file-name-handler-alist @@ -124,8 +137,8 @@ This variable is local to each buffer.") (directory-file-name . tramp-handle-directory-file-name) (directory-files . tramp-smb-handle-directory-files) (directory-files-and-attributes . tramp-smb-handle-directory-files-and-attributes) - (dired-call-process . tramp-smb-not-handled) - (dired-compress-file . tramp-smb-not-handled) + (dired-call-process . ignore) + (dired-compress-file . ignore) ;; `dired-uncache' performed by default handler ;; `expand-file-name' not necessary because we cannot expand "~/" (file-accessible-directory-p . tramp-smb-handle-file-directory-p) @@ -143,10 +156,10 @@ This variable is local to each buffer.") (file-name-nondirectory . tramp-handle-file-name-nondirectory) ;; `file-name-sans-versions' performed by default handler (file-newer-than-file-p . tramp-smb-handle-file-newer-than-file-p) - (file-ownership-preserved-p . tramp-smb-not-handled) + (file-ownership-preserved-p . ignore) (file-readable-p . tramp-smb-handle-file-exists-p) (file-regular-p . tramp-handle-file-regular-p) - (file-symlink-p . tramp-smb-not-handled) + (file-symlink-p . tramp-handle-file-symlink-p) ;; `file-truename' performed by default handler (file-writable-p . tramp-smb-handle-file-writable-p) (find-backup-file-name . tramp-handle-find-backup-file-name) @@ -157,15 +170,15 @@ This variable is local to each buffer.") (load . tramp-handle-load) (make-directory . tramp-smb-handle-make-directory) (make-directory-internal . tramp-smb-handle-make-directory-internal) - (make-symbolic-link . tramp-smb-not-handled) + (make-symbolic-link . ignore) (rename-file . tramp-smb-handle-rename-file) - (set-file-modes . tramp-smb-not-handled) - (set-visited-file-modtime . tramp-smb-not-handled) - (shell-command . tramp-smb-not-handled) + (set-file-modes . ignore) + (set-visited-file-modtime . ignore) + (shell-command . ignore) (substitute-in-file-name . tramp-smb-handle-substitute-in-file-name) (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory) - (vc-registered . tramp-smb-not-handled) - (verify-visited-file-modtime . tramp-smb-not-handled) + (vc-registered . ignore) + (verify-visited-file-modtime . ignore) (write-region . tramp-smb-handle-write-region) ) "Alist of handler functions for Tramp SMB method. @@ -174,13 +187,7 @@ Operations not mentioned here will be handled by the default Emacs primitives.") (defun tramp-smb-file-name-p (filename) "Check if it's a filename for SMB servers." (let ((v (tramp-dissect-file-name filename))) - (string= - (tramp-find-method - (tramp-file-name-multi-method v) - (tramp-file-name-method v) - (tramp-file-name-user v) - (tramp-file-name-host v)) - tramp-smb-method))) + (string= (tramp-file-name-method v) tramp-smb-method))) (defun tramp-smb-file-name-handler (operation &rest args) "Invoke the SMB related OPERATION. @@ -188,9 +195,7 @@ First arg specifies the OPERATION, second arg is a list of arguments to pass to the OPERATION." (let ((fn (assoc operation tramp-smb-file-name-handler-alist))) (if fn - (if (eq (cdr fn) 'tramp-smb-not-handled) - (apply (cdr fn) operation args) - (save-match-data (apply (cdr fn) args))) + (save-match-data (apply (cdr fn) args)) (tramp-run-real-handler operation args)))) (add-to-list 'tramp-foreign-file-name-handler-alist @@ -199,214 +204,198 @@ pass to the OPERATION." ;; File name primitives -(defun tramp-smb-not-handled (operation &rest args) - "Default handler for all functions which are disrecarded." - (tramp-message 10 "Won't be handled: %s %s" operation args) - nil) - (defun tramp-smb-handle-copy-file - (filename newname &optional ok-if-already-exists keep-date) - "Like `copy-file' for tramp files. -KEEP-DATE is not handled in case NEWNAME resides on an SMB server." + (filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) + "Like `copy-file' for Tramp files. +KEEP-DATE is not handled in case NEWNAME resides on an SMB server. +PRESERVE-UID-GID is completely ignored." (setq filename (expand-file-name filename) newname (expand-file-name newname)) (let ((tmpfile (file-local-copy filename))) (if tmpfile - ;; remote filename + ;; Remote filename. (rename-file tmpfile newname ok-if-already-exists) - ;; remote newname + ;; Remote newname. (when (file-directory-p newname) (setq newname (expand-file-name (file-name-nondirectory filename) newname))) - (when (and (not ok-if-already-exists) - (file-exists-p newname)) - (error "copy-file: file %s already exists" newname)) (with-parsed-tramp-file-name newname nil - (save-excursion - (let ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname t))) - (unless share - (error "Target `%s' must contain a share name" filename)) - (tramp-smb-maybe-open-connection user host share) - (tramp-message-for-buffer - nil tramp-smb-method user host - 5 "Copying file %s to file %s..." filename newname) - (if (tramp-smb-send-command - user host (format "put %s \"%s\"" filename file)) - (tramp-message-for-buffer - nil tramp-smb-method user host - 5 "Copying file %s to file %s...done" filename newname) - (error "Cannot copy `%s'" filename)))))))) + (when (and (not ok-if-already-exists) + (file-exists-p newname)) + (tramp-error v 'file-already-exists newname)) + + ;; We must also flush the cache of the directory, because + ;; file-attributes reads the values from there. + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + (let ((share (tramp-smb-get-share localname)) + (file (tramp-smb-get-localname localname t))) + (unless share + (tramp-error + v 'file-error "Target `%s' must contain a share name" newname)) + (tramp-message v 0 "Copying file %s to file %s..." filename newname) + (if (tramp-smb-send-command + v (format "put %s \"%s\"" filename file)) + (tramp-message + v 0 "Copying file %s to file %s...done" filename newname) + (tramp-error v 'file-error "Cannot copy `%s'" filename))))))) (defun tramp-smb-handle-delete-directory (directory) - "Like `delete-directory' for tramp files." + "Like `delete-directory' for Tramp files." (setq directory (directory-file-name (expand-file-name directory))) (when (file-exists-p directory) (with-parsed-tramp-file-name directory nil - (save-excursion - (let ((share (tramp-smb-get-share localname)) - (dir (tramp-smb-get-localname (file-name-directory localname) t)) - (file (file-name-nondirectory localname))) - (tramp-smb-maybe-open-connection user host share) - (if (and - (tramp-smb-send-command user host (format "cd \"%s\"" dir)) - (tramp-smb-send-command user host (format "rmdir \"%s\"" file))) - ;; Go Home - (tramp-smb-send-command user host (format "cd \\")) - ;; Error - (tramp-smb-send-command user host (format "cd \\")) - (error "Cannot delete directory `%s'" directory))))))) + ;; We must also flush the cache of the directory, because + ;; file-attributes reads the values from there. + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-directory-property v localname) + (let ((dir (tramp-smb-get-localname (file-name-directory localname) t)) + (file (file-name-nondirectory localname))) + (unwind-protect + (unless (and + (tramp-smb-send-command v (format "cd \"%s\"" dir)) + (tramp-smb-send-command v (format "rmdir \"%s\"" file))) + ;; Error + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (search-forward-regexp tramp-smb-errors nil t) + (tramp-error + v 'file-error "%s `%s'" (match-string 0) directory))) + ;; Always go home + (tramp-smb-send-command v (format "cd \\"))))))) (defun tramp-smb-handle-delete-file (filename) - "Like `delete-file' for tramp files." + "Like `delete-file' for Tramp files." (setq filename (expand-file-name filename)) (when (file-exists-p filename) (with-parsed-tramp-file-name filename nil - (save-excursion - (let ((share (tramp-smb-get-share localname)) - (dir (tramp-smb-get-localname (file-name-directory localname) t)) - (file (file-name-nondirectory localname))) - (tramp-smb-maybe-open-connection user host share) - (if (and - (tramp-smb-send-command user host (format "cd \"%s\"" dir)) - (tramp-smb-send-command user host (format "rm \"%s\"" file))) - ;; Go Home - (tramp-smb-send-command user host (format "cd \\")) - ;; Error - (tramp-smb-send-command user host (format "cd \\")) - (error "Cannot delete file `%s'" filename))))))) + ;; We must also flush the cache of the directory, because + ;; file-attributes reads the values from there. + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + (let ((dir (tramp-smb-get-localname (file-name-directory localname) t)) + (file (file-name-nondirectory localname))) + (unwind-protect + (unless (and + (tramp-smb-send-command v (format "cd \"%s\"" dir)) + (tramp-smb-send-command v (format "rm \"%s\"" file))) + ;; Error + (with-current-buffer (tramp-get-connection-buffer v) + (goto-char (point-min)) + (search-forward-regexp tramp-smb-errors nil t) + (tramp-error + v 'file-error "%s `%s'" (match-string 0) filename))) + ;; Always go home + (tramp-smb-send-command v (format "cd \\"))))))) (defun tramp-smb-handle-directory-files (directory &optional full match nosort) - "Like `directory-files' for tramp files." - (setq directory (directory-file-name (expand-file-name directory))) - (with-parsed-tramp-file-name directory nil - (save-excursion - (let* ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname nil)) - (entries (tramp-smb-get-file-entries user host share file))) - ;; Just the file names are needed - (setq entries (mapcar 'car entries)) - ;; Discriminate with regexp - (when match - (setq entries - (delete nil - (mapcar (lambda (x) (when (string-match match x) x)) - entries)))) - ;; Make absolute localnames if necessary - (when full - (setq entries - (mapcar (lambda (x) - (concat (file-name-as-directory directory) x)) - entries))) - ;; Sort them if necessary - (unless nosort (setq entries (sort entries 'string-lessp))) - ;; That's it - entries)))) + "Like `directory-files' for Tramp files." + (let ((result (mapcar 'directory-file-name + (file-name-all-completions "" directory)))) + ;; Discriminate with regexp + (when match + (setq result + (delete nil + (mapcar (lambda (x) (when (string-match match x) x)) + result)))) + ;; Append directory + (when full + (setq result + (mapcar + (lambda (x) (expand-file-name x directory)) + result))) + ;; Sort them if necessary + (unless nosort (setq result (sort result 'string-lessp))) + ;; That's it + result)) (defun tramp-smb-handle-directory-files-and-attributes (directory &optional full match nosort id-format) - "Like `directory-files-and-attributes' for tramp files." + "Like `directory-files-and-attributes' for Tramp files." (mapcar (lambda (x) ;; We cannot call `file-attributes' for backward compatibility reasons. ;; Its optional parameter ID-FORMAT is introduced with Emacs 22. (cons x (tramp-smb-handle-file-attributes - (if full x (concat (file-name-as-directory directory) x)) id-format))) + (if full x (expand-file-name x directory)) id-format))) (directory-files directory full match nosort))) (defun tramp-smb-handle-file-attributes (filename &optional id-format) - "Like `file-attributes' for tramp files." + "Like `file-attributes' for Tramp files." + ;; Reading just the filename entry via "dir localname" is not + ;; possible, because when filename is a directory, some smbclient + ;; versions return the content of the directory, and other versions + ;; don't. Therefore, the whole content of the upper directory is + ;; retrieved, and the entry of the filename is extracted from. (with-parsed-tramp-file-name filename nil - (save-excursion - (let* ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname nil)) - (entries (tramp-smb-get-file-entries user host share file)) + (with-file-property v localname (format "file-attributes-%s" id-format) + (let* ((entries (tramp-smb-get-file-entries + (file-name-directory filename))) (entry (and entries - (assoc (file-name-nondirectory file) entries))) + (assoc (file-name-nondirectory filename) entries))) (uid (if (and id-format (equal id-format 'string)) "nobody" -1)) (gid (if (and id-format (equal id-format 'string)) "nogroup" -1)) - (inode (tramp-smb-get-inode share file)) - (device (tramp-get-device nil tramp-smb-method user host))) + (inode (tramp-get-inode v)) + (device (tramp-get-device v))) - ; check result + ;; Check result. (when entry (list (and (string-match "d" (nth 1 entry)) - t) ;0 file type - -1 ;1 link count - uid ;2 uid - gid ;3 gid - '(0 0) ;4 atime - (nth 3 entry) ;5 mtime - '(0 0) ;6 ctime - (nth 2 entry) ;7 size - (nth 1 entry) ;8 mode - nil ;9 gid weird - inode ;10 inode number - device)))))) ;11 file system number + t) ;0 file type + -1 ;1 link count + uid ;2 uid + gid ;3 gid + '(0 0) ;4 atime + (nth 3 entry) ;5 mtime + '(0 0) ;6 ctime + (nth 2 entry) ;7 size + (nth 1 entry) ;8 mode + nil ;9 gid weird + inode ;10 inode number + device)))))) ;11 file system number (defun tramp-smb-handle-file-directory-p (filename) - "Like `file-directory-p' for tramp files." - (with-parsed-tramp-file-name filename nil - (save-excursion - (let* ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname nil)) - (entries (tramp-smb-get-file-entries user host share file)) - (entry (and entries - (assoc (file-name-nondirectory file) entries)))) - (and entry - (string-match "d" (nth 1 entry)) - t))))) + "Like `file-directory-p' for Tramp files." + (and (file-exists-p filename) + (eq ?d (aref (nth 8 (file-attributes filename)) 0)))) (defun tramp-smb-handle-file-exists-p (filename) - "Like `file-exists-p' for tramp files." - (with-parsed-tramp-file-name filename nil - (save-excursion - (let* ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname nil)) - (entries (tramp-smb-get-file-entries user host share file))) - (and entries - (member (file-name-nondirectory file) (mapcar 'car entries)) - t))))) + "Like `file-exists-p' for Tramp files." + (not (null (file-attributes filename)))) (defun tramp-smb-handle-file-local-copy (filename) - "Like `file-local-copy' for tramp files." + "Like `file-local-copy' for Tramp files." (with-parsed-tramp-file-name filename nil - (save-excursion - (let ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname t)) - (tmpfil (tramp-make-temp-file filename))) - (unless (file-exists-p filename) - (error "Cannot make local copy of non-existing file `%s'" filename)) - (tramp-message-for-buffer - nil tramp-smb-method user host - 5 "Fetching %s to tmp file %s..." filename tmpfil) - (tramp-smb-maybe-open-connection user host share) - (if (tramp-smb-send-command - user host (format "get \"%s\" %s" file tmpfil)) - (tramp-message-for-buffer - nil tramp-smb-method user host - 5 "Fetching %s to tmp file %s...done" filename tmpfil) - (error "Cannot make local copy of file `%s'" filename)) - tmpfil)))) + (let ((file (tramp-smb-get-localname localname t)) + (tmpfile (tramp-compat-make-temp-file filename))) + (unless (file-exists-p filename) + (tramp-error + v 'file-error + "Cannot make local copy of non-existing file `%s'" filename)) + (tramp-message v 4 "Fetching %s to tmp file %s..." filename tmpfile) + (if (tramp-smb-send-command v (format "get \"%s\" %s" file tmpfile)) + (tramp-message + v 4 "Fetching %s to tmp file %s...done" filename tmpfile) + (tramp-error + v 'file-error + "Cannot make local copy of file `%s'" filename)) + tmpfile))) ;; This function should return "foo/" for directories and "bar" for ;; files. (defun tramp-smb-handle-file-name-all-completions (filename directory) - "Like `file-name-all-completions' for tramp files." - (with-parsed-tramp-file-name directory nil - (save-match-data - (save-excursion - (let* ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname nil)) - (entries (tramp-smb-get-file-entries user host share file))) - - (all-completions - filename + "Like `file-name-all-completions' for Tramp files." + (all-completions + filename + (with-parsed-tramp-file-name directory nil + (with-file-property v localname "file-name-all-completions" + (save-match-data + (let ((entries (tramp-smb-get-file-entries directory))) (mapcar (lambda (x) (list @@ -416,89 +405,110 @@ KEEP-DATE is not handled in case NEWNAME resides on an SMB server." entries))))))) (defun tramp-smb-handle-file-newer-than-file-p (file1 file2) - "Like `file-newer-than-file-p' for tramp files." + "Like `file-newer-than-file-p' for Tramp files." (cond ((not (file-exists-p file1)) nil) ((not (file-exists-p file2)) t) - (t (tramp-smb-time-less-p (file-attributes file2) - (file-attributes file1))))) + (t (tramp-time-less-p (nth 5 (file-attributes file2)) + (nth 5 (file-attributes file1)))))) (defun tramp-smb-handle-file-writable-p (filename) - "Like `file-writable-p' for tramp files." - (if (not (file-exists-p filename)) - (let ((dir (file-name-directory filename))) - (and (file-exists-p dir) - (file-writable-p dir))) - (with-parsed-tramp-file-name filename nil - (save-excursion - (let* ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname nil)) - (entries (tramp-smb-get-file-entries user host share file)) - (entry (and entries - (assoc (file-name-nondirectory file) entries)))) - (and share entry - (string-match "w" (nth 1 entry)) - t)))))) + "Like `file-writable-p' for Tramp files." + (if (file-exists-p filename) + (string-match "w" (or (nth 8 (file-attributes filename)) "")) + (let ((dir (file-name-directory filename))) + (and (file-exists-p dir) + (file-writable-p dir))))) (defun tramp-smb-handle-insert-directory (filename switches &optional wildcard full-directory-p) - "Like `insert-directory' for tramp files. -WILDCARD and FULL-DIRECTORY-P are not handled." + "Like `insert-directory' for Tramp files." (setq filename (expand-file-name filename)) - (when (file-directory-p filename) - ;; This check is a little bit strange, but in `dired-add-entry' - ;; this function is called with a non-directory ... + (when full-directory-p + ;; Called from `dired-add-entry'. (setq filename (file-name-as-directory filename))) (with-parsed-tramp-file-name filename nil + (tramp-flush-file-property v (file-name-directory localname)) (save-match-data - (let* ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname nil)) - (entries (tramp-smb-get-file-entries user host share file))) - - ;; Delete dummy "" entry, useless entries + (let ((base (file-name-nondirectory filename)) + ;; We should not destroy the cache entry. + (entries (copy-sequence + (tramp-smb-get-file-entries + (file-name-directory filename))))) + + (when wildcard + (string-match "\\." base) + (setq base (replace-match "\\\\." nil nil base)) + (string-match "\\*" base) + (setq base (replace-match ".*" nil nil base)) + (string-match "\\?" base) + (setq base (replace-match ".?" nil nil base))) + + ;; Filter entries. (setq entries - (if (file-directory-p filename) - (delq (assoc "" entries) entries) - ;; We just need the only and only entry FILENAME. - (list (assoc (file-name-nondirectory filename) entries)))) - - ;; Sort entries + (delq + nil + (if (or wildcard (zerop (length base))) + ;; Check for matching entries. + (mapcar + (lambda (x) + (when (string-match + (format "^%s" base) (nth 0 x)) + x)) + entries) + ;; We just need the only and only entry FILENAME. + (list (assoc base entries))))) + + ;; Sort entries. (setq entries (sort entries (lambda (x y) (if (string-match "t" switches) - ; sort by date - (tramp-smb-time-less-p (nth 3 y) (nth 3 x)) - ; sort by name + ;; Sort by date. + (tramp-time-less-p (nth 3 y) (nth 3 x)) + ;; Sort by name. (string-lessp (nth 0 x) (nth 0 y)))))) - ;; Print entries + ;; Handle "-F" switch. + (when (string-match "F" switches) + (mapc + (lambda (x) + (when (not (zerop (length (car x)))) + (cond + ((char-equal ?d (string-to-char (nth 1 x))) + (setcar x (concat (car x) "/"))) + ((char-equal ?x (string-to-char (nth 1 x))) + (setcar x (concat (car x) "*")))))) + entries)) + + ;; Print entries. (mapcar (lambda (x) - (insert - (format - "%10s %3d %-8s %-8s %8s %s %s\n" - (nth 1 x) ; mode - 1 "nobody" "nogroup" - (nth 2 x) ; size - (format-time-string - (if (tramp-smb-time-less-p - (tramp-smb-time-subtract (current-time) (nth 3 x)) - tramp-smb-half-a-year) - "%b %e %R" - "%b %e %Y") - (nth 3 x)) ; date - (nth 0 x))) ; file name - (forward-line) - (beginning-of-line)) - entries))))) + (when (not (zerop (length (nth 0 x)))) + (insert + (format + "%10s %3d %-8s %-8s %8s %s %s\n" + (nth 1 x) ; mode + 1 "nobody" "nogroup" + (nth 2 x) ; size + (format-time-string + (if (tramp-time-less-p + (tramp-time-subtract (current-time) (nth 3 x)) + tramp-half-a-year) + "%b %e %R" + "%b %e %Y") + (nth 3 x)) ; date + (nth 0 x))) ; file name + (forward-line) + (beginning-of-line))) + entries))))) (defun tramp-smb-handle-make-directory (dir &optional parents) - "Like `make-directory' for tramp files." + "Like `make-directory' for Tramp files." (setq dir (directory-file-name (expand-file-name dir))) (unless (file-name-absolute-p dir) - (setq dir (concat default-directory dir))) + (setq dir (expand-file-name dir default-directory))) (with-parsed-tramp-file-name dir nil (save-match-data (let* ((share (tramp-smb-get-share localname)) @@ -510,26 +520,28 @@ WILDCARD and FULL-DIRECTORY-P are not handled." (when (file-directory-p ldir) (make-directory-internal dir)) (unless (file-directory-p dir) - (error "Couldn't make directory %s" dir)))))) + (tramp-error v 'file-error "Couldn't make directory %s" dir)))))) (defun tramp-smb-handle-make-directory-internal (directory) - "Like `make-directory-internal' for tramp files." + "Like `make-directory-internal' for Tramp files." (setq directory (directory-file-name (expand-file-name directory))) (unless (file-name-absolute-p directory) - (setq directory (concat default-directory directory))) + (setq directory (expand-file-name directory default-directory))) (with-parsed-tramp-file-name directory nil (save-match-data - (let* ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname nil))) + (let* ((file (tramp-smb-get-localname localname t))) (when (file-directory-p (file-name-directory directory)) - (tramp-smb-maybe-open-connection user host share) - (tramp-smb-send-command user host (format "mkdir \"%s\"" file))) + (tramp-smb-send-command v (format "mkdir \"%s\"" file)) + ;; We must also flush the cache of the directory, because + ;; file-attributes reads the values from there. + (tramp-flush-file-property v (file-name-directory localname))) (unless (file-directory-p directory) - (error "Couldn't make directory %s" directory)))))) + (tramp-error + v 'file-error "Couldn't make directory %s" directory)))))) (defun tramp-smb-handle-rename-file (filename newname &optional ok-if-already-exists) - "Like `rename-file' for tramp files." + "Like `rename-file' for Tramp files." (setq filename (expand-file-name filename) newname (expand-file-name newname)) @@ -543,80 +555,81 @@ WILDCARD and FULL-DIRECTORY-P are not handled." (when (file-directory-p newname) (setq newname (expand-file-name (file-name-nondirectory filename) newname))) - (when (and (not ok-if-already-exists) - (file-exists-p newname)) - (error "rename-file: file %s already exists" newname)) (with-parsed-tramp-file-name newname nil - (save-excursion - (let ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname t))) - (tramp-smb-maybe-open-connection user host share) - (tramp-message-for-buffer - nil tramp-smb-method user host - 5 "Copying file %s to file %s..." filename newname) - (if (tramp-smb-send-command - user host (format "put %s \"%s\"" filename file)) - (tramp-message-for-buffer - nil tramp-smb-method user host - 5 "Copying file %s to file %s...done" filename newname) - (error "Cannot rename `%s'" filename))))))) + (when (and (not ok-if-already-exists) + (file-exists-p newname)) + (tramp-error v 'file-already-exists newname)) + ;; We must also flush the cache of the directory, because + ;; file-attributes reads the values from there. + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + (let ((file (tramp-smb-get-localname localname t))) + (tramp-message v 0 "Copying file %s to file %s..." filename newname) + (if (tramp-smb-send-command v (format "put %s \"%s\"" filename file)) + (tramp-message + v 0 "Copying file %s to file %s...done" filename newname) + (tramp-error v 'file-error "Cannot rename `%s'" filename)))))) (delete-file filename)) (defun tramp-smb-handle-substitute-in-file-name (filename) - "Like `handle-substitute-in-file-name' for tramp files. -Catches errors for shares like \"C$/\", which are common in Microsoft Windows." + "Like `handle-substitute-in-file-name' for Tramp files. +\"//\" substitutes only in the local filename part. Catches +errors for shares like \"C$/\", which are common in Microsoft Windows." + (with-parsed-tramp-file-name filename nil + ;; Ignore in LOCALNAME everything before "//". + (when (and (stringp localname) (string-match ".+?/\\(/\\|~\\)" localname)) + (setq filename + (concat (file-remote-p filename) + (replace-match "\\1" nil nil localname))))) (condition-case nil (tramp-run-real-handler 'substitute-in-file-name (list filename)) (error filename))) (defun tramp-smb-handle-write-region (start end filename &optional append visit lockname confirm) - "Like `write-region' for tramp files." - (unless (eq append nil) - (error "Cannot append to file using tramp (`%s')" filename)) + "Like `write-region' for Tramp files." (setq filename (expand-file-name 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)) - (error "File not overwritten"))) (with-parsed-tramp-file-name filename nil - (save-excursion - (let ((share (tramp-smb-get-share localname)) - (file (tramp-smb-get-localname localname t)) - (curbuf (current-buffer)) - tmpfil) - ;; Write region into a tmp file. - (setq tmpfil (tramp-make-temp-file filename)) - ;; We say `no-message' here because we don't want the visited file - ;; modtime data to be clobbered from the temp file. We call - ;; `set-visited-file-modtime' ourselves later on. - (tramp-run-real-handler - 'write-region - (if confirm ; don't pass this arg unless defined for backward compat. - (list start end tmpfil append 'no-message lockname confirm) - (list start end tmpfil append 'no-message lockname))) - - (tramp-smb-maybe-open-connection user host share) - (tramp-message-for-buffer - nil tramp-smb-method user host - 5 "Writing tmp file %s to file %s..." tmpfil filename) - (if (tramp-smb-send-command - user host (format "put %s \"%s\"" tmpfil file)) - (tramp-message-for-buffer - nil tramp-smb-method user host - 5 "Writing tmp file %s to file %s...done" tmpfil filename) - (error "Cannot write `%s'" filename)) - - (delete-file tmpfil) - (unless (equal curbuf (current-buffer)) - (error "Buffer has changed from `%s' to `%s'" - curbuf (current-buffer))) - (when (eq visit t) - (set-visited-file-modtime)))))) + (unless (eq append nil) + (tramp-error + v 'file-error "Cannot append to file using Tramp (`%s')" 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"))) + ;; We must also flush the cache of the directory, because + ;; `file-attributes' reads the values from there. + (tramp-flush-file-property v (file-name-directory localname)) + (tramp-flush-file-property v localname) + (let ((file (tramp-smb-get-localname localname t)) + (curbuf (current-buffer)) + (tmpfile (tramp-compat-make-temp-file filename))) + ;; We say `no-message' here because we don't want the visited file + ;; modtime data to be clobbered from the temp file. We call + ;; `set-visited-file-modtime' ourselves later on. + (tramp-run-real-handler + 'write-region + (if confirm ; don't pass this arg unless defined for backward compat. + (list start end tmpfile append 'no-message lockname confirm) + (list start end tmpfile append 'no-message lockname))) + + (tramp-message v 5 "Writing tmp file %s to file %s..." tmpfile filename) + (if (tramp-smb-send-command v (format "put %s \"%s\"" tmpfile file)) + (tramp-message + v 5 "Writing tmp file %s to file %s...done" tmpfile filename) + (tramp-error v 'file-error "Cannot write `%s'" filename)) + + (delete-file tmpfile) + (unless (equal curbuf (current-buffer)) + (tramp-error + v 'file-error + "Buffer has changed from `%s' to `%s'" curbuf (current-buffer))) + (when (eq visit t) + (set-visited-file-modtime))))) ;; Internal file name functions @@ -652,51 +665,53 @@ If CONVERT is non-nil exchange \"/\" by \"\\\\\"." ;; Share names of a host are cached. It is very unlikely that the ;; shares do change during connection. -(defun tramp-smb-get-file-entries (user host share localname) - "Read entries which match LOCALNAME. +(defun tramp-smb-get-file-entries (directory) + "Read entries which match DIRECTORY. Either the shares are listed, or the `dir' command is executed. -Only entries matching the localname are returned. Result is a list of (LOCALNAME MODE SIZE MONTH DAY TIME YEAR)." - (save-excursion - (save-match-data - (let ((base (or (and (> (length localname) 0) - (string-match "\\([^/]+\\)$" localname) - (regexp-quote (match-string 1 localname))) - "")) - res entry) - (set-buffer (tramp-get-buffer nil tramp-smb-method user host)) - (if (and (not share) tramp-smb-share-cache) - ;; Return cached shares - (setq res tramp-smb-share-cache) - ;; Read entries - (tramp-smb-maybe-open-connection user host share) - (when share - (tramp-smb-send-command - user host - (format "dir %s" - (if (zerop (length localname)) "" (concat "\"" localname "*\""))))) - (goto-char (point-min)) - ;; Loop the listing - (unless (re-search-forward tramp-smb-errors nil t) - (while (not (eobp)) - (setq entry (tramp-smb-read-file-entry share)) - (forward-line) - (when entry (add-to-list 'res entry)))) - (unless share + (with-parsed-tramp-file-name directory nil + (setq localname (or localname "/")) + (with-file-property v localname "file-entries" + (with-current-buffer (tramp-get-buffer v) + (let* ((share (tramp-smb-get-share localname)) + (file (tramp-smb-get-localname localname nil)) + (cache (tramp-get-connection-property v "share-cache" nil)) + res entry) + + (if (and (not share) cache) + ;; Return cached shares + (setq res cache) + + ;; Read entries + (setq file (file-name-as-directory file)) + (when (string-match "^\\./" file) + (setq file (substring file 1))) + (if share + (tramp-smb-send-command v (format "dir \"%s*\"" file)) + ;; `tramp-smb-maybe-open-connection' lists also the share names + (tramp-smb-maybe-open-connection v)) + + ;; Loop the listing + (goto-char (point-min)) + (unless (re-search-forward tramp-smb-errors nil t) + (while (not (eobp)) + (setq entry (tramp-smb-read-file-entry share)) + (forward-line) + (when entry (add-to-list 'res entry)))) + ;; Cache share entries - (setq tramp-smb-share-cache res))) + (unless share + (tramp-set-connection-property v "share-cache" res))) - ;; Add directory itself - (add-to-list 'res '("" "drwxrwxrwx" 0 (0 0))) + ;; Add directory itself + (add-to-list 'res '("" "drwxrwxrwx" 0 (0 0))) - ;; There's a very strange error (debugged with XEmacs 21.4.14) - ;; If there's no short delay, it returns nil. No idea about - (when (featurep 'xemacs) (sleep-for 0.01)) + ;; There's a very strange error (debugged with XEmacs 21.4.14) + ;; If there's no short delay, it returns nil. No idea about. + (when (featurep 'xemacs) (sleep-for 0.01)) - ;; Check for matching entries - (delq nil (mapcar - (lambda (x) (and (string-match base (nth 0 x)) x)) - res)))))) + ;; Return entries + (delq nil res)))))) ;; Return either a share name (if SHARE is nil), or a file name ;; @@ -721,7 +736,7 @@ Result is a list of (LOCALNAME MODE SIZE MONTH DAY TIME YEAR)." ;; \s- - space delimeter ;; \w\{3,3\} - month ;; \s- - space delimeter -;; [ 19][0-9] - day +;; [ 12][0-9] - day ;; \s- - space delimeter ;; [0-9]\{2,2\}:[0-9]\{2,2\}:[0-9]\{2,2\} - time ;; \s- - space delimeter @@ -756,18 +771,20 @@ Result is a list of (LOCALNAME MODE SIZE MONTH DAY TIME YEAR)." "Parse entry in SMB output buffer. If SHARE is result, entries are of type dir. Otherwise, shares are listed. Result is the list (LOCALNAME MODE SIZE MTIME)." - (let ((line (buffer-substring (point) (tramp-point-at-eol))) +;; We are called from `tramp-smb-get-file-entries', which sets the +;; current buffer. + (let ((line (buffer-substring (point) (tramp-compat-line-end-position))) localname mode size month day hour min sec year mtime) (if (not share) - ; Read share entries - (when (string-match "^\\s-+\\(\\S-+\\)\\s-+Disk" line) + ;; Read share entries. + (when (string-match "^\\s-+\\(\\S-\\(.*\\S-\\)?\\)\\s-+Disk" line) (setq localname (match-string 1 line) mode "dr-xr-xr-x" size 0)) - ; Real listing + ;; Real listing. (block nil ;; year @@ -833,219 +850,185 @@ Result is the list (LOCALNAME MODE SIZE MTIME)." (if (and sec min hour day month year) (encode-time sec min hour day - (cdr (assoc (downcase month) tramp-smb-parse-time-months)) + (cdr (assoc (downcase month) tramp-parse-time-months)) year) '(0 0))) (list localname mode size mtime)))) -;; Inodes don't exist for SMB files. Therefore we must generate virtual ones. -;; Used in `find-buffer-visiting'. -;; The method applied might be not so efficient (Ange-FTP uses hashes). But -;; performance isn't the major issue given that file transfer will take time. - -(defun tramp-smb-get-inode (share file) - "Returns the virtual inode number. -If it doesn't exist, generate a new one." - (let ((string (concat share "/" (directory-file-name file)))) - (unless (assoc string tramp-smb-inodes) - (add-to-list 'tramp-smb-inodes - (list string (length tramp-smb-inodes)))) - (nth 1 (assoc string tramp-smb-inodes)))) - ;; Connection functions -(defun tramp-smb-send-command (user host command) - "Send the COMMAND to USER at HOST (logged into an SMB session). -Erases temporary buffer before sending the command. Returns nil if -there has been an error message from smbclient." - (save-excursion - (set-buffer (tramp-get-buffer nil tramp-smb-method user host)) - (erase-buffer) - (tramp-send-command nil tramp-smb-method user host command nil t) - (tramp-smb-wait-for-output user host))) - -(defun tramp-smb-maybe-open-connection (user host share) - "Maybe open a connection to HOST, logging in as USER, using `tramp-smb-program'. +(defun tramp-smb-send-command (vec command) + "Send the COMMAND to connection VEC. +Returns nil if there has been an error message from smbclient." + (tramp-smb-maybe-open-connection vec) + (tramp-message vec 6 "%s" command) + (tramp-send-string vec command) + (tramp-smb-wait-for-output vec)) + +(defun tramp-smb-maybe-open-connection (vec) + "Maybe open a connection to HOST, log in as USER, using `tramp-smb-program'. Does not do anything if a connection is already open, but re-opens the connection if a previous connection has died for some reason." - (let ((process-connection-type tramp-process-connection-type) - (p (get-buffer-process - (tramp-get-buffer nil tramp-smb-method user host)))) - (save-excursion - (set-buffer (tramp-get-buffer nil tramp-smb-method user host)) - ;; Check whether it is still the same share - (unless (and p (processp p) (string-equal tramp-smb-share share)) - (when (and p (processp p)) - (delete-process p) - (setq p nil))) - ;; If too much time has passed since last command was sent, look - ;; whether process is still alive. If it isn't, kill it. - (when (and tramp-last-cmd-time - (> (tramp-time-diff (current-time) tramp-last-cmd-time) 60) - p (processp p) (memq (process-status p) '(run open))) - (unless (and p (processp p) (memq (process-status p) '(run open))) - (delete-process p) - (setq p nil)))) - (unless (and p (processp p) (memq (process-status p) '(run open))) - (when (and p (processp p)) - (delete-process p)) - (tramp-smb-open-connection user host share)))) - -(defun tramp-smb-open-connection (user host share) - "Open a connection using `tramp-smb-program'. -This starts the command `smbclient //HOST/SHARE -U USER', then waits -for a remote password prompt. It queries the user for the password, -then sends the password to the remote host. - -Domain names in USER and port numbers in HOST are acknowledged." - - (when (and (fboundp 'executable-find) - (not (funcall 'executable-find tramp-smb-program))) - (error "Cannot find command %s in %s" tramp-smb-program exec-path)) + (let* ((share (tramp-smb-get-share (tramp-file-name-localname vec))) + (buf (tramp-get-buffer vec)) + (p (get-buffer-process buf))) - (save-match-data - (let* ((buffer (tramp-get-buffer nil tramp-smb-method user host)) - (real-user user) - (real-host host) - domain port args) - - ; Check for domain ("user%domain") and port ("host#port") - (when (and user (string-match "\\(.+\\)%\\(.+\\)" user)) - (setq real-user (or (match-string 1 user) user) - domain (match-string 2 user))) - - (when (and host (string-match "\\(.+\\)#\\(.+\\)" host)) - (setq real-host (or (match-string 1 host) host) - port (match-string 2 host))) - - (if share - (setq args (list (concat "//" real-host "/" share))) - (setq args (list "-L" real-host ))) - - (if real-user - (setq args (append args (list "-U" real-user))) - (setq args (append args (list "-N")))) - - (when domain (setq args (append args (list "-W" domain)))) - (when port (setq args (append args (list "-p" port)))) - - ; OK, let's go - (tramp-pre-connection nil tramp-smb-method user host tramp-chunksize) - (tramp-message 7 "Opening connection for //%s@%s/%s..." - user host (or share "")) - - (let* ((default-directory (tramp-temporary-file-directory)) - ;; If we omit the conditional here, then we would use - ;; `undecided-dos' in some cases. With the conditional, - ;; we use nil in these cases. Which one is right? - (coding-system-for-read (unless (and (not (featurep 'xemacs)) - (> emacs-major-version 20)) - tramp-dos-coding-system)) - (p (apply #'start-process (buffer-name buffer) buffer - tramp-smb-program args))) - - (tramp-message 9 "Started process %s" (process-command p)) - (tramp-set-process-query-on-exit-flag p nil) - (set-buffer buffer) - (setq tramp-smb-share share) - - ; send password - (when real-user - (let ((pw-prompt "Password:")) - (tramp-message 9 "Sending password") - (tramp-enter-password p pw-prompt user host))) - - (unless (tramp-smb-wait-for-output user host) - (tramp-clear-passwd user host) - (error "Cannot open connection //%s@%s/%s" - user host (or share ""))))))) + ;; If too much time has passed since last command was sent, look + ;; whether has been an error message; maybe due to connection timeout. + (with-current-buffer buf + (goto-char (point-min)) + (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)) + (re-search-forward tramp-smb-errors nil t)) + (delete-process p) + (setq p nil))) + + ;; Check whether it is still the same share. + (unless + (and p (processp p) (memq (process-status p) '(run open)) + (string-equal + share + (tramp-get-connection-property p "smb-share" ""))) + + (save-match-data + ;; There might be unread output from checking for share names. + (when buf (with-current-buffer buf (erase-buffer))) + (when (and p (processp p)) (delete-process p)) + + (unless (let ((default-directory + (tramp-compat-temporary-file-directory))) + (executable-find tramp-smb-program)) + (error "Cannot find command %s in %s" tramp-smb-program exec-path)) + + (let* ((user (tramp-file-name-user vec)) + (host (tramp-file-name-host vec)) + (real-user user) + (real-host host) + domain port args) + + ;; Check for domain ("user%domain") and port ("host#port"). + (when (and user (string-match "\\(.+\\)%\\(.+\\)" user)) + (setq real-user (or (match-string 1 user) user) + domain (match-string 2 user))) + + (when (and host (string-match "\\(.+\\)#\\(.+\\)" host)) + (setq real-host (or (match-string 1 host) host) + port (match-string 2 host))) + + (if share + (setq args (list (concat "//" real-host "/" share))) + (setq args (list "-L" real-host ))) + + (if (not (zerop (length real-user))) + (setq args (append args (list "-U" real-user))) + (setq args (append args (list "-N")))) + + (when domain (setq args (append args (list "-W" domain)))) + (when port (setq args (append args (list "-p" port)))) + (setq args (append args (list "-s" "/dev/null"))) + + ;; OK, let's go. + (tramp-message + vec 3 "Opening connection for //%s%s/%s..." + (if (not (zerop (length user))) (concat user "@") "") + host (or share "")) + + (let* ((coding-system-for-read nil) + (process-connection-type tramp-process-connection-type) + (p (let ((default-directory + (tramp-compat-temporary-file-directory))) + (apply #'start-process + (tramp-buffer-name vec) (tramp-get-buffer vec) + tramp-smb-program args)))) + + (tramp-message + vec 6 "%s" (mapconcat 'identity (process-command p) " ")) + (set-process-sentinel p 'tramp-flush-connection-property) + (tramp-set-process-query-on-exit-flag p nil) + (tramp-set-connection-property p "smb-share" share) + + ;; Set variables for computing the prompt for reading password. + (setq tramp-current-method tramp-smb-method + tramp-current-user user + tramp-current-host host) + + ;; Set chunksize. Otherwise, `tramp-send-string' might + ;; try it itself. + (tramp-set-connection-property p "chunksize" tramp-chunksize) + + ;; Play login scenario. + (tramp-process-actions + p vec + (if share + tramp-smb-actions-with-share + tramp-smb-actions-without-share)) + + (tramp-message + vec 3 "Opening connection for //%s%s/%s...done" + (if (not (zerop (length user))) (concat user "@") "") + host (or share "")))))))) ;; We don't use timeouts. If needed, the caller shall wrap around. -(defun tramp-smb-wait-for-output (user host) +(defun tramp-smb-wait-for-output (vec) "Wait for output from smbclient command. Returns nil if an error message has appeared." - (let ((proc (get-buffer-process (current-buffer))) - (found (progn (goto-char (point-min)) - (re-search-forward tramp-smb-prompt nil t))) - (err (progn (goto-char (point-min)) - (re-search-forward tramp-smb-errors nil t)))) - - ;; Algorithm: get waiting output. See if last line contains - ;; tramp-smb-prompt sentinel or tramp-smb-errors strings. - ;; If not, wait a bit and again get waiting output. - (while (not found) - - ;; Accept pending output. - (tramp-accept-process-output proc) - - ;; Search for prompt. - (goto-char (point-min)) - (setq found (re-search-forward tramp-smb-prompt nil t)) - - ;; Search for errors. - (goto-char (point-min)) - (setq err (re-search-forward tramp-smb-errors nil t))) + (with-current-buffer (tramp-get-buffer vec) + (let ((p (get-buffer-process (current-buffer))) + (found (progn (goto-char (point-min)) + (re-search-forward tramp-smb-prompt nil t))) + (err (progn (goto-char (point-min)) + (re-search-forward tramp-smb-errors nil t)))) - ;; Add output to debug buffer if appropriate. - (when tramp-debug-buffer - (append-to-buffer - (tramp-get-debug-buffer nil tramp-smb-method user host) - (point-min) (point-max))) + ;; Algorithm: get waiting output. See if last line contains + ;; tramp-smb-prompt sentinel or tramp-smb-errors strings. + ;; If not, wait a bit and again get waiting output. + (while (and (not found) (not err)) - ;; Return value is whether no error message has appeared. - (not err))) + ;; Accept pending output. + (tramp-accept-process-output p) + ;; Search for prompt. + (goto-char (point-min)) + (setq found (re-search-forward tramp-smb-prompt nil t)) -;; Snarfed code from time-date.el and parse-time.el + ;; Search for errors. + (goto-char (point-min)) + (setq err (re-search-forward tramp-smb-errors nil t))) -(defconst tramp-smb-half-a-year '(241 17024) -"Evaluated by \"(days-to-time 183)\".") + ;; When the process is still alive, read pending output. + (while (and (not found) (memq (process-status p) '(run open))) -(defconst tramp-smb-parse-time-months '(("jan" . 1) ("feb" . 2) ("mar" . 3) - ("apr" . 4) ("may" . 5) ("jun" . 6) - ("jul" . 7) ("aug" . 8) ("sep" . 9) - ("oct" . 10) ("nov" . 11) ("dec" . 12)) -"Alist mapping month names to integers.") + ;; Accept pending output. + (tramp-accept-process-output p) -(defun tramp-smb-time-less-p (t1 t2) - "Say whether time value T1 is less than time value T2." - (unless t1 (setq t1 '(0 0))) - (unless t2 (setq t2 '(0 0))) - (or (< (car t1) (car t2)) - (and (= (car t1) (car t2)) - (< (nth 1 t1) (nth 1 t2))))) + ;; Search for prompt. + (goto-char (point-min)) + (setq found (re-search-forward tramp-smb-prompt nil t))) -(defun tramp-smb-time-subtract (t1 t2) - "Subtract two time values. -Return the difference in the format of a time value." - (unless t1 (setq t1 '(0 0))) - (unless t2 (setq t2 '(0 0))) - (let ((borrow (< (cadr t1) (cadr t2)))) - (list (- (car t1) (car t2) (if borrow 1 0)) - (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2))))) + ;; Return value is whether no error message has appeared. + (tramp-message vec 6 "\n%s" (buffer-string)) + (not err)))) (provide 'tramp-smb) ;;; TODO: -;; * Provide a local smb.conf. The default one might not be readable. ;; * Error handling in case password is wrong. ;; * Read password from "~/.netrc". ;; * Return more comprehensive file permission string. Think whether it is ;; possible to implement `set-file-modes'. -;; * Handle WILDCARD and FULL-DIRECTORY-P in -;; `tramp-smb-handle-insert-directory'. ;; * Handle links (FILENAME.LNK). -;; * Maybe local tmp files should have the same extension like the original -;; files. Strange behaviour with jka-compr otherwise? -;; * Copy files in dired from SMB to another method doesn't work. ;; * Try to remove the inclusion of dummy "" directory. Seems to be at ;; several places, especially in `tramp-smb-handle-insert-directory'. -;; * Provide variables for debug. ;; * (RMS) Use unwind-protect to clean up the state so as to make the state ;; regular again. +;; * Make it multi-hop capable. ;;; arch-tag: fcc9dbec-7503-4d73-b638-3c8aa59575f5 ;;; tramp-smb.el ends here diff --git a/lisp/net/tramp-util.el b/lisp/net/tramp-util.el deleted file mode 100644 index 145f3cfc836..00000000000 --- a/lisp/net/tramp-util.el +++ /dev/null @@ -1,138 +0,0 @@ -;;; -*- coding: iso-2022-7bit; -*- -;;; tramp-util.el --- Misc utility functions to use with Tramp - -;; Copyright (C) 2001, 2002, 2003, 2004, 2005, -;; 2006, 2007, 2008 Free Software Foundation, Inc. - -;; Author: kai.grossjohann@gmx.net -;; Keywords: comm, extensions, processes - -;; This file 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, or (at your option) -;; any later version. - -;; This file is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to -;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. - -;;; Commentary: - -;; Some misc. utility functions that might go nicely with Tramp. -;; Mostly, these are kluges awaiting real solutions later on. - -;;; Code: - -(require 'compile) -(require 'tramp) -(add-hook 'tramp-util-unload-hook - '(lambda () - (when (featurep 'tramp) - (unload-feature 'tramp 'force)))) - -;; Define a Tramp minor mode. It's intention is to redefine some keys for Tramp -;; specific functions, like compilation. -;; The key remapping works since Emacs 22 only. Unknown for XEmacs. - -;; Pacify byte-compiler -(eval-when-compile - (unless (fboundp 'define-minor-mode) - (defalias 'define-minor-mode 'identity) - (defvar tramp-minor-mode)) - (unless (featurep 'xemacs) - (defalias 'add-menu-button 'ignore))) - -(defvar tramp-minor-mode-map (make-sparse-keymap) - "Keymap for Tramp minor mode.") - -(define-minor-mode tramp-minor-mode "Tramp minor mode for utility functions." - :group 'tramp - :global nil - :init-value nil - :lighter " Tramp" - :keymap tramp-minor-mode-map - (setq tramp-minor-mode - (and tramp-minor-mode (tramp-tramp-file-p default-directory)))) - -(add-hook 'find-file-hooks 'tramp-minor-mode t) -(add-hook 'tramp-util-unload-hook - '(lambda () - (remove-hook 'find-file-hooks 'tramp-minor-mode))) - -(add-hook 'dired-mode-hook 'tramp-minor-mode t) -(add-hook 'tramp-util-unload-hook - '(lambda () - (remove-hook 'dired-mode-hook 'tramp-minor-mode))) - -(defun tramp-remap-command (old-command new-command) - "Replaces bindings of OLD-COMMAND by NEW-COMMAND. -If remapping functionality for keymaps is defined, this happens for all -bindings. Otherwise, only bindings active during invocation are taken -into account. XEmacs menubar bindings are not changed by this." - (if (functionp 'command-remapping) - ;; Emacs 22 - (eval - `(define-key tramp-minor-mode-map [remap ,old-command] new-command)) - ;; previous Emacs versions. - (mapcar - '(lambda (x) - (define-key tramp-minor-mode-map x new-command)) - (where-is-internal old-command)))) - -(tramp-remap-command 'compile 'tramp-compile) -(tramp-remap-command 'recompile 'tramp-recompile) - -;; XEmacs has an own mimic for menu entries -(when (fboundp 'add-menu-button) - (funcall 'add-menu-button - '("Tools" "Compile") - ["Compile..." - (command-execute (if tramp-minor-mode 'tramp-compile 'compile)) - :active (fboundp 'compile)]) - (funcall 'add-menu-button - '("Tools" "Compile") - ["Repeat Compilation" - (command-execute (if tramp-minor-mode 'tramp-recompile 'recompile)) - :active (fboundp 'compile)])) - -;; Utility functions. - -(defun tramp-compile (command) - "Compile on remote host." - (interactive - (if (or compilation-read-command current-prefix-arg) - (list (read-from-minibuffer "Compile command: " - compile-command nil nil - '(compile-history . 1))) - (list compile-command))) - (setq compile-command command) - (save-some-buffers (not compilation-ask-about-save) nil) - (let ((d default-directory)) - (save-excursion - (pop-to-buffer (get-buffer-create "*Compilation*") t) - (erase-buffer) - (setq default-directory d))) - (tramp-handle-shell-command command (get-buffer "*Compilation*")) - (pop-to-buffer (get-buffer "*Compilation*")) - (tramp-minor-mode 1) - (compilation-minor-mode 1)) - -(defun tramp-recompile () - "Re-compile on remote host." - (interactive) - (save-some-buffers (not compilation-ask-about-save) nil) - (tramp-handle-shell-command compile-command (get-buffer "*Compilation*")) - (pop-to-buffer (get-buffer "*Compilation*")) - (tramp-minor-mode 1) - (compilation-minor-mode 1)) - -(provide 'tramp-util) - -;;; arch-tag: 500f9992-a44e-46d0-83a7-980799251808 -;;; tramp-util.el ends here diff --git a/lisp/net/tramp-uu.el b/lisp/net/tramp-uu.el index c5316cd35ef..56c91377373 100644 --- a/lisp/net/tramp-uu.el +++ b/lisp/net/tramp-uu.el @@ -1,10 +1,10 @@ -;;; -*- coding: iso-2022-7bit; -*- ;;; tramp-uu.el --- uuencode in Lisp +;;; -*- coding: utf-8; -*- ;; Copyright (C) 2002, 2003, 2004, 2005, 2006, ;; 2007, 2008 Free Software Foundation, Inc. -;; Author: Kai Gro,A_(Bjohann <kai.grossjohann@gmx.net> +;; Author: Kai Großjohann <kai.grossjohann@gmx.net> ;; Keywords: comm, terminals ;; This file is free software; you can redistribute it and/or modify @@ -18,9 +18,8 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to -;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. ;;; Commentary: diff --git a/lisp/net/tramp-vc.el b/lisp/net/tramp-vc.el deleted file mode 100644 index 52e47905970..00000000000 --- a/lisp/net/tramp-vc.el +++ /dev/null @@ -1,539 +0,0 @@ -;;; tramp-vc.el --- Version control integration for TRAMP.el - -;; Copyright (C) 2000, 2001, 2002, 2003, 2004, -;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc. - -;; Author: Daniel Pittman <daniel@danann.net> -;; Keywords: comm, processes - -;; This file is part of GNU Emacs. - -;; GNU Emacs is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation; either version 3, or (at your option) -;; any later version. - -;; GNU Emacs is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. - -;;; Commentary: - -;; See the main module, 'tramp.el' for discussion of the purpose of TRAMP. -;; This module provides integration between remote files accessed by TRAMP and -;; the Emacs version control system. - -;;; Code: - -(require 'vc) -;; Old VC defines vc-rcs-release in vc.el, new VC requires extra module. -(unless (boundp 'vc-rcs-release) - (require 'vc-rcs)) -(require 'tramp) - -;; Avoid byte-compiler warnings if the byte-compiler supports this. -;; Currently, XEmacs supports this. -(eval-when-compile - (when (fboundp 'byte-compiler-options) - (let (unused-vars) ; Pacify Emacs byte-compiler - (defalias 'warnings 'identity) ; Pacify Emacs byte-compiler - (byte-compiler-options (warnings (- unused-vars)))))) - -;; -- vc -- - -;; This used to blow away the file-name-handler-alist and reinstall -;; TRAMP into it. This was intended to let VC work remotely. It didn't, -;; at least not in my XEmacs 21.2 install. -;; -;; In any case, tramp-run-real-handler now deals correctly with disabling -;; the things that should be, making this a no-op. -;; -;; I have removed it from the tramp-file-name-handler-alist because the -;; shortened version does nothing. This is for reference only now. -;; -;; Daniel Pittman <daniel@danann.net> -;; -;; (defun tramp-handle-vc-registered (file) -;; "Like `vc-registered' for tramp files." -;; (tramp-run-real-handler 'vc-registered (list file))) - -;; `vc-do-command' -;; This function does not deal well with remote files, so we define -;; our own version and make a backup of the original function and -;; call our version for tramp files and the original version for -;; normal files. - -;; The following function is pretty much copied from vc.el, but -;; the part that actually executes a command is changed. -;; CCC: this probably works for Emacs 21, too. -(defun tramp-vc-do-command (buffer okstatus command file last &rest flags) - "Like `vc-do-command' but invoked for tramp files. -See `vc-do-command' for more information." - (save-match-data - (and file (setq file (expand-file-name file))) - (if (not buffer) (setq buffer "*vc*")) - (if vc-command-messages - (message "Running `%s' on `%s'..." command file)) - (let ((obuf (current-buffer)) (camefrom (current-buffer)) - (squeezed nil) - (olddir default-directory) - vc-file status) - (let* ((v (tramp-dissect-file-name (expand-file-name file))) - (multi-method (tramp-file-name-multi-method v)) - (method (tramp-file-name-method v)) - (user (tramp-file-name-user v)) - (host (tramp-file-name-host v)) - (localname (tramp-file-name-localname v))) - (set-buffer (get-buffer-create buffer)) - (set (make-local-variable 'vc-parent-buffer) camefrom) - (set (make-local-variable 'vc-parent-buffer-name) - (concat " from " (buffer-name camefrom))) - (setq default-directory olddir) - - (erase-buffer) - - (mapcar - (function - (lambda (s) (and s (setq squeezed (append squeezed (list s)))))) - flags) - (if (and (eq last 'MASTER) file - (setq vc-file (vc-name file))) - (setq squeezed - (append squeezed - (list (tramp-file-name-localname - (tramp-dissect-file-name vc-file)))))) - (if (and file (eq last 'WORKFILE)) - (progn - (let* ((pwd (expand-file-name default-directory)) - (preflen (length pwd))) - (if (string= (substring file 0 preflen) pwd) - (setq file (substring file preflen)))) - (setq squeezed (append squeezed (list file))))) - ;; Unless we (save-window-excursion) the layout of windows in - ;; the current frame changes. This is painful, at best. - ;; - ;; As a point of note, (save-excursion) is still here only because - ;; it preserves (point) in the current buffer. (save-window-excursion) - ;; does not, at least under XEmacs 21.2. - ;; - ;; I trust that the FSF support this as well. I can't find useful - ;; documentation to check :( - ;; - ;; Daniel Pittman <daniel@danann.net> - (save-excursion - (save-window-excursion - ;; Actually execute remote command - ;; `shell-command' cannot be used; it isn't magic in XEmacs. - (tramp-handle-shell-command - (mapconcat 'tramp-shell-quote-argument - (cons command squeezed) " ") t) - ;;(tramp-wait-for-output) - ;; Get status from command - (tramp-send-command multi-method method user host "echo $?") - (tramp-wait-for-output) - ;; Make sure to get status from last line of output. - (goto-char (point-max)) (forward-line -1) - (setq status (read (current-buffer))) - (message "Command %s returned status %d." command status))) - (goto-char (point-max)) - (set-buffer-modified-p nil) - (forward-line -1) - (if (or (not (integerp status)) - (and (integerp okstatus) (< okstatus status))) - (progn - (pop-to-buffer buffer) - (goto-char (point-min)) - (shrink-window-if-larger-than-buffer) - (error "Running `%s'...FAILED (%s)" command - (if (integerp status) - (format "status %d" status) - status)) - ) - (if vc-command-messages - (message "Running %s...OK" command)) - ) - (set-buffer obuf) - status)) - )) - -;; Following code snarfed from Emacs 21 vc.el and slightly tweaked. -(defun tramp-vc-do-command-new (buffer okstatus command file &rest flags) - "Like `vc-do-command' but for TRAMP files. -This function is for the new VC which comes with Emacs 21. -Since TRAMP doesn't do async commands yet, this function doesn't, either." - (and file (setq file (expand-file-name file))) - (if vc-command-messages - (message "Running %s on %s..." command file)) - (save-current-buffer - (unless (eq buffer t) - ; Pacify byte-compiler - (funcall (symbol-function 'vc-setup-buffer) buffer)) - (let ((squeezed nil) - (inhibit-read-only t) - (status 0)) - (let* ((v (when file (tramp-dissect-file-name file))) - (multi-method (when file (tramp-file-name-multi-method v))) - (method (when file (tramp-file-name-method v))) - (user (when file (tramp-file-name-user v))) - (host (when file (tramp-file-name-host v))) - (localname (when file (tramp-file-name-localname v)))) - (setq squeezed (delq nil (copy-sequence flags))) - (when file - (setq squeezed (append squeezed (list (file-relative-name - file default-directory))))) - (let ((w32-quote-process-args t)) - (when (eq okstatus 'async) - (message "Tramp doesn't do async commands, running synchronously.")) - ;; `shell-command' cannot be used; it isn't magic in XEmacs. - (setq status (tramp-handle-shell-command - (mapconcat 'tramp-shell-quote-argument - (cons command squeezed) " ") t)) - (when (or (not (integerp status)) - (and (integerp okstatus) (< okstatus status))) - (pop-to-buffer (current-buffer)) - (goto-char (point-min)) - (shrink-window-if-larger-than-buffer) - (error "Running %s...FAILED (%s)" command - (if (integerp status) (format "status %d" status) status)))) - (if vc-command-messages - (message "Running %s...OK" command)) - ; Pacify byte-compiler - (funcall (symbol-function 'vc-exec-after) - `(run-hook-with-args - 'vc-post-command-functions ',command ',localname ',flags)) - status)))) - - -;; The context for a VC command is the current buffer. -;; That makes a test on the buffers file more reliable than a test on the -;; arguments. -;; This is needed to handle remote VC correctly - else we test against the -;; local VC system and get things wrong... -;; Daniel Pittman <daniel@danann.net> -;;-(if (fboundp 'vc-call-backend) -;;- () ;; This is the new VC for which we don't have an appropriate advice yet -;;-) -(unless (fboundp 'process-file) - (if (fboundp 'vc-call-backend) - (defadvice vc-do-command - (around tramp-advice-vc-do-command - (buffer okstatus command file &rest flags) - activate) - "Invoke tramp-vc-do-command for tramp files." - (let ((file (symbol-value 'file))) ;pacify byte-compiler - (if (or (and (stringp file) (tramp-tramp-file-p file)) - (and (buffer-file-name) (tramp-tramp-file-p (buffer-file-name)))) - (setq ad-return-value - (apply 'tramp-vc-do-command-new buffer okstatus command - file ;(or file (buffer-file-name)) - flags)) - ad-do-it))) - (defadvice vc-do-command - (around tramp-advice-vc-do-command - (buffer okstatus command file last &rest flags) - activate) - "Invoke tramp-vc-do-command for tramp files." - (let ((file (symbol-value 'file))) ;pacify byte-compiler - (if (or (and (stringp file) (tramp-tramp-file-p file)) - (and (buffer-file-name) (tramp-tramp-file-p (buffer-file-name)))) - (setq ad-return-value - (apply 'tramp-vc-do-command buffer okstatus command - (or file (buffer-file-name)) last flags)) - ad-do-it)))) - - (add-hook 'tramp-unload-hook - '(lambda () (ad-unadvise 'vc-do-command)))) - - -;; XEmacs uses this to do some of its work. Like vc-do-command, we -;; need to enhance it to make VC work via TRAMP-mode. -;; -;; Like the previous function, this is a cut-and-paste job from the VC -;; file. It's based on the vc-do-command code. -;; CCC: this isn't used in Emacs 21, so do as before. -(defun tramp-vc-simple-command (okstatus command file &rest args) - ;; Simple version of vc-do-command, for use in vc-hooks only. - ;; Don't switch to the *vc-info* buffer before running the - ;; command, because that would change its default directory - (save-match-data - (let* ((v (tramp-dissect-file-name (expand-file-name file))) - (multi-method (tramp-file-name-multi-method v)) - (method (tramp-file-name-method v)) - (user (tramp-file-name-user v)) - (host (tramp-file-name-host v)) - (localname (tramp-file-name-localname v))) - (save-excursion (set-buffer (get-buffer-create "*vc-info*")) - (erase-buffer)) - (let ((exec-path (append vc-path exec-path)) exec-status - ;; Add vc-path to PATH for the execution of this command. - (process-environment - (cons (concat "PATH=" (getenv "PATH") - path-separator - (mapconcat 'identity vc-path path-separator)) - process-environment))) - ;; Call the actual process. See tramp-vc-do-command for discussion of - ;; why this does both (save-window-excursion) and (save-excursion). - ;; - ;; As a note, I don't think that the process-environment stuff above - ;; has any effect on the remote system. This is a hard one though as - ;; there is no real reason to expect local and remote paths to be - ;; identical... - ;; - ;; Daniel Pittman <daniel@danann.net> - (save-excursion - (save-window-excursion - ;; Actually execute remote command - ;; `shell-command' cannot be used; it isn't magic in XEmacs. - (tramp-handle-shell-command - (mapconcat 'tramp-shell-quote-argument - (append (list command) args (list localname)) " ") - (get-buffer-create"*vc-info*")) - ;(tramp-wait-for-output) - ;; Get status from command - (tramp-send-command multi-method method user host "echo $?") - (tramp-wait-for-output) - (setq exec-status (read (current-buffer))) - (message "Command %s returned status %d." command exec-status))) - - ;; Maybe okstatus can be `async' here. But then, maybe the - ;; async thing is new in Emacs 21, but this function is only - ;; used in Emacs 20. - (cond ((> exec-status okstatus) - (switch-to-buffer (get-file-buffer file)) - (shrink-window-if-larger-than-buffer - (display-buffer "*vc-info*")) - (error "Couldn't find version control information"))) - exec-status)))) - -;; This function does not exist any more in Emacs-21's VC -(defadvice vc-simple-command - (around tramp-advice-vc-simple-command - (okstatus command file &rest args) - activate) - "Invoke tramp-vc-simple-command for tramp files." - (let ((file (symbol-value 'file))) ;pacify byte-compiler - (if (or (and (stringp file) (tramp-tramp-file-p file)) - (and (buffer-file-name) (tramp-tramp-file-p (buffer-file-name)))) - (setq ad-return-value - (apply 'tramp-vc-simple-command okstatus command - (or file (buffer-file-name)) args)) - ad-do-it))) - -(add-hook 'tramp-unload-hook - '(lambda () (ad-unadvise 'vc-simple-command))) - - -;; `vc-workfile-unchanged-p' -;; This function does not deal well with remote files, so we do the -;; same as for `vc-do-command'. - -;; `vc-workfile-unchanged-p' checks the modification time, we cannot -;; do that for remote files, so here's a version which relies on diff. -;; CCC: this one probably works for Emacs 21, too. -(defun tramp-vc-workfile-unchanged-p - (filename &optional want-differences-if-changed) - (if (fboundp 'vc-backend-diff) - ;; Old VC. Call `vc-backend-diff'. - (let ((status (funcall (symbol-function 'vc-backend-diff) - filename nil nil - (not want-differences-if-changed)))) - (zerop status)) - ;; New VC. Call `vc-default-workfile-unchanged-p'. - (funcall (symbol-function 'vc-default-workfile-unchanged-p) - (vc-backend filename) filename))) - -(defadvice vc-workfile-unchanged-p - (around tramp-advice-vc-workfile-unchanged-p - (filename &optional want-differences-if-changed) - activate) - "Invoke tramp-vc-workfile-unchanged-p for tramp files." - (if (and (stringp filename) - (tramp-tramp-file-p filename) - (not - (let ((v (tramp-dissect-file-name filename))) - ;; The following check is probably to test whether - ;; file-attributes returns correct last modification - ;; times. This check needs to be changed. - (tramp-get-remote-perl (tramp-file-name-multi-method v) - (tramp-file-name-method v) - (tramp-file-name-user v) - (tramp-file-name-host v))))) - (setq ad-return-value - (tramp-vc-workfile-unchanged-p filename want-differences-if-changed)) - ad-do-it)) - -(add-hook 'tramp-unload-hook - '(lambda () (ad-unadvise 'vc-workfile-unchanged-p))) - - -;; Redefine a function from vc.el -- allow tramp files. -;; `save-match-data' seems not to be required -- it isn't in -;; the original version, either. -;; CCC: this might need some work -- how does the Emacs 21 version -;; work, anyway? Does it work over ange-ftp? Hm. -(if (not (fboundp 'vc-backend-checkout)) - () ;; our replacement won't work and is unnecessary anyway -(defun vc-checkout (filename &optional writable rev) - "Retrieve a copy of the latest version of the given file." - ;; If ftp is on this system and the name matches the ange-ftp format - ;; for a remote file, the user is trying something that won't work. - (funcall (symbol-function 'vc-backend-checkout) filename writable rev) - (vc-resynch-buffer filename t t)) -) - - -;; Do we need to advise the vc-user-login-name function anyway? -;; This will return the correct login name for the owner of a -;; file. It does not deal with the default remote user name... -;; -;; That is, when vc calls (vc-user-login-name), we return the -;; local login name, something that may be different to the remote -;; default. -;; -;; The remote VC operations will occur as the user that we logged -;; in with however - not always the same as the local user. -;; -;; In the end, I did advise the function. This is because, well, -;; the thing didn't work right otherwise ;) -;; -;; Daniel Pittman <daniel@danann.net> - -(defun tramp-handle-vc-user-login-name (&optional uid) - "Return the default user name on the remote machine. -Whenever VC calls this function, `file' is bound to the file name -in question. If no uid is provided or the uid is equal to the uid -owning the file, then we return the user name given in the file name. - -This should only be called when `file' is bound to the -filename we are thinking about..." - ;; Pacify byte-compiler; this symbol is bound in the calling - ;; function. CCC: Maybe it would be better to move the - ;; boundness-checking into this function? - (let* ((file (symbol-value 'file)) - (remote-uid - ;; With Emacs 22, `file-attributes' has got an optional parameter - ;; ID-FORMAT. Handle this case backwards compatible. - (if (and (functionp 'subr-arity) - (= 2 (cdr (funcall (symbol-function 'subr-arity) - (symbol-function 'file-attributes))))) - (nth 2 (file-attributes file 'integer)) - (nth 2 (file-attributes file))))) - (if (and uid (/= uid remote-uid)) - (error "tramp-handle-vc-user-login-name cannot map a uid to a name") - (let* ((v (tramp-dissect-file-name (expand-file-name file))) - (u (tramp-file-name-user v))) - (cond ((stringp u) u) - ((vectorp u) (elt u (1- (length u)))) - ((null u) (user-login-name)) - (t (error "tramp-handle-vc-user-login-name cannot cope!"))))))) - - -;; The following defadvice is no longer necessary after changes in VC -;; on 2006-01-25, Andre. - -;; That means either GNU Emacs >= 22 or the "new vc" package from XEmacs -;; packages collection; as of 2007-09-06, test for availability of -;; `vc-find-version' works for both of those cases. -(unless (fboundp 'vc-find-version) - (defadvice vc-user-login-name - (around tramp-vc-user-login-name activate) - "Support for files on remote machines accessed by TRAMP." - ;; We rely on the fact that `file' is bound when this is called. - ;; This appears to be the case everywhere in vc.el and vc-hooks.el - ;; as of Emacs 20.5. - ;; - ;; With Emacs 22, the definition of `vc-user-login-name' has been - ;; changed. It doesn't need to be adviced any longer. - (let ((file (when (boundp 'file) - (symbol-value 'file)))) ;pacify byte-compiler - (or (and (stringp file) - (tramp-tramp-file-p file) ; tramp file - (setq ad-return-value - (save-match-data - (tramp-handle-vc-user-login-name (ad-get-arg 0))))) ; get the owner name - ad-do-it))) ; else call the original - - (add-hook 'tramp-unload-hook - '(lambda () (ad-unadvise 'vc-user-login-name)))) - - -;; Determine the name of the user owning a file. -(defun tramp-file-owner (filename) - "Return who owns FILE (user name, as a string)." - (let ((v (tramp-dissect-file-name - (expand-file-name filename)))) - (if (not (file-exists-p filename)) - nil ; file cannot be opened - ;; file exists, find out stuff - (save-excursion - (tramp-send-command - (tramp-file-name-multi-method v) (tramp-file-name-method v) - (tramp-file-name-user v) (tramp-file-name-host v) - (format "%s -Lld %s" - (tramp-get-ls-command (tramp-file-name-multi-method v) - (tramp-file-name-method v) - (tramp-file-name-user v) - (tramp-file-name-host v)) - (tramp-shell-quote-argument (tramp-file-name-localname v)))) - (tramp-wait-for-output) - ;; parse `ls -l' output ... - ;; ... file mode flags - (read (current-buffer)) - ;; ... number links - (read (current-buffer)) - ;; ... uid (as a string) - (symbol-name (read (current-buffer))))))) - -;; Wire ourselves into the VC infrastructure... -;; This function does not exist any more in Emacs-21's VC -;; CCC: it appears that no substitute is needed for Emacs 21. -(defadvice vc-file-owner - (around tramp-vc-file-owner activate) - "Support for files on remote machines accessed by TRAMP." - (let ((filename (ad-get-arg 0))) - (or (and (tramp-file-name-p filename) ; tramp file - (setq ad-return-value - (save-match-data - (tramp-file-owner filename)))) ; get the owner name - ad-do-it))) ; else call the original - -(add-hook 'tramp-unload-hook - '(lambda () (ad-unadvise 'vc-file-owner))) - - -;; We need to make the version control software backend version -;; information local to the current buffer. This is because each TRAMP -;; buffer can (theoretically) have a different VC version and I am -;; *way* too lazy to try and push the correct value into each new -;; buffer. -;; -;; Remote VC costs will just have to be paid, at least for the moment. -;; Well, at least, they will right until I feel guilty about doing a -;; botch job here and fix it. :/ -;; -;; Daniel Pittman <daniel@danann.net> -;; CCC: this is probably still needed for Emacs 21. -(defun tramp-vc-setup-for-remote () - "Make the backend release variables buffer local. -This makes remote VC work correctly at the cost of some processing time." - (when (and (buffer-file-name) - (tramp-tramp-file-p (buffer-file-name))) - (make-local-variable 'vc-rcs-release) - (setq vc-rcs-release nil))) - -(add-hook 'find-file-hooks 'tramp-vc-setup-for-remote t) -(add-hook 'tramp-unload-hook - '(lambda () - (remove-hook 'find-file-hooks 'tramp-vc-setup-for-remote))) - -;; No need to load this again if anyone asks. -(provide 'tramp-vc) - -;;; arch-tag: 27cc42ce-da19-468d-ad5c-a2690558db60 -;;; tramp-vc.el ends here diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el index 5a8068320ef..5a65b95b0f8 100644 --- a/lisp/net/tramp.el +++ b/lisp/net/tramp.el @@ -1,12 +1,12 @@ -;;; -*- mode: Emacs-Lisp; coding: iso-2022-7bit; -*- ;;; tramp.el --- Transparent Remote Access, Multiple Protocol +;;; -*- mode: Emacs-Lisp; coding: utf-8; -*- ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, ;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc. ;; (copyright statements below in code to be updated with the above notice) -;; Author: Kai Gro,A_(Bjohann <kai.grossjohann@gmx.net> +;; Author: Kai Großjohann <kai.grossjohann@gmx.net> ;; Michael Albinus <michael.albinus@gmx.de> ;; Keywords: comm, processes @@ -23,9 +23,8 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. ;;; Commentary: @@ -39,10 +38,9 @@ ;; Notes: ;; ----- ;; -;; This package only works for Emacs 20 and higher, and for XEmacs 21 -;; and higher. (XEmacs 20 is missing the `with-timeout' macro. Emacs -;; 19 is reported to have other problems. For XEmacs 21, you need the -;; package `fsf-compat' for the `with-timeout' macro.) +;; This package only works for Emacs 21.1 and higher, and for XEmacs 21.4 +;; and higher. For XEmacs 21, you need the package `fsf-compat' for +;; the `with-timeout' macro.) ;; ;; This version might not work with pre-Emacs 21 VC unless VC is ;; loaded before tramp.el. Could you please test this and tell me about @@ -74,30 +72,36 @@ (when (featurep 'trampver) (unload-feature 'trampver 'force)))) -(if (featurep 'xemacs) - (require 'timer-funcs) - (require 'timer)) +(require 'tramp-compat) +(add-hook 'tramp-unload-hook + '(lambda () + (when (featurep 'tramp-compat) + (unload-feature 'tramp-compat 'force)))) -(require 'format-spec) ;from Gnus 5.8, also in tar ball +(require 'format-spec) ; from Gnus 5.8, also in tar ball ;; As long as password.el is not part of (X)Emacs, it shouldn't ;; be mandatory (if (featurep 'xemacs) (load "password" 'noerror) - (require 'password nil 'noerror)) ;from No Gnus, also in tar ball - -;; The explicit check is not necessary in Emacs, which provides the -;; feature even if implemented in C, but it appears to be necessary -;; in XEmacs. -(unless (and (fboundp 'base64-encode-region) - (fboundp 'base64-decode-region)) - (require 'base64)) ;for the mimencode methods + (or (require 'password-cache nil 'noerror) + (require 'password nil 'noerror))) ; from No Gnus, also in tar ball + (require 'shell) (require 'advice) -;; `copy-tree' is part of subr.el since Emacs 22. -(eval-when-compile - (unless (functionp 'copy-tree) - (require 'cl))) +;; Requiring 'tramp-cache results in an endless loop. +(autoload 'tramp-get-file-property "tramp-cache") +(autoload 'tramp-set-file-property "tramp-cache") +(autoload 'tramp-flush-file-property "tramp-cache") +(autoload 'tramp-flush-directory-property "tramp-cache") +(autoload 'tramp-get-connection-property "tramp-cache") +(autoload 'tramp-set-connection-property "tramp-cache") +(autoload 'tramp-flush-connection-property "tramp-cache") +(autoload 'tramp-parse-connection-properties "tramp-cache") +(add-hook 'tramp-unload-hook + '(lambda () + (when (featurep 'tramp-cache) + (unload-feature 'tramp-cache 'force)))) (autoload 'tramp-uuencode-region "tramp-uu" "Implementation of `uuencode' in Lisp.") @@ -106,80 +110,39 @@ (when (featurep 'tramp-uu) (unload-feature 'tramp-uu 'force)))) -(unless (fboundp 'uudecode-decode-region) - (autoload 'uudecode-decode-region "uudecode")) +(autoload 'uudecode-decode-region "uudecode") -;; XEmacs is distributed with few Lisp packages. Further packages are -;; installed using EFS. If we use a unified filename format, then -;; Tramp is required in addition to EFS. (But why can't Tramp just -;; disable EFS when Tramp is loaded? Then XEmacs can ship with EFS -;; just like before.) Another reason for using a separate filename -;; syntax on XEmacs is that EFS hooks into XEmacs in many places, but -;; Tramp only knows how to deal with `file-name-handler-alist', not -;; the other places. -;;;###autoload -(defvar tramp-unified-filenames (not (featurep 'xemacs)) - "Non-nil means to use unified Ange-FTP/Tramp filename syntax. -Otherwise, use a separate filename syntax for Tramp.") - -;; Load foreign methods. Because they do require Tramp internally, this -;; must be done with the `eval-after-load' trick. - -;; tramp-ftp supports Ange-FTP only. Not suited for XEmacs therefore. -(unless (featurep 'xemacs) - (eval-after-load "tramp" - '(progn - (require 'tramp-ftp) - (add-hook 'tramp-unload-hook - '(lambda () - (when (featurep 'tramp-ftp) - (unload-feature 'tramp-ftp 'force))))))) -(when (and tramp-unified-filenames (featurep 'xemacs)) - (eval-after-load "tramp" - '(progn - (require 'tramp-efs) - (add-hook 'tramp-unload-hook - '(lambda () - (when (featurep 'tramp-efs) - (unload-feature 'tramp-efs 'force))))))) - -;; tramp-smb uses "smbclient" from Samba. -;; Not available under Cygwin and Windows, because they don't offer -;; "smbclient". And even not necessary there, because Emacs supports -;; UNC file names like "//host/share/localname". -(unless (memq system-type '(cygwin windows-nt)) - (eval-after-load "tramp" - '(progn - (require 'tramp-smb) - (add-hook 'tramp-unload-hook - '(lambda () - (when (featurep 'tramp-smb) - (unload-feature 'tramp-smb 'force))))))) +;; The following Tramp packages must be loaded after tramp.el, because +;; they require it as well. +(eval-after-load "tramp" + '(dolist + (feature + (list -(require 'custom) + ;; Tramp interactive commands. + 'tramp-cmds -(unless (boundp 'custom-print-functions) - (defvar custom-print-functions nil)) ; not autoloaded before Emacs 20.4 + ;; Load foreign FTP method. + (if (featurep 'xemacs) 'tramp-efs 'tramp-ftp) -;; Avoid byte-compiler warnings if the byte-compiler supports this. -;; Currently, XEmacs supports this. -(eval-when-compile - (when (featurep 'xemacs) - (let (unused-vars) ; Pacify Emacs byte-compiler - (defalias 'warnings 'identity) ; Pacify Emacs byte-compiler - (byte-compiler-options (warnings (- unused-vars)))))) - -;; `directory-sep-char' is an obsolete variable in Emacs. But it is -;; used in XEmacs, so we set it here and there. The following is needed -;; to pacify Emacs byte-compiler. -(eval-when-compile - (when (boundp 'byte-compile-not-obsolete-var) - (setq byte-compile-not-obsolete-var 'directory-sep-char))) - -;; `set-buffer-multibyte' comes from Emacs Leim. -(eval-and-compile - (unless (fboundp 'set-buffer-multibyte) - (defalias 'set-buffer-multibyte 'ignore))) + ;; tramp-smb uses "smbclient" from Samba. Not available + ;; under Cygwin and Windows, because they don't offer + ;; "smbclient". And even not necessary there, because Emacs + ;; supports UNC file names like "//host/share/localname". + (unless (memq system-type '(cygwin windows-nt)) 'tramp-smb) + + ;; Load foreign FISH method. + 'tramp-fish + + ;; Load gateways. It needs `make-network-process' from Emacs 22. + (when (functionp 'make-network-process) 'tramp-gw))) + + (when feature + (require feature) + (add-hook 'tramp-unload-hook + `(lambda () + (when (featurep ,feature) + (unload-feature ,feature 'force))))))) ;;; User Customizable Internal Variables: @@ -188,16 +151,23 @@ Otherwise, use a separate filename syntax for Tramp.") :group 'files :version "22.1") -(defcustom tramp-verbose 9 - "*Verbosity level for tramp.el. 0 means be silent, 10 is most verbose." +(defcustom tramp-verbose 3 + "*Verbosity level for Tramp. +Any level x includes messages for all levels 1 .. x-1. The levels are + + 0 silent (no tramp messages at all) + 1 errors + 2 warnings + 3 connection to remote hosts (default level) + 4 activities + 5 internal + 6 sent and received strings + 7 file caching + 8 connection properties +10 traces (huge)." :group 'tramp :type 'integer) -(defcustom tramp-debug-buffer nil - "*Whether to send all commands and responses to a debug buffer." - :group 'tramp - :type 'boolean) - ;; Emacs case (eval-and-compile (when (boundp 'backup-directory-alist) @@ -206,7 +176,7 @@ Otherwise, use a separate filename syntax for Tramp.") Each element looks like (REGEXP . DIRECTORY), with the same meaning like in `backup-directory-alist'. If a Tramp file is backed up, and DIRECTORY is a local file name, the backup directory is prepended with Tramp file -name prefix \(multi-method, method, user, host\) of file. +name prefix \(method, user, host\) of file. \(setq tramp-backup-directory-alist backup-directory-alist\) @@ -225,7 +195,7 @@ policy for local files." It has the same meaning like `bkup-backup-directory-info' from package `backup-dir'. If a Tramp file is backed up, and BACKUP-DIR is a local file name, the backup directory is prepended with Tramp file name prefix -\(multi-method, method, user, host\) of file. +\(method, user, host\) of file. \(setq tramp-bkup-backup-directory-info bkup-backup-directory-info\) @@ -245,8 +215,7 @@ policy for local files." "*Put auto-save files in this directory, if set. The idea is to use a local directory so that auto-saving is faster." :group 'tramp - :type '(choice (const nil) - string)) + :type '(choice (const nil) string)) (defcustom tramp-encoding-shell (if (memq system-type '(windows-nt)) @@ -263,9 +232,7 @@ For encoding and deocding, commands like the following are executed: /bin/sh -c COMMAND < INPUT > OUTPUT This variable can be used to change the \"/bin/sh\" part. See the -variable `tramp-encoding-command-switch' for the \"-c\" part. Also, see the -variable `tramp-encoding-reads-stdin' to specify whether the commands read -standard input or a file. +variable `tramp-encoding-command-switch' for the \"-c\" part. Note that this variable is not used for remote commands. There are mechanisms in tramp.el which automatically determine the right shell to @@ -282,310 +249,339 @@ See the variable `tramp-encoding-shell' for more information." :group 'tramp :type 'string) -(defcustom tramp-encoding-reads-stdin t - "*If non-nil, encoding commands read from standard input. -If nil, the filename is the last argument. - -Note that the commands always must write to standard output." +(defcustom tramp-copy-size-limit 10240 + "*The maximum file size where inline copying is preferred over an out-of-the-band copy." :group 'tramp - :type 'boolean) - -(defcustom tramp-multi-sh-program - tramp-encoding-shell - "*Use this program for bootstrapping multi-hop connections. -This variable is similar to `tramp-encoding-shell', but it is only used -when initializing a multi-hop connection. Therefore, the set of -commands sent to this shell is quite restricted, and if you are -careful it works to use CMD.EXE under Windows (instead of a Bourne-ish -shell which does not normally exist on Windows anyway). - -To use multi-hop methods from Windows, you also need suitable entries -in `tramp-multi-connection-function-alist' for the first hop. + :type 'integer) -This variable defaults to the value of `tramp-encoding-shell'." +(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 '(file :must-match t)) + :type 'string) -;; CCC I have changed all occurrences of comint-quote-filename with -;; tramp-shell-quote-argument, except in tramp-handle-expand-many-files. -;; There, comint-quote-filename was removed altogether. If it turns -;; out to be necessary there, something will need to be done. -;;-(defcustom tramp-file-name-quote-list -;;- '(?] ?[ ?\| ?& ?< ?> ?\( ?\) ?\; ?\ ?\* ?\? ?\! ?\" ?\' ?\` ?# ?\@ ?\+ ) -;;- "*Protect these characters from the remote shell. -;;-Any character in this list is quoted (preceded with a backslash) -;;-because it means something special to the shell. This takes effect -;;-when sending file and directory names to the remote shell. -;;- -;;-See `comint-file-name-quote-list' for details." -;;- :group 'tramp -;;- :type '(repeat character)) - -(defcustom tramp-methods - '( ("rcp" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "rsh") - (tramp-copy-program "rcp") - (tramp-remote-sh "/bin/sh") - (tramp-login-args nil) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("scp" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program "scp") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("scp1" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program "scp") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-1" "-e" "none")) - (tramp-copy-args ("-1")) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("scp2" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program "scp") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-2" "-e" "none")) - (tramp-copy-args ("-2")) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("scp1_old" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh1") - (tramp-copy-program "scp1") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("scp2_old" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh2") - (tramp-copy-program "scp2") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("rsync" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program "rsync") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none")) - (tramp-copy-args ("-e" "ssh")) - (tramp-copy-keep-date-arg "-t") - (tramp-password-end-of-line nil)) - ("remcp" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "remsh") - (tramp-copy-program "rcp") - (tramp-remote-sh "/bin/sh") - (tramp-login-args nil) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("rsh" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "rsh") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args nil) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("ssh" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("ssh1" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-1" "-e" "none")) - (tramp-copy-args ("-1")) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("ssh2" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-2" "-e" "none")) - (tramp-copy-args ("-2")) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("ssh1_old" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh1") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("ssh2_old" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh2") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("remsh" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "remsh") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args nil) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("telnet" - (tramp-connection-function tramp-open-connection-telnet) - (tramp-login-program "telnet") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args nil) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("su" (tramp-connection-function tramp-open-connection-su) - (tramp-login-program "su") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-" "%u")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("sudo" (tramp-connection-function tramp-open-connection-su) - (tramp-login-program "sudo") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-u" "%u" "-s" - "-p" "Password:")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("multi" (tramp-connection-function tramp-open-connection-multi) - (tramp-login-program nil) - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args nil) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("scpc" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program "scp") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-o" "ControlPath=%t.%%r@%%h:%%p" - "-o" "ControlMaster=yes" - "-e" "none")) - (tramp-copy-args ("-o" "ControlPath=%t.%%r@%%h:%%p" - "-o" "ControlMaster=auto")) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("scpx" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program "scp") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none" "-t" "-t" "/bin/sh")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ("sshx" (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "ssh") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-e" "none" "-t" "-t" "/bin/sh")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("krlogin" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "krlogin") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-x")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line nil)) - ("plink" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "plink") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-ssh")) ;optionally add "-v" - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line "xy")) ;see docstring for "xy" - ("plink1" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "plink") - (tramp-copy-program nil) - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-1" "-ssh")) ;optionally add "-v" - (tramp-copy-args nil) - (tramp-copy-keep-date-arg nil) - (tramp-password-end-of-line "xy")) ;see docstring for "xy" - ("pscp" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "plink") - (tramp-copy-program "pscp") - (tramp-remote-sh "/bin/sh") - (tramp-login-args ("-ssh")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line "xy")) ;see docstring for "xy" - ("fcp" - (tramp-connection-function tramp-open-connection-rsh) - (tramp-login-program "fsh") - (tramp-copy-program "fcp") - (tramp-remote-sh "/bin/sh -i") - (tramp-login-args ("sh" "-i")) - (tramp-copy-args nil) - (tramp-copy-keep-date-arg "-p") - (tramp-password-end-of-line nil)) - ) +(defvar 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 (("-p" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil)) + ("scp" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-P" "%p") ("-p" "%k") ("-q"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("scp1" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-1" "-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-1") ("-P" "%p") ("-p" "%k") + ("-q"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("scp2" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-2" "-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-2") ("-P" "%p") ("-p" "%k") + ("-q"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("scp1_old" + (tramp-login-program "ssh1") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp1") + (tramp-copy-args (("-p" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil)) + ("scp2_old" + (tramp-login-program "ssh2") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp2") + (tramp-copy-args (("-p" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil)) + ("sftp" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "sftp") + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("rsync" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "rsync") + (tramp-copy-args (("-e" "ssh") ("-t" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil)) + ("remcp" (tramp-login-program "remsh") + (tramp-login-args (("%h") ("-l" "%u"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "rcp") + (tramp-copy-args (("-p" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil)) + ("rsh" (tramp-login-program "rsh") + (tramp-login-args (("%h") ("-l" "%u"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("ssh" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("ssh1" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-1" "-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("ssh2" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-2" "-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("ssh1_old" + (tramp-login-program "ssh1") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("ssh2_old" + (tramp-login-program "ssh2") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("remsh" (tramp-login-program "remsh") + (tramp-login-args (("%h") ("-l" "%u"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("telnet" + (tramp-login-program "telnet") + (tramp-login-args (("%h") ("%p"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil) + (tramp-default-port 23)) + ("su" (tramp-login-program "su") + (tramp-login-args (("-") ("%u"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("sudo" (tramp-login-program "sudo") + (tramp-login-args (("-u" "%u") + ("-s" "-p" "Password:"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("scpc" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-o" "ControlPath=%t.%%r@%%h:%%p") + ("-o" "ControlMaster=yes") + ("-e" "none"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-P" "%p") ("-p" "%k") ("-q") + ("-o" "ControlPath=%t.%%r@%%h:%%p") + ("-o" "ControlMaster=auto"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("scpx" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none" "-t" "-t" "/bin/sh"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "scp") + (tramp-copy-args (("-p" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("sshx" (tramp-login-program "ssh") + (tramp-login-args (("%h") ("-l" "%u") ("-p" "%p") + ("-e" "none" "-t" "-t" "/bin/sh"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil) + (tramp-gw-args (("-o" + "GlobalKnownHostsFile=/dev/null") + ("-o" "UserKnownHostsFile=/dev/null") + ("-o" "StrictHostKeyChecking=no"))) + (tramp-default-port 22)) + ("krlogin" + (tramp-login-program "krlogin") + (tramp-login-args (("%h") ("-l" "%u") ("-x"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("plink" (tramp-login-program "plink") + (tramp-login-args (("%h") ("-l" "%u") ("-P" "%p") + ("-ssh"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line "xy") ;see docstring for "xy" + (tramp-default-port 22)) + ("plink1" + (tramp-login-program "plink") + (tramp-login-args (("%h") ("-l" "%u") ("-P" "%p") + ("-1" "-ssh"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line "xy") ;see docstring for "xy" + (tramp-default-port 22)) + ("plinkx" + (tramp-login-program "plink") + (tramp-login-args (("-load" "%h") ("-t") + (,(format + "env 'TERM=%s' 'PROMPT_COMMAND=' 'PS1=$ '" + tramp-terminal-type)) + ("/bin/sh"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program nil) + (tramp-copy-args nil) + (tramp-copy-keep-date nil) + (tramp-password-end-of-line nil)) + ("pscp" (tramp-login-program "plink") + (tramp-login-args (("%h") ("-l" "%u") ("-P" "%p") + ("-ssh"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "pscp") + (tramp-copy-args (("-scp") ("-p" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line "xy") ;see docstring for "xy" + (tramp-default-port 22)) + ("psftp" (tramp-login-program "plink") + (tramp-login-args (("%h") ("-l" "%u") ("-P" "%p") + ("-ssh"))) + (tramp-remote-sh "/bin/sh") + (tramp-copy-program "pscp") + (tramp-copy-args (("-psftp") ("-p" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line "xy")) ;see docstring for "xy" + ("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 (("-p" "%k"))) + (tramp-copy-keep-date t) + (tramp-password-end-of-line nil))) "*Alist of methods for remote files. This is a list of entries of the form (NAME PARAM1 PARAM2 ...). Each NAME stands for a remote access method. Each PARAM is a pair of the form (KEY VALUE). The following KEYs are defined: - * `tramp-connection-function' - This specifies the function to use to connect to the remote host. - Currently, `tramp-open-connection-rsh', `tramp-open-connection-telnet' - and `tramp-open-connection-su' are defined. See the documentation - of these functions for more details. * `tramp-remote-sh' This specifies the Bourne shell to use on the remote host. This MUST be a Bourne-like shell. It is normally not necessary to set - this to any value other than \"/bin/sh\": tramp wants to use a shell + this to any value other than \"/bin/sh\": Tramp wants to use a shell which groks tilde expansion, but it can search for it. Also note that \"/bin/sh\" exists on all Unixen, this might not be true for the value that you decide to use. You Have Been Warned. * `tramp-login-program' This specifies the name of the program to use for logging in to the - remote host. Depending on `tramp-connection-function', this may be - the name of rsh or a workalike program (when - `tramp-connection-function' is `tramp-open-connection-rsh'), or the - name of telnet or a workalike (for `tramp-open-connection-telnet'), - or the name of su or a workalike (for `tramp-open-connection-su'). + remote host. This may be the name of rsh or a workalike program, + or the name of telnet or a workalike, or the name of su or a workalike. * `tramp-login-args' This specifies the list of arguments to pass to the above - mentioned program. Please note that this is a list of arguments, + mentioned program. Please note that this is a list of list of arguments, that is, normally you don't want to put \"-a -b\" or \"-f foo\" - here. Instead, you want two list elements, one for \"-a\" and one - for \"-b\", or one for \"-f\" and one for \"foo\". - If `tramp-connection-function' is `tramp-open-connection-su', then - \"%u\" in this list is replaced by the user name, and \"%%\" can - be used to obtain a literal percent character. - \"%t\" is replaced by the temporary file name for `scp'-like methods. + here. Instead, you want a list (\"-a\" \"-b\"), or (\"-f\" \"foo\"). + There are some patterns: \"%h\" in this list is replaced by the host + name, \"%u\" is replaced by the user name, \"%p\" is replaced by the + port number, and \"%%\" can be used to obtain a literal percent character. + If a list containing \"%h\", \"%u\" or \"%p\" is unchanged during + expansion (i.e. no host or no user specified), this list is not used as + argument. By this, arguments like (\"-l\" \"%u\") are optional. + \"%t\" is replaced by the temporary file name produced with + `tramp-make-tramp-temp-file'. \"%k\" indicates the keep-date + parameter of a program, if exists. * `tramp-copy-program' This specifies the name of the program to use for remotely copying the file; this might be the absolute filename of rcp or the name of @@ -593,10 +589,16 @@ pair of the form (KEY VALUE). The following KEYs are defined: * `tramp-copy-args' This specifies the list of parameters to pass to the above mentioned program, the hints for `tramp-login-args' also apply here. - * `tramp-copy-keep-date-arg' - This specifies the parameter to use for the copying program when the - timestamp of the original file should be kept. For `rcp', use `-p', for - `rsync', use `-t'. + * `tramp-copy-keep-date' + This specifies whether the copying program when the preserves the + timestamp of the original file. + * `tramp-default-port' + The default port of a method is needed in case of gateway connections. + Additionally, it is used as indication which method is prepared for + passing gateways. + * `tramp-gw-args' + As the attribute name says, additional arguments are specified here + when a method is applied via a gateway. * `tramp-password-end-of-line' This specifies the string to use for terminating the line after submitting the password. If this method parameter is nil, then the @@ -618,78 +620,22 @@ file is passed through the same buffer used by `tramp-login-program'. In this case, the file contents need to be protected since the `tramp-login-program' might use escape codes or the connection might not be eight-bit clean. Therefore, file contents are encoded for transit. -See the variable `tramp-coding-commands' for details. +See the variables `tramp-local-coding-commands' and +`tramp-remote-coding-commands' for details. So, to summarize: if the method is an out-of-band method, then you must specify `tramp-copy-program' and `tramp-copy-args'. If it is an -inline method, then these two parameters should be nil. Every method, -inline or out of band, must specify `tramp-connection-function' plus -the associated arguments (for example, the login program if you chose -`tramp-open-connection-telnet'). +inline method, then these two parameters should be nil. Methods which +are fit for gateways must have `tramp-default-port' at least. Notes: -When using `tramp-open-connection-su' the phrase `open connection to a -remote host' sounds strange, but it is used nevertheless, for -consistency. No connection is opened to a remote host, but `su' is -started on the local host. You are not allowed to specify a remote -host other than `localhost' or the name of the local host." - :group 'tramp - :type '(repeat - (cons string - (set (list (const tramp-connection-function) function) - (list (const tramp-login-program) - (choice (const nil) string)) - (list (const tramp-copy-program) - (choice (const nil) string)) - (list (const tramp-remote-sh) - (choice (const nil) string)) - (list (const tramp-login-args) (repeat string)) - (list (const tramp-copy-args) (repeat string)) - (list (const tramp-copy-keep-date-arg) - (choice (const nil) string)) - (list (const tramp-encoding-command) - (choice (const nil) string)) - (list (const tramp-decoding-command) - (choice (const nil) string)) - (list (const tramp-encoding-function) - (choice (const nil) function)) - (list (const tramp-decoding-function) - (choice (const nil) function)) - (list (const tramp-password-end-of-line) - (choice (const nil) string)))))) - -(defcustom tramp-multi-methods '("multi" "multiu") - "*List of multi-hop methods. -Each entry in this list should be a method name as mentioned in the -variable `tramp-methods'." - :group 'tramp - :type '(repeat string)) - -(defcustom tramp-multi-connection-function-alist - '(("telnet" tramp-multi-connect-telnet "telnet %h%n") - ("rsh" tramp-multi-connect-rlogin "rsh %h -l %u%n") - ("remsh" tramp-multi-connect-rlogin "remsh %h -l %u%n") - ("ssh" tramp-multi-connect-rlogin "ssh %h -l %u%n") - ("ssht" tramp-multi-connect-rlogin "ssh %h -e none -t -t -l %u%n") - ("su" tramp-multi-connect-su "su - %u%n") - ("sudo" tramp-multi-connect-su "sudo -u %u -s -p Password:%n")) - "*List of connection functions for multi-hop methods. -Each list item is a list of three items (METHOD FUNCTION COMMAND), -where METHOD is the name as used in the file name, FUNCTION is the -function to be executed, and COMMAND is the shell command used for -connecting. - -COMMAND may contain percent escapes. `%u' will be replaced with the -user name, `%h' will be replaced with the host name, and `%n' will be -replaced with an end-of-line character, as specified in the variable -`tramp-rsh-end-of-line'. Use `%%' for a literal percent character. -Note that the interpretation of the percent escapes also depends on -the FUNCTION. For example, the `%u' escape is forbidden with the -function `tramp-multi-connect-telnet'. See the documentation of the -various functions for details." - :group 'tramp - :type '(repeat (list string function string))) +When using `su' or `sudo' the phrase `open connection to a remote +host' sounds strange, but it is used nevertheless, for consistency. +No connection is opened to a remote host, but `su' or `sudo' is +started on the local host. You should specify a remote host +`localhost' or the name of the local host. Another host name is +useful only in combination with `tramp-default-proxies-alist'.") (defcustom tramp-default-method ;; An external copy method seems to be preferred, because it is much @@ -701,30 +647,26 @@ various functions for details." ;; another good choice because of the "ControlMaster" option, but ;; this is a more modern alternative in OpenSSH 4, which cannot be ;; taken as default. - (let ((e-f (fboundp 'executable-find))) - (cond - ;; PuTTY is installed. - ((and e-f (funcall 'executable-find "pscp")) - (if (or (fboundp 'password-read) - ;; Pageant is running. - (and (fboundp 'w32-window-exists-p) - (funcall 'w32-window-exists-p "Pageant" "Pageant"))) - "pscp" - "plink")) - ;; There is an ssh installation. - ((and e-f (funcall 'executable-find "scp")) - (if (or (fboundp 'password-read) - ;; ssh-agent is running. - (getenv "SSH_AUTH_SOCK") - (getenv "SSH_AGENT_PID")) - "scp" - "ssh")) - ;; Under Emacs 20, `executable-find' does not exists. So we - ;; couldn't check whether there is an ssh implementation. Let's - ;; hope the best. - ((not e-f) "ssh") - ;; Fallback. - (t "ftp"))) + (cond + ;; PuTTY is installed. + ((executable-find "pscp") + (if (or (fboundp 'password-read) + ;; Pageant is running. + (and (fboundp 'w32-window-exists-p) + (funcall (symbol-function 'w32-window-exists-p) + "Pageant" "Pageant"))) + "pscp" + "plink")) + ;; There is an ssh installation. + ((executable-find "scp") + (if (or (fboundp 'password-read) + ;; ssh-agent is running. + (getenv "SSH_AUTH_SOCK") + (getenv "SSH_AGENT_PID")) + "scp" + "ssh")) + ;; Fallback. + (t "ftp")) "*Default method to use for transferring files. See `tramp-methods' for possibilities. Also see `tramp-default-method-alist'." @@ -733,7 +675,7 @@ Also see `tramp-default-method-alist'." (defcustom tramp-default-method-alist '(("\\`localhost\\'" "\\`root\\'" "su")) - "*Default method to use for specific user/host pairs. + "*Default method to use for specific host/user pairs. This is an alist of items (HOST USER METHOD). The first matching item specifies the method to use for a file name which does not specify a method. HOST and USER are regular expressions or nil, which is @@ -749,42 +691,90 @@ See `tramp-methods' for a list of possibilities for METHOD." (regexp :tag "User regexp") (string :tag "Method")))) -;; Default values for non-Unices seeked +(defcustom tramp-default-user + nil + "*Default user to use for transferring files. +It is nil by default; otherwise settings in configuration files like +\"~/.ssh/config\" would be overwritten. Also see `tramp-default-user-alist'. + +This variable is regarded as obsolete, and will be removed soon." + :group 'tramp + :type '(choice (const nil) string)) + +(defcustom tramp-default-user-alist + `(("\\`su\\(do\\)?\\'" nil "root") + ("\\`r\\(em\\)?\\(cp\\|sh\\)\\|telnet\\|plink1?\\'" + nil ,(user-login-name))) + "*Default user to use for specific method/host pairs. +This is an alist of items (METHOD HOST USER). The first matching item +specifies the user to use for a file name which does not specify a +user. METHOD and USER are regular expressions or nil, which is +interpreted as a regular expression which always matches. If no entry +matches, the variable `tramp-default-user' takes effect. + +If the file name does not specify the method, lookup is done using the +empty string for the method name." + :group 'tramp + :type '(repeat (list (regexp :tag "Method regexp") + (regexp :tag "Host regexp") + (string :tag "User")))) + +(defcustom tramp-default-host + (system-name) + "*Default host to use for transferring files. +Useful for su and sudo methods mostly." + :group 'tramp + :type 'string) + +(defcustom tramp-default-proxies-alist nil + "*Route to be followed for specific host/user pairs. +This is an alist of items (HOST USER PROXY). The first matching +item specifies the proxy to be passed for a file name located on +a remote target matching USER@HOST. HOST and USER are regular +expressions or nil, which is interpreted as a regular expression +which always matches. PROXY must be a Tramp filename without a +localname part. Method and user name on PROXY are optional, +which is interpreted with the default values. PROXY can contain +the patterns %h and %u, which are replaced by the strings +matching HOST or USER, respectively." + :group 'tramp + :type '(repeat (list (regexp :tag "Host regexp") + (regexp :tag "User regexp") + (string :tag "Proxy remote name")))) + (defconst tramp-completion-function-alist-rsh - (unless (memq system-type '(windows-nt)) - '((tramp-parse-rhosts "/etc/hosts.equiv") - (tramp-parse-rhosts "~/.rhosts"))) + '((tramp-parse-rhosts "/etc/hosts.equiv") + (tramp-parse-rhosts "~/.rhosts")) "Default list of (FUNCTION FILE) pairs to be examined for rsh methods.") -;; Default values for non-Unices seeked (defconst tramp-completion-function-alist-ssh - (unless (memq system-type '(windows-nt)) - '((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"))) + '((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.") -;; Default values for non-Unices seeked (defconst tramp-completion-function-alist-telnet - (unless (memq system-type '(windows-nt)) - '((tramp-parse-hosts "/etc/hosts"))) + '((tramp-parse-hosts "/etc/hosts")) "Default list of (FUNCTION FILE) pairs to be examined for telnet methods.") -;; Default values for non-Unices seeked (defconst tramp-completion-function-alist-su - (unless (memq system-type '(windows-nt)) - '((tramp-parse-passwd "/etc/passwd"))) + '((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.") + (defvar tramp-completion-function-alist nil "*Alist of methods for remote files. This is a list of entries of the form (NAME PAIR1 PAIR2 ...). @@ -800,6 +790,7 @@ names from FILE for completion. The following predefined FUNCTIONs exists: * `tramp-parse-hosts' for \"/etc/hosts\" like files, * `tramp-parse-passwd' for \"/etc/passwd\" like files. * `tramp-parse-netrc' for \"~/.netrc\" like files. + * `tramp-parse-putty' for PuTTY registry keys. FUNCTION can also be a customer defined function. For more details see the info pages.") @@ -843,8 +834,6 @@ the info pages.") (tramp-set-completion-function "sudo" tramp-completion-function-alist-su) (tramp-set-completion-function - "multi" nil) - (tramp-set-completion-function "scpx" tramp-completion-function-alist-ssh) (tramp-set-completion-function "sshx" tramp-completion-function-alist-ssh) @@ -855,10 +844,26 @@ the info pages.") (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))) +(defconst tramp-echo-mark "_echo\b\b\b\b\b" + "String mark to be transmitted around shell commands. +Used to separate their echo from the output they produce. This +will only be used if we cannot disable remote echo via stty. +This string must have no effect on the remote shell except for +producing some echo which can later be detected by +`tramp-echoed-echo-mark-regexp'. Using some characters followed +by an equal number of backspaces to erase them will usually +suffice.") + +(defconst tramp-echoed-echo-mark-regexp "_echo\\(\b\\( \b\\)?\\)\\{5\\}" + "Regexp which matches `tramp-echo-mark' as it gets echoed by +the remote shell.") + (defcustom tramp-rsh-end-of-line "\n" "*String used for end of line in rsh connections. I don't think this ever needs to be changed, so please tell me about it @@ -883,17 +888,45 @@ The default value is to use the same value as `tramp-rsh-end-of-line'." :group 'tramp :type 'string) +;; "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 ":"! (defcustom tramp-remote-path - ;; "/usr/xpg4/bin" has been placed first, because on Solaris a POSIX - ;; compatible "id" is needed. - '("/usr/xpg4/bin" "/bin" "/usr/bin" "/usr/sbin" "/usr/local/bin" - "/usr/ccs/bin" "/local/bin" "/local/freeware/bin" "/local/gnu/bin" + '(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. -Please notify me about other semi-standard directories to include here. +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." +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." + :group 'tramp + :type '(repeat (choice + (const :tag "Default Directories" tramp-default-remote-path) + (string :tag "Directory")))) + +(defcustom tramp-remote-process-environment + `("HISTFILE=$HOME/.tramp_history" "HISTSIZE=1" "LC_CTYPE=C" "LC_TIME=C" + ,(concat "TERM=" tramp-terminal-type) + "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)) @@ -920,7 +953,7 @@ which should work well in many cases." :type 'regexp) (defcustom tramp-password-prompt-regexp - "^.*\\([pP]assword\\|passphrase\\).*:\^@? *" + "^.*\\([pP]assword\\|[pP]assphrase\\).*:\^@? *" "*Regexp matching password-like prompts. The regexp should match at end of buffer. @@ -931,14 +964,16 @@ The `sudo' program appears to insert a `^@' character into the prompt." (defcustom tramp-wrong-passwd-regexp (concat "^.*" ;; These strings should be on the last line - (regexp-opt '("Permission denied." + (regexp-opt '("Permission denied" "Login incorrect" "Login Incorrect" "Connection refused" "Connection closed" "Sorry, try again." "Name or service not known" - "Host key verification failed.") t) + "Host key verification failed." + "No supported authentication methods left to try!" + "Tramp connection closed") t) ".*" "\\|" "^.*\\(" @@ -1011,7 +1046,7 @@ be ignored safely." In fact this expression is empty by intention, it will be used only to check regularly the status of the associated process. The answer will be provided by `tramp-action-process-alive', -`tramp-multi-action-process-alive' and`tramp-action-out-of-band', which see." +`tramp-action-out-of-band', which see." :group 'tramp :type 'regexp) @@ -1019,18 +1054,12 @@ The answer will be provided by `tramp-action-process-alive', "*Prefix to use for temporary files. If this is a relative file name (such as \"tramp.\"), it is considered relative to the directory name returned by the function -`tramp-temporary-file-directory' (which see). It may also be an +`tramp-compat-temporary-file-directory' (which see). It may also be an absolute file name; don't forget to include a prefix for the filename part, though." :group 'tramp :type 'string) -(defcustom tramp-discard-garbage nil - "*If non-nil, try to discard garbage sent by remote shell. -Some shells send such garbage upon connection setup." - :group 'tramp - :type 'boolean) - (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 @@ -1047,144 +1076,139 @@ shell from reading its init file." '(alist :key-type string :value-type string) '(repeat (cons string string)))) -(defcustom tramp-prefix-format - (if tramp-unified-filenames "/" "/[") - "*String matching the very beginning of tramp file names. -Used in `tramp-make-tramp-file-name' and `tramp-make-tramp-multi-file-name'." - :group 'tramp - :type 'string) +;; XEmacs is distributed with few Lisp packages. Further packages are +;; installed using EFS. If we use a unified filename format, then +;; Tramp is required in addition to EFS. (But why can't Tramp just +;; disable EFS when Tramp is loaded? Then XEmacs can ship with EFS +;; just like before.) Another reason for using a separate filename +;; syntax on XEmacs is that EFS hooks into XEmacs in many places, but +;; Tramp only knows how to deal with `file-name-handler-alist', not +;; the other places. -(defcustom tramp-prefix-regexp - (concat "^" (regexp-quote tramp-prefix-format)) - "*Regexp matching the very beginning of tramp file names. -Should always start with \"^\". Derived from `tramp-prefix-format'." - :group 'tramp - :type 'regexp) +;; Currently, we have the choice between 'ftp, 'sep, and 'url. +;;;###autoload +(defcustom tramp-syntax + (if (featurep 'xemacs) 'sep 'ftp) + "Tramp filename syntax to be used. -(defcustom tramp-method-regexp - "[a-zA-Z_0-9-]+" - "*Regexp matching methods identifiers." - :group 'tramp - :type 'regexp) +It can have the following values: -;; It is a little bit annoying that in XEmacs case this delimeter is different -;; for single-hop and multi-hop cases. -(defcustom tramp-postfix-single-method-format - (if tramp-unified-filenames ":" "/") - "*String matching delimeter between method and user or host names. -Applicable for single-hop methods. -Used in `tramp-make-tramp-file-name'." + 'ftp -- Ange-FTP respective EFS like syntax (GNU Emacs default) + 'sep -- Syntax as defined for XEmacs (not available yet for GNU Emacs) + 'url -- URL-like syntax." :group 'tramp - :type 'string) + :type (if (featurep 'xemacs) + '(choice (const :tag "EFS" ftp) + (const :tag "XEmacs" sep) + (const :tag "URL" url)) + '(choice (const :tag "Ange-FTP" ftp) + (const :tag "URL" url)))) + +(defconst tramp-prefix-format + (cond ((equal tramp-syntax 'ftp) "/") + ((equal tramp-syntax 'sep) "/[") + ((equal tramp-syntax 'url) "/") + (t (error "Wrong `tramp-syntax' defined"))) + "*String matching the very beginning of Tramp file names. +Used in `tramp-make-tramp-file-name'.") + +(defconst tramp-prefix-regexp + (concat "^" (regexp-quote tramp-prefix-format)) + "*Regexp matching the very beginning of Tramp file names. +Should always start with \"^\". Derived from `tramp-prefix-format'.") -(defcustom tramp-postfix-single-method-regexp - (regexp-quote tramp-postfix-single-method-format) - "*Regexp matching delimeter between method and user or host names. -Applicable for single-hop methods. -Derived from `tramp-postfix-single-method-format'." - :group 'tramp - :type 'regexp) +(defconst tramp-method-regexp + "[a-zA-Z_0-9-]+" + "*Regexp matching methods identifiers.") -(defcustom tramp-postfix-multi-method-format - ":" +(defconst tramp-postfix-method-format + (cond ((equal tramp-syntax 'ftp) ":") + ((equal tramp-syntax 'sep) "/") + ((equal tramp-syntax 'url) "://") + (t (error "Wrong `tramp-syntax' defined"))) "*String matching delimeter between method and user or host names. -Applicable for multi-hop methods. -Used in `tramp-make-tramp-multi-file-name'." - :group 'tramp - :type 'string) +Used in `tramp-make-tramp-file-name'.") -(defcustom tramp-postfix-multi-method-regexp - (regexp-quote tramp-postfix-multi-method-format) +(defconst tramp-postfix-method-regexp + (regexp-quote tramp-postfix-method-format) "*Regexp matching delimeter between method and user or host names. -Applicable for multi-hop methods. -Derived from `tramp-postfix-multi-method-format'." - :group 'tramp - :type 'regexp) +Derived from `tramp-postfix-method-format'.") -(defcustom tramp-postfix-multi-hop-format - (if tramp-unified-filenames ":" "/") - "*String matching delimeter between host and next method. -Applicable for multi-hop methods. -Used in `tramp-make-tramp-multi-file-name'." - :group 'tramp - :type 'string) - -(defcustom tramp-postfix-multi-hop-regexp - (regexp-quote tramp-postfix-multi-hop-format) - "*Regexp matching delimeter between host and next method. -Applicable for multi-hop methods. -Derived from `tramp-postfix-multi-hop-format'." - :group 'tramp - :type 'regexp) +(defconst tramp-user-regexp + "[^:/ \t]+" + "*Regexp matching user names.") -(defcustom tramp-user-regexp - "[^:/ \t]*" - "*Regexp matching user names." - :group 'tramp - :type 'regexp) - -(defcustom tramp-postfix-user-format +(defconst tramp-postfix-user-format "@" "*String matching delimeter between user and host names. -Used in `tramp-make-tramp-file-name' and `tramp-make-tramp-multi-file-name'." - :group 'tramp - :type 'string) +Used in `tramp-make-tramp-file-name'.") -(defcustom tramp-postfix-user-regexp +(defconst tramp-postfix-user-regexp (regexp-quote tramp-postfix-user-format) "*Regexp matching delimeter between user and host names. -Derived from `tramp-postfix-user-format'." - :group 'tramp - :type 'regexp) - -(defcustom tramp-host-regexp - "[a-zA-Z0-9_.-]*" - "*Regexp matching host names." - :group 'tramp - :type 'regexp) - -(defcustom tramp-host-with-port-regexp - "[a-zA-Z0-9_.#-]*" - "*Regexp matching host names." - :group 'tramp - :type 'regexp) - -(defcustom tramp-postfix-host-format - (if tramp-unified-filenames ":" "]") +Derived from `tramp-postfix-user-format'.") + +(defconst tramp-host-regexp + "[a-zA-Z0-9_.-]+" + "*Regexp matching host names.") + +(defconst tramp-prefix-port-format + (cond ((equal tramp-syntax 'ftp) "#") + ((equal tramp-syntax 'sep) "#") + ((equal tramp-syntax 'url) ":") + (t (error "Wrong `tramp-syntax' defined"))) + "*String matching delimeter between host names and port numbers.") + +(defconst tramp-prefix-port-regexp + (regexp-quote tramp-prefix-port-format) + "*Regexp matching delimeter between host names and port numbers. +Derived from `tramp-prefix-port-format'.") + +(defconst tramp-port-regexp + "[0-9]+" + "*Regexp matching port numbers.") + +(defconst tramp-host-with-port-regexp + (concat "\\(" tramp-host-regexp "\\)" + tramp-prefix-port-regexp + "\\(" tramp-port-regexp "\\)") + "*Regexp matching host names with port numbers.") + +(defconst tramp-postfix-host-format + (cond ((equal tramp-syntax 'ftp) ":") + ((equal tramp-syntax 'sep) "]") + ((equal tramp-syntax 'url) "") + (t (error "Wrong `tramp-syntax' defined"))) "*String matching delimeter between host names and localnames. -Used in `tramp-make-tramp-file-name' and `tramp-make-tramp-multi-file-name'." - :group 'tramp - :type 'string) +Used in `tramp-make-tramp-file-name'.") -(defcustom tramp-postfix-host-regexp +(defconst tramp-postfix-host-regexp (regexp-quote tramp-postfix-host-format) "*Regexp matching delimeter between host names and localnames. -Derived from `tramp-postfix-host-format'." - :group 'tramp - :type 'regexp) +Derived from `tramp-postfix-host-format'.") -(defcustom tramp-localname-regexp +(defconst tramp-localname-regexp ".*$" - "*Regexp matching localnames." - :group 'tramp - :type 'regexp) + "*Regexp matching localnames.") ;; File name format. -(defcustom tramp-file-name-structure +(defconst tramp-file-name-structure (list (concat tramp-prefix-regexp - "\\(" "\\(" tramp-method-regexp "\\)" tramp-postfix-single-method-regexp "\\)?" - "\\(" "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp "\\)?" - "\\(" tramp-host-with-port-regexp "\\)" tramp-postfix-host-regexp - "\\(" tramp-localname-regexp "\\)") - 2 4 5 6) + "\\(" "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp "\\)?" + "\\(" "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp "\\)?" + "\\(" tramp-host-regexp + "\\(" tramp-prefix-port-regexp tramp-port-regexp "\\)?" "\\)?" + tramp-postfix-host-regexp + "\\(" tramp-localname-regexp "\\)") + 2 4 5 7) "*List of five elements (REGEXP METHOD USER HOST FILE), detailing \ -the tramp file name structure. +the Tramp file name structure. -The first element REGEXP is a regular expression matching a tramp file +The first element REGEXP is a regular expression matching a Tramp file name. The regex should contain parentheses around the method name, the user name, the host name, and the file name parts. @@ -1195,192 +1219,97 @@ but for the host name. The fifth element FILE is for the file name. These numbers are passed directly to `match-string', which see. That means the opening parentheses are counted to identify the pair. -See also `tramp-file-name-regexp'." - :group 'tramp - :type '(list (regexp :tag "File name regexp") - (integer :tag "Paren pair for method name") - (integer :tag "Paren pair for user name ") - (integer :tag "Paren pair for host name ") - (integer :tag "Paren pair for file name "))) +See also `tramp-file-name-regexp'.") ;;;###autoload (defconst tramp-file-name-regexp-unified "\\`/[^/:]+:" "Value for `tramp-file-name-regexp' for unified remoting. Emacs (not XEmacs) uses a unified filename syntax for Ange-FTP and -Tramp. See `tramp-file-name-structure-unified' for more explanations.") +Tramp. See `tramp-file-name-structure' for more explanations.") ;;;###autoload (defconst tramp-file-name-regexp-separate "\\`/\\[.*\\]" "Value for `tramp-file-name-regexp' for separate remoting. XEmacs uses a separate filename syntax for Tramp and EFS. -See `tramp-file-name-structure-separate' for more explanations.") +See `tramp-file-name-structure' for more explanations.") + +;;;###autoload +(defconst tramp-file-name-regexp-url + "\\`/[^/:]+://" + "Value for `tramp-file-name-regexp' for URL-like remoting. +See `tramp-file-name-structure' for more explanations.") ;;;###autoload -(defcustom tramp-file-name-regexp - (if tramp-unified-filenames - tramp-file-name-regexp-unified - tramp-file-name-regexp-separate) - "*Regular expression matching file names handled by tramp. -This regexp should match tramp file names but no other file names. +(defconst tramp-file-name-regexp + (cond ((equal tramp-syntax 'ftp) tramp-file-name-regexp-unified) + ((equal tramp-syntax 'sep) tramp-file-name-regexp-separate) + ((equal tramp-syntax 'url) tramp-file-name-regexp-url) + (t (error "Wrong `tramp-syntax' defined"))) + "*Regular expression matching file names handled by Tramp. +This regexp should match Tramp file names but no other file names. \(When tramp.el is loaded, this regular expression is prepended to `file-name-handler-alist', and that is searched sequentially. Thus, -if the tramp entry appears rather early in the `file-name-handler-alist' -and is a bit too general, then some files might be considered tramp -files which are not really tramp files. +if the Tramp entry appears rather early in the `file-name-handler-alist' +and is a bit too general, then some files might be considered Tramp +files which are not really Tramp files. Please note that the entry in `file-name-handler-alist' is made when this file (tramp.el) is loaded. This means that this variable must be set before loading tramp.el. Alternatively, `file-name-handler-alist' can be updated after changing this variable. -Also see `tramp-file-name-structure'." - :group 'tramp - :type 'regexp) +Also see `tramp-file-name-structure'.") ;;;###autoload (defconst tramp-completion-file-name-regexp-unified - "^/$\\|^/[^/:][^/]*$" + (if (memq system-type '(cygwin windows-nt)) + "^\\([a-zA-Z]:\\)?/$\\|^\\([a-zA-Z]:\\)?/[^/:][^/]*$" + "^/$\\|^/[^/:][^/]*$") "Value for `tramp-completion-file-name-regexp' for unified remoting. Emacs (not XEmacs) uses a unified filename syntax for Ange-FTP and -Tramp. See `tramp-file-name-structure-unified' for more explanations.") +Tramp. See `tramp-file-name-structure' for more explanations.") ;;;###autoload (defconst tramp-completion-file-name-regexp-separate - "^/\\([[][^]]*\\)?$" + (if (memq system-type '(cygwin windows-nt)) + "^\\([a-zA-Z]:\\)?/\\([[][^]]*\\)?$" + "^/\\([[][^]]*\\)?$") "Value for `tramp-completion-file-name-regexp' for separate remoting. XEmacs uses a separate filename syntax for Tramp and EFS. -See `tramp-file-name-structure-separate' for more explanations.") +See `tramp-file-name-structure' for more explanations.") ;;;###autoload -(defcustom tramp-completion-file-name-regexp - (if tramp-unified-filenames - tramp-completion-file-name-regexp-unified - tramp-completion-file-name-regexp-separate) - "*Regular expression matching file names handled by tramp completion. -This regexp should match partial tramp file names only. +(defconst tramp-completion-file-name-regexp-url + (if (memq system-type '(cygwin windows-nt)) + "^\\([a-zA-Z]:\\)?/$\\|^\\([a-zA-Z]:\\)?/[^/:]+\\(:\\(/\\(/[^/]*\\)?\\)?\\)?$" + "^/$\\|^/[^/:]+\\(:\\(/\\(/[^/]*\\)?\\)?\\)?$") + "Value for `tramp-completion-file-name-regexp' for URL-like remoting. +See `tramp-file-name-structure' for more explanations.") + +;;;###autoload +(defconst tramp-completion-file-name-regexp + (cond ((equal tramp-syntax 'ftp) tramp-completion-file-name-regexp-unified) + ((equal tramp-syntax 'sep) tramp-completion-file-name-regexp-separate) + ((equal tramp-syntax 'url) tramp-completion-file-name-regexp-url) + (t (error "Wrong `tramp-syntax' defined"))) + "*Regular expression matching file names handled by Tramp completion. +This regexp should match partial Tramp file names only. Please note that the entry in `file-name-handler-alist' is made when this file (tramp.el) is loaded. This means that this variable must be set before loading tramp.el. Alternatively, `file-name-handler-alist' can be updated after changing this variable. -Also see `tramp-file-name-structure'." - :group 'tramp - :type 'regexp) - -(defcustom tramp-multi-file-name-structure - (list - (concat - tramp-prefix-regexp - "\\(" "\\(" tramp-method-regexp "\\)" "\\)?" - "\\(" "\\(" tramp-postfix-multi-hop-regexp "%s" "\\)+" "\\)?" - tramp-postfix-host-regexp "\\(" tramp-localname-regexp "\\)") - 2 3 -1) - "*Describes the file name structure of `multi' files. -Multi files allow you to contact a remote host in several hops. -This is a list of four elements (REGEXP METHOD HOP LOCALNAME). - -The first element, REGEXP, gives a regular expression to match against -the file name. In this regular expression, `%s' is replaced with the -value of `tramp-multi-file-name-hop-structure'. (Note: in order to -allow multiple hops, you normally want to use something like -\"\\\\(\\\\(%s\\\\)+\\\\)\" in the regular expression. The outer pair -of parentheses is used for the HOP element, see below.) - -All remaining elements are numbers. METHOD gives the number of the -paren pair which matches the method name. HOP gives the number of the -paren pair which matches the hop sequence. LOCALNAME gives the number of -the paren pair which matches the localname (pathname) on the remote host. - -LOCALNAME can also be negative, which means to count from the end. Ie, a -value of -1 means the last paren pair. - -I think it would be good if the regexp matches the whole of the -string, but I haven't actually tried what happens if it doesn't..." - :group 'tramp - :type '(list (regexp :tag "File name regexp") - (integer :tag "Paren pair for method name") - (integer :tag "Paren pair for hops") - (integer :tag "Paren pair to match localname"))) - -(defcustom tramp-multi-file-name-hop-structure - (list - (concat - "\\(" tramp-method-regexp "\\)" tramp-postfix-multi-method-regexp - "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp - "\\(" tramp-host-with-port-regexp "\\)") - 1 2 3) - "*Describes the structure of a hop in multi files. -This is a list of four elements (REGEXP METHOD USER HOST). First -element REGEXP is used to match against the hop. Pair number METHOD -matches the method of one hop, pair number USER matches the user of -one hop, pair number HOST matches the host of one hop. - -This regular expression should match exactly all of one hop." - :group 'tramp - :type '(list (regexp :tag "Hop regexp") - (integer :tag "Paren pair for method name") - (integer :tag "Paren pair for user name") - (integer :tag "Paren pair for host name"))) - -(defcustom tramp-make-multi-tramp-file-format - (list - (concat tramp-prefix-format "%m") - (concat tramp-postfix-multi-hop-format - "%m" tramp-postfix-multi-method-format - "%u" tramp-postfix-user-format - "%h") - (concat tramp-postfix-host-format "%p")) - "*Describes how to construct a `multi' file name. -This is a list of three elements PREFIX, HOP and LOCALNAME. - -The first element PREFIX says how to construct the prefix, the second -element HOP specifies what each hop looks like, and the final element -LOCALNAME says how to construct the localname (pathname). +Also see `tramp-file-name-structure'.") -In PREFIX, `%%' means `%' and `%m' means the method name. - -In HOP, `%%' means `%' and `%m', `%u', `%h' mean the hop method, hop -user and hop host, respectively. - -In LOCALNAME, `%%' means `%' and `%p' means the localname. - -The resulting file name always contains one copy of PREFIX and one -copy of LOCALNAME, but there is one copy of HOP for each hop in the file -name. - -Note: the current implementation requires the prefix to contain the -method name, followed by all the hops, and the localname must come -last." - :group 'tramp - :type '(list string string string)) - -(defcustom tramp-terminal-type "dumb" - "*Value of TERM environment variable for logging in to remote host. -Because Tramp wants to parse the output of the remote shell, it is easily -confused by ANSI color escape sequences and suchlike. Often, shell init -files conditionalize this setup based on the TERM environment variable." - :group 'tramp - :type 'string) - -(defcustom tramp-completion-without-shell-p nil - "*If nil, use shell wildcards for completion, else rely on Lisp only. -Using shell wildcards for completions has the advantage that it can be -fast even in large directories, but completion is always -case-sensitive. Relying on Lisp only means that case-insensitive -completion is possible (subject to the variable `completion-ignore-case'), -but it might be slow on large directories." - :group 'tramp - :type 'boolean) - -(defcustom tramp-actions-before-shell - '((tramp-password-prompt-regexp tramp-action-password) - (tramp-login-prompt-regexp tramp-action-login) +(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-wrong-passwd-regexp tramp-action-permission-denied) (tramp-yesno-prompt-regexp tramp-action-yesno) (tramp-yn-prompt-regexp tramp-action-yn) (tramp-terminal-prompt-regexp tramp-action-terminal) @@ -1395,51 +1324,19 @@ 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." - :group 'tramp - :type '(repeat (list variable function))) +corresponding PATTERN matches, the ACTION function is called.") -(defcustom tramp-actions-copy-out-of-band +(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-copy-failed) + (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." - :group 'tramp - :type '(repeat (list variable function))) - -(defcustom tramp-multi-actions - '((tramp-password-prompt-regexp tramp-multi-action-password) - (tramp-login-prompt-regexp tramp-multi-action-login) - (shell-prompt-pattern tramp-multi-action-succeed) - (tramp-shell-prompt-pattern tramp-multi-action-succeed) - (tramp-wrong-passwd-regexp tramp-multi-action-permission-denied) - (tramp-process-alive-regexp tramp-multi-action-process-alive)) - "List of pattern/action pairs. -This list is used for each hop in multi-hop connections. -See `tramp-actions-before-shell' for more info." - :group 'tramp - :type '(repeat (list variable function))) - -(defcustom tramp-initial-commands - '("unset HISTORY" - "unset correct" - "unset autocorrect") - "List of commands to send to the first remote shell that we see. -These commands will be sent to any shell, and thus they should be -designed to work in such circumstances. Also, restrict the commands -to the bare necessity for getting the remote shell into a state -where it is possible to execute the Bourne-ish shell. - -At the moment, the command to execute the Bourne-ish shell uses strange -quoting which `tcsh' tries to correct, so we send the command \"unset -autocorrect\" to the remote host." - :group 'tramp - :type '(repeat string)) -;; Chunked sending kluge. We set this to 500 for black-listed constellations +See `tramp-actions-before-shell' for more info.") + +;; Chunked sending kludge. We set this to 500 for black-listed constellations ;; known to have a bug in `process-send-string'; some ssh connections appear ;; to drop bytes when data is sent too quickly. There is also a connection ;; buffer local variable, which is computed depending on remote host properties @@ -1495,16 +1392,16 @@ You will see the number of bytes sent successfully to the remote host. If that number exceeds 1000, you can stop the execution by hitting C-g, because your Emacs is likely clean. -If your Emacs is buggy, the code stops and gives you an indication -about the value `tramp-chunksize' should be set. Maybe you could just -experiment a bit, e.g. changing the values of `init' and `step' -in the third line of the code. - When it is necessary to set `tramp-chunksize', you might consider to use an out-of-the-band method (like \"scp\") instead of an internal one \(like \"ssh\"), because setting `tramp-chunksize' to non-nil decreases performance. +If your Emacs is buggy, the code stops and gives you an indication +about the value `tramp-chunksize' should be set. Maybe you could just +experiment a bit, e.g. changing the values of `init' and `step' +in the third line of the code. + Please raise a bug report via \"M-x tramp-bug\" if your system needs this variable to be set as well." :group 'tramp @@ -1521,146 +1418,39 @@ opening a connection to a remote host." :group 'tramp :type '(choice (const nil) (const t) (const pty))) -;;; Internal Variables: +(defcustom tramp-completion-reread-directory-timeout 10 + "Defines seconds since last remote command before rereading a directory. +A remote directory might have changed its contents. In order to +make it visible during file name completion in the minibuffer, +Tramp flushes its cache and rereads the directory contents when +more than `tramp-completion-reread-directory-timeout' seconds +have been gone since last remote command execution. A value of 0 +would require an immediate reread during filename completion, nil +means to use always cached values for the directory contents." + :group 'tramp + :type '(choice (const nil) integer)) -(defvar tramp-buffer-file-attributes nil - "Holds the `ls -ild' output for the current buffer. -This variable is local to each buffer. It is not used if the remote -machine groks Perl. If it is used, it's used as an emulation for -the visited file modtime.") -(make-variable-buffer-local 'tramp-buffer-file-attributes) - -(defvar tramp-md5-function - (cond ((and (require 'md5) (fboundp 'md5)) 'md5) - ((fboundp 'md5-encode) - (lambda (x) (base64-encode-string - (funcall (symbol-function 'md5-encode) x)))) - (t (error "Couldn't find an `md5' function"))) - "Function to call for running the MD5 algorithm.") +;;; Internal Variables: (defvar tramp-end-of-output - (concat "///" - (funcall tramp-md5-function - (concat - (prin1-to-string process-environment) - (current-time-string) -;; (prin1-to-string -;; (if (fboundp 'directory-files-and-attributes) -;; (funcall 'directory-files-and-attributes -;; (or (getenv "HOME") -;; (tramp-temporary-file-directory))) -;; (mapcar -;; (lambda (x) -;; (cons x (file-attributes x))) -;; (directory-files (or (getenv "HOME") -;; (tramp-temporary-file-directory)) -;; t)))) - ))) + (concat + "///" (md5 (concat + (prin1-to-string process-environment) (current-time-string)))) "String used to recognize end of output.") -(defvar tramp-connection-function nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-remote-sh nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-login-program nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-login-args nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-copy-program nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-copy-args nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-copy-keep-date-arg nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-encoding-command nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-decoding-command nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-encoding-function nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-decoding-function nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -(defvar tramp-password-end-of-line nil - "This internal variable holds a parameter for `tramp-methods'. -In the connection buffer, this variable has the value of the like-named -method parameter, as specified in `tramp-methods' (which see).") - -;; CCC `local in each buffer'? -(defvar tramp-ls-command nil - "This command is used to get a long listing with numeric user and group ids. -This variable is automatically made buffer-local to each rsh process buffer -upon opening the connection.") - -(defvar tramp-current-multi-method nil - "Name of `multi' connection method for this *tramp* buffer, or nil if not multi. -This variable is automatically made buffer-local to each rsh process buffer -upon opening the connection.") - (defvar tramp-current-method nil - "Connection method for this *tramp* buffer. -This variable is automatically made buffer-local to each rsh process buffer -upon opening the connection.") + "Connection method for this *tramp* buffer.") (defvar tramp-current-user nil - "Remote login name for this *tramp* buffer. -This variable is automatically made buffer-local to each rsh process buffer -upon opening the connection.") + "Remote login name for this *tramp* buffer.") (defvar tramp-current-host nil - "Remote host for this *tramp* buffer. -This variable is automatically made buffer-local to each rsh process buffer -upon opening the connection.") - -(defvar tramp-test-groks-nt nil - "Whether the `test' command groks the `-nt' switch. -\(`test A -nt B' tests if file A is newer than file B.) -This variable is automatically made buffer-local to each rsh process buffer -upon opening the connection.") - -(defvar tramp-file-exists-command nil - "Command to use for checking if a file exists. -This variable is automatically made buffer-local to each rsh process buffer -upon opening the connection.") - -(defconst tramp-uudecode "\ -tramp_uudecode () { -\(echo begin 600 /tmp/tramp.$$; tail +2) | uudecode + "Remote host for this *tramp* buffer.") + +(defconst tramp-uudecode + "(echo begin 600 /tmp/tramp.$$; tail +2) | uudecode cat /tmp/tramp.$$ -rm -f /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 @@ -1672,7 +1462,8 @@ we have this shell function.") ;; end. ;; The device number is returned as "-1", because there will be a virtual ;; device number set in `tramp-handle-file-attributes' -(defconst tramp-perl-file-attributes "\ +(defconst tramp-perl-file-attributes + "%s -e ' @stat = lstat($ARGV[0]); if (($stat[2] & 0170000) == 0120000) { @@ -1690,7 +1481,7 @@ else $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 %u t (%u . %u) -1)\\n\", + \"(%%s %%u %%s %%s (%%u %%u) (%%u %%u) (%%u %%u) %%u.0 %%u t (%%u . %%u) -1)\\n\", $type, $stat[3], $uid, @@ -1705,11 +1496,14 @@ printf( $stat[2], $stat[1] >> 16 & 0xffff, $stat[1] & 0xffff -);" +);' \"$1\" \"$2\" \"$3\" 2>/dev/null" "Perl script to produce output suitable for use with `file-attributes' -on the remote file system.") +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 "\ +(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); @@ -1736,7 +1530,7 @@ for($i = 0; $i < $n; $i++) $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 %u t (%u . %u) (%u %u))\\n\", + \"(\\\"%%s\\\" %%s %%u %%s %%s (%%u %%u) (%%u %%u) (%%u %%u) %%u.0 %%u t (%%u . %%u) (%%u %%u))\\n\", $filename, $type, $stat[3], @@ -1755,9 +1549,11 @@ for($i = 0; $i < $n; $i++) $stat[0] >> 16 & 0xffff, $stat[0] & 0xffff); } -printf(\")\\n\");" +printf(\")\\n\");' \"$1\" \"$2\" \"$3\" 2>/dev/null" "Perl script implementing `directory-files-attributes' as Lisp `read'able -output.") +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 uu encoding. ;; (defvar tramp-perl-encode "%s -e'\ @@ -1780,23 +1576,23 @@ output.") ;; Escape sequence %s is replaced with name of Perl binary.") ;; These two use base64 encoding. -(defvar tramp-perl-encode-with-module - "perl -MMIME::Base64 -0777 -ne 'print encode_base64($_)'" +(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.") -(defvar tramp-perl-decode-with-module - "perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)'" +(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.") -(defvar tramp-perl-encode +(defconst tramp-perl-encode "%s -e ' # This script contributed by Juanma Barranquero <lektu@terra.es>. # Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 @@ -1833,13 +1629,12 @@ while (my $data = <STDIN>) { (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.") -(defvar tramp-perl-decode +(defconst tramp-perl-decode "%s -e ' # This script contributed by Juanma Barranquero <lektu@terra.es>. # Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 @@ -1879,68 +1674,35 @@ while (my $data = <STDIN>) { ((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-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) +(defconst tramp-file-mode-type-map + '((0 . "-") ; Normal file (SVID-v2 and XPG2) + (1 . "p") ; fifo + (2 . "c") ; character device + (3 . "m") ; multiplexed character device (v7) + (4 . "d") ; directory + (5 . "?") ; Named special file (XENIX) + (6 . "b") ; block device + (7 . "?") ; multiplexed block device (v7) + (8 . "-") ; regular file + (9 . "n") ; network special file (HP-UX) + (10 . "l") ; symlink + (11 . "?") ; ACL shadow inode (Solaris, not userspace) + (12 . "s") ; socket + (13 . "D") ; door special (Solaris) + (14 . "w")) ; whiteout (BSD) "A list of file types returned from the `stat' system call. This is used to map a mode number to a permission string.") -(defvar tramp-dos-coding-system - (if (and (fboundp 'coding-system-p) - (funcall 'coding-system-p '(dos))) - 'dos - 'undecided-dos) - "Some Emacsen know the `dos' coding system, others need `undecided-dos'.") - -(defvar tramp-last-cmd nil - "Internal Tramp variable recording the last command sent. -This variable is buffer-local in every buffer.") -(make-variable-buffer-local 'tramp-last-cmd) - -(defvar tramp-process-echoes nil - "Whether to process echoes from the remote shell.") - -(defvar tramp-last-cmd-time nil - "Internal Tramp variable recording the time when the last cmd was sent. -This variable is buffer-local in every buffer.") -(make-variable-buffer-local 'tramp-last-cmd-time) - -;; This variable does not have the right value in XEmacs. What should -;; I use instead of find-operation-coding-system in XEmacs? -(defvar tramp-feature-write-region-fix - (when (fboundp 'find-operation-coding-system) - (let ((file-coding-system-alist '(("test" emacs-mule)))) - (funcall (symbol-function 'find-operation-coding-system) - 'write-region 0 0 "" nil "test"))) - "Internal variable to say if `write-region' chooses the right coding. -Older versions of Emacs chose the coding system for `write-region' based -on the FILENAME argument, even if VISIT was a string.") - ;; New handlers should be added here. The following operations can be ;; handled using the normal primitives: file-name-as-directory, -;; file-name-directory, file-name-nondirectory, ;; file-name-sans-versions, get-file-buffer. (defconst tramp-file-name-handler-alist - '( - (load . tramp-handle-load) + '((load . tramp-handle-load) (make-symbolic-link . tramp-handle-make-symbolic-link) (file-name-directory . tramp-handle-file-name-directory) (file-name-nondirectory . tramp-handle-file-name-nondirectory) @@ -1948,7 +1710,6 @@ on the FILENAME argument, even if VISIT was a string.") (file-exists-p . tramp-handle-file-exists-p) (file-directory-p . tramp-handle-file-directory-p) (file-executable-p . tramp-handle-file-executable-p) - (file-accessible-directory-p . tramp-handle-file-accessible-directory-p) (file-readable-p . tramp-handle-file-readable-p) (file-regular-p . tramp-handle-file-regular-p) (file-symlink-p . tramp-handle-file-symlink-p) @@ -1965,23 +1726,29 @@ on the FILENAME argument, even if VISIT was a string.") (copy-file . tramp-handle-copy-file) (rename-file . tramp-handle-rename-file) (set-file-modes . tramp-handle-set-file-modes) + (set-file-times . tramp-handle-set-file-times) (make-directory . tramp-handle-make-directory) (delete-directory . tramp-handle-delete-directory) (delete-file . tramp-handle-delete-file) (directory-file-name . tramp-handle-directory-file-name) - (shell-command . tramp-handle-shell-command) + ;; `executable-find' is not official yet. + (executable-find . tramp-handle-executable-find) + (start-file-process . tramp-handle-start-file-process) (process-file . tramp-handle-process-file) + (shell-command . tramp-handle-shell-command) (insert-directory . tramp-handle-insert-directory) (expand-file-name . tramp-handle-expand-file-name) + (substitute-in-file-name . tramp-handle-substitute-in-file-name) (file-local-copy . tramp-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-handle-insert-file-contents-literally) (write-region . tramp-handle-write-region) (find-backup-file-name . tramp-handle-find-backup-file-name) (make-auto-save-file-name . tramp-handle-make-auto-save-file-name) (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory) (dired-compress-file . tramp-handle-dired-compress-file) - (dired-call-process . tramp-handle-dired-call-process) (dired-recursive-delete-directory . tramp-handle-dired-recursive-delete-directory) (set-visited-file-modtime . tramp-handle-set-visited-file-modtime) @@ -1989,7 +1756,7 @@ on the FILENAME argument, even if VISIT was a string.") "Alist of handler functions. Operations not mentioned here will be handled by the normal Emacs functions.") -;; Handlers for partial tramp file names. For Emacs just +;; Handlers for partial Tramp file names. For Emacs just ;; `file-name-all-completions' is needed. ;;;###autoload (defconst tramp-completion-file-name-handler-alist @@ -2009,48 +1776,117 @@ normal Emacs functions.") If (FUNCTION FILENAME) returns non-nil, then all I/O on that file is done by calling HANDLER.") -;;; Internal functions which must come first. +;;; Internal functions which must come first: -(defsubst tramp-message (level fmt-string &rest args) +(defsubst tramp-debug-message (vec fmt-string &rest args) + "Append message to debug buffer. +Message is formatted with FMT-STRING as control string and the remaining +ARGS to actually emit the message (if applicable)." + (when (get-buffer (tramp-buffer-name vec)) + (with-current-buffer (tramp-get-debug-buffer vec) + (goto-char (point-max)) + (unless (bolp) + (insert "\n")) + ;; Timestamp + (insert (format-time-string "%T ")) + ;; Calling function + (let ((btn 1) btf fn) + (while (not fn) + (setq btf (nth 1 (backtrace-frame btn))) + (if (not btf) + (setq fn "") + (when (symbolp btf) + (setq fn (symbol-name btf)) + (unless (and (string-match "^tramp" fn) + (not (string-match + "^tramp\\(-debug\\)?\\(-message\\|-error\\)$" + fn))) + (setq fn nil))) + (setq btn (1+ btn)))) + ;; The following code inserts filename and line number. + ;; Should be deactivated by default, because it is time + ;; consuming. +; (let ((ffn (find-function-noselect (intern fn)))) +; (insert +; (format +; "%s:%d: " +; (file-name-nondirectory (buffer-file-name (car ffn))) +; (with-current-buffer (car ffn) +; (1+ (count-lines (point-min) (cdr ffn))))))) + (insert (format "%s " fn))) + ;; The message + (insert (apply 'format fmt-string args))))) + +(defsubst tramp-message (vec-or-proc level fmt-string &rest args) "Emit a message depending on verbosity level. -First arg LEVEL says to be quiet if `tramp-verbose' is less than LEVEL. The -message is emitted only if `tramp-verbose' is greater than or equal to LEVEL. -Calls function `message' with FMT-STRING as control string and the remaining -ARGS to actually emit the message (if applicable). - -This function expects to be called from the tramp buffer only!" - (when (<= level tramp-verbose) - (apply #'message (concat "tramp: " fmt-string) args) - (when tramp-debug-buffer - (save-excursion - (set-buffer - (tramp-get-debug-buffer - tramp-current-multi-method tramp-current-method - tramp-current-user tramp-current-host)) - (goto-char (point-max)) - (unless (bolp) - (insert "\n")) - (tramp-insert-with-face - 'italic - (concat "# " (apply #'format fmt-string args) "\n")))))) - -(defun tramp-message-for-buffer - (multi-method method user host level fmt-string &rest args) - "Like `tramp-message' but temporarily switches to the tramp buffer. -First three args METHOD, USER, and HOST identify the tramp buffer to use, -remaining args passed to `tramp-message'." - (save-excursion - (set-buffer (tramp-get-buffer multi-method method user host)) - (apply 'tramp-message level fmt-string args))) +VEC-OR-PROC identifies the Tramp buffer to use. It can be either a +vector or a process. LEVEL says to be quiet if `tramp-verbose' is +less than LEVEL. The message is emitted only if `tramp-verbose' is +greater than or equal to LEVEL. + +The message is also logged into the debug buffer when `tramp-verbose' +is greater than or equal 4. + +Calls functions `message' and `tramp-debug-message' with FMT-STRING as +control string and the remaining ARGS to actually emit the message (if +applicable)." + (condition-case nil + (when (<= level tramp-verbose) + ;; Match data must be preserved! + (save-match-data + ;; Display only when there is a minimum level. + (when (<= level 3) + (apply 'message + (concat + (cond + ((= level 0) "") + ((= level 1) "") + ((= level 2) "Warning: ") + (t "Tramp: ")) + fmt-string) + args)) + ;; Log only when there is a minimum level. + (when (>= tramp-verbose 4) + (when (and vec-or-proc + (processp vec-or-proc) + (buffer-name (process-buffer vec-or-proc))) + (with-current-buffer (process-buffer vec-or-proc) + ;; Translate proc to vec. + (setq vec-or-proc (tramp-dissect-file-name default-directory)))) + (when (and vec-or-proc (vectorp vec-or-proc)) + (apply 'tramp-debug-message + vec-or-proc + (concat (format "(%d) # " level) fmt-string) + args))))) + ;; Suppress all errors. + (error nil))) -(defsubst tramp-line-end-position nil - "Return point at end of line. -Calls `line-end-position' or `point-at-eol' if defined, else -own implementation." - (cond - ((fboundp 'line-end-position) (funcall (symbol-function 'line-end-position))) - ((fboundp 'point-at-eol) (funcall (symbol-function 'point-at-eol))) - (t (save-excursion (end-of-line) (point))))) +(defsubst tramp-error (vec-or-proc signal fmt-string &rest args) + "Emit an error. +VEC-OR-PROC identifies the connection to use, SIGNAL is the +signal identifier to be raised, remaining args passed to +`tramp-message'. Finally, signal SIGNAL is raised." + (tramp-message + vec-or-proc 1 "%s" + (error-message-string + (list signal (get signal 'error-message) (apply 'format fmt-string args)))) + (signal signal (list (apply 'format fmt-string args)))) + +(defsubst tramp-error-with-buffer + (buffer vec-or-proc signal fmt-string &rest args) + "Emit an error, and show BUFFER. +If BUFFER is nil, show the connection buffer. Wait for 30\", or until +an input event arrives. The other arguments are passed to `tramp-error'." + (save-window-excursion + (unwind-protect + (apply 'tramp-error vec-or-proc signal fmt-string args) + (when (and vec-or-proc (not (zerop tramp-verbose))) + (let ((enable-recursive-minibuffers t)) + (pop-to-buffer + (or (and (bufferp buffer) buffer) + (and (processp vec-or-proc) (process-buffer vec-or-proc)) + (tramp-get-buffer vec-or-proc))) + (sit-for 30)))))) (defmacro with-parsed-tramp-file-name (filename var &rest body) "Parse a Tramp filename and make components available in the body. @@ -2059,18 +1895,15 @@ First arg FILENAME is evaluated and dissected into its components. Second arg VAR is a symbol. It is used as a variable name to hold the filename structure. It is also used as a prefix for the variables holding the components. For example, if VAR is the symbol `foo', then -`foo' will be bound to the whole structure, `foo-multi-method' will -be bound to the multi-method component, and so on for `foo-method', -`foo-user', `foo-host', `foo-localname'. +`foo' will be bound to the whole structure, `foo-method' will be bound to +the method component, and so on for `foo-user', `foo-host', `foo-localname'. Remaining args are Lisp expressions to be evaluated (inside an implicit `progn'). -If VAR is nil, then we bind `v' to the structure and `multi-method', -`method', `user', `host', `localname' to the components." +If VAR is nil, then we bind `v' to the structure and `method', `user', +`host', `localname' to the components." `(let* ((,(or var 'v) (tramp-dissect-file-name ,filename)) - (,(if var (intern (concat (symbol-name var) "-multi-method")) 'multi-method) - (tramp-file-name-multi-method ,(or var 'v))) (,(if var (intern (concat (symbol-name var) "-method")) 'method) (tramp-file-name-method ,(or var 'v))) (,(if var (intern (concat (symbol-name var) "-user")) 'user) @@ -2082,14 +1915,41 @@ If VAR is nil, then we bind `v' to the structure and `multi-method', ,@body)) (put 'with-parsed-tramp-file-name 'lisp-indent-function 2) -;; Enable debugging. -(eval-and-compile - (when (featurep 'edebug) - (def-edebug-spec with-parsed-tramp-file-name (form symbolp body)))) -;; Highlight as keyword. -(when (functionp 'font-lock-add-keywords) - (funcall 'font-lock-add-keywords - 'emacs-lisp-mode '("\\<with-parsed-tramp-file-name\\>"))) +(put 'with-parsed-tramp-file-name 'edebug-form-spec '(form symbolp body)) +(font-lock-add-keywords 'emacs-lisp-mode '("\\<with-parsed-tramp-file-name\\>")) + +(defmacro with-file-property (vec file property &rest body) + "Check in Tramp cache for PROPERTY, otherwise execute BODY and set cache. +FILE must be a local file name on a connection identified via VEC." + `(if (file-name-absolute-p ,file) + (let ((value (tramp-get-file-property ,vec ,file ,property 'undef))) + (when (eq value 'undef) + ;; We cannot pass @body as parameter to + ;; `tramp-set-file-property' because it mangles our + ;; debug messages. + (setq value (progn ,@body)) + (tramp-set-file-property ,vec ,file ,property value)) + value) + ,@body)) + +(put 'with-file-property 'lisp-indent-function 3) +(put 'with-file-property 'edebug-form-spec t) +(font-lock-add-keywords 'emacs-lisp-mode '("\\<with-file-property\\>")) + +(defmacro with-connection-property (key property &rest body) + "Checks in Tramp for property PROPERTY, otherwise executes BODY and set." + `(let ((value (tramp-get-connection-property ,key ,property 'undef))) + (when (eq value 'undef) + ;; We cannot pass ,@body as parameter to + ;; `tramp-set-connection-property' because it mangles our debug + ;; messages. + (setq value (progn ,@body)) + (tramp-set-connection-property ,key ,property value)) + value)) + +(put 'with-connection-property 'lisp-indent-function 2) +(put 'with-connection-property 'edebug-form-spec t) +(font-lock-add-keywords 'emacs-lisp-mode '("\\<with-connection-property\\>")) (defmacro tramp-let-maybe (variable value &rest body) "Let-bind VARIABLE to VALUE in BODY, but only if VARIABLE is not obsolete. @@ -2102,6 +1962,33 @@ The intent is to protect against `obsolete variable' warnings." (put 'tramp-let-maybe 'lisp-indent-function 2) (put 'tramp-let-maybe 'edebug-form-spec t) +(defsubst tramp-make-tramp-temp-file (vec) + "Create a temporary file on the remote host identified by VEC. +Return the local name of the temporary file." + (let ((prefix + (tramp-make-tramp-file-name + (tramp-file-name-method vec) + (tramp-file-name-user vec) + (tramp-file-name-host vec) + (expand-file-name + tramp-temp-name-prefix (tramp-get-remote-tmpdir vec)))) + result) + (while (not result) + ;; `make-temp-file' would be the natural choice for + ;; implementation. But it calls `write-region' internally, + ;; which also needs a temporary file - we would end in an + ;; infinite loop. + (setq result (make-temp-name prefix)) + (if (file-exists-p result) + (setq result nil) + ;; This creates the file by side effect. + (set-file-times result) + (set-file-modes result (tramp-octal-to-decimal "0700")))) + + ;; Return the local part. + (with-parsed-tramp-file-name result nil localname))) + + ;;; Config Manipulation Functions: (defun tramp-set-completion-function (method function-list) @@ -2127,12 +2014,19 @@ Example: tramp-completion-function-alist)) (while v - ;; Remove double entries + ;; Remove double entries. (when (member (car v) (cdr v)) (setcdr v (delete (car v) (cdr v)))) - ;; Check for function and file + ;; Check for function and file or registry key. (unless (and (functionp (nth 0 (car v))) - (file-exists-p (nth 1 (car v)))) + (if (string-match "^HKEY_CURRENT_USER" (nth 1 (car v))) + ;; Windows registry. + (and (memq system-type '(cygwin windows-nt)) + (zerop + (tramp-local-call-process + "reg" nil nil nil "query" (nth 1 (car v))))) + ;; Configuration file. + (file-exists-p (nth 1 (car v))))) (setq r (delete (car v) r))) (setq v (cdr v))) @@ -2141,15 +2035,69 @@ Example: (cons method r))))) (defun tramp-get-completion-function (method) - "Returns list of completion functions for METHOD. + "Returns a list of completion functions for METHOD. For definition of that list see `tramp-set-completion-function'." - (cdr (assoc method tramp-completion-function-alist))) + (cons + ;; Hosts visited once shall be remembered. + `(tramp-parse-connection-properties ,method) + ;; The method related defaults. + (cdr (assoc method tramp-completion-function-alist)))) + + +;;; Fontification of `read-file-name': + +;; rfn-eshadow.el is part of Emacs 22. It is autoloaded. +(defvar tramp-rfn-eshadow-overlay) +(make-variable-buffer-local 'tramp-rfn-eshadow-overlay) + +(defun tramp-rfn-eshadow-setup-minibuffer () + "Set up a minibuffer for `file-name-shadow-mode'. +Adds another overlay hiding filename parts according to Tramp's +special handling of `substitute-in-file-name'." + (when (symbol-value 'minibuffer-completing-file-name) + (setq tramp-rfn-eshadow-overlay + (funcall (symbol-function 'make-overlay) + (funcall (symbol-function 'minibuffer-prompt-end)) + (funcall (symbol-function 'minibuffer-prompt-end)))) + ;; Copy rfn-eshadow-overlay properties. + (let ((props (funcall (symbol-function 'overlay-properties) + (symbol-value 'rfn-eshadow-overlay)))) + (while props + (funcall (symbol-function 'overlay-put) + tramp-rfn-eshadow-overlay (pop props) (pop props)))))) + +(when (boundp 'rfn-eshadow-setup-minibuffer-hook) + (add-hook 'rfn-eshadow-setup-minibuffer-hook + 'tramp-rfn-eshadow-setup-minibuffer)) + +(defun tramp-rfn-eshadow-update-overlay () + "Update `rfn-eshadow-overlay' to cover shadowed part of minibuffer input. +This is intended to be used as a minibuffer `post-command-hook' for +`file-name-shadow-mode'; the minibuffer should have already +been set up by `rfn-eshadow-setup-minibuffer'." + ;; In remote files name, there is a shadowing just for the local part. + (let ((end (or (funcall (symbol-function 'overlay-end) + (symbol-value 'rfn-eshadow-overlay)) + (funcall (symbol-function 'minibuffer-prompt-end))))) + (when (file-remote-p (buffer-substring-no-properties end (point-max))) + (save-excursion + (save-restriction + (narrow-to-region + (1+ (or (string-match "/" (buffer-string) end) end)) (point-max)) + (let ((rfn-eshadow-overlay tramp-rfn-eshadow-overlay) + (rfn-eshadow-update-overlay-hook nil)) + (funcall (symbol-function 'rfn-eshadow-update-overlay)))))))) + +(when (boundp 'rfn-eshadow-update-overlay-hook) + (add-hook 'rfn-eshadow-update-overlay-hook + 'tramp-rfn-eshadow-update-overlay)) + ;;; File Name Handler Functions: (defun tramp-handle-make-symbolic-link (filename linkname &optional ok-if-already-exists) - "Like `make-symbolic-link' for tramp files. + "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. @@ -2159,12 +2107,12 @@ 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-multi-method l-method l-user l-host)) + (let ((ln (tramp-get-remote-ln l)) (cwd (file-name-directory l-localname))) (unless ln - (signal 'file-error - (list "Making a symbolic link." - "ln(1) does not exist on the remote host."))) + (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) @@ -2175,33 +2123,27 @@ target of the symlink differ." (format "File %s already exists; make it a link anyway? " l-localname))))) - (signal 'file-already-exists (list "File already exists" 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))))) + (setq filename + (tramp-file-name-localname + (tramp-dissect-file-name (expand-file-name filename))))) ;; Right, they are on the same host, regardless of user, method, etc. ;; We now make the link on the remote machine. This will occur as the user ;; that FILENAME belongs to. (zerop (tramp-send-command-and-check - l-multi-method l-method l-user l-host - (format "cd %s && %s -sf %s %s" - cwd ln - filename - l-localname) - t))))) + l (format "cd %s && %s -sf %s %s" cwd ln filename l-localname) t))))) (defun tramp-handle-load (file &optional noerror nomessage nosuffix must-suffix) - "Like `load' for tramp files. Not implemented!" - (unless (file-name-absolute-p file) - (error "Tramp cannot `load' files without absolute file name")) - (with-parsed-tramp-file-name file nil + "Like `load' for Tramp files." + (with-parsed-tramp-file-name (expand-file-name file) nil (unless nosuffix (cond ((file-exists-p (concat file ".elc")) (setq file (concat file ".elc"))) @@ -2212,138 +2154,144 @@ target of the symlink differ." ;; Included for safety's sake. (unless (or (file-name-directory file) (string-match "\\.elc?\\'" file)) - (error "File `%s' does not include a `.el' or `.elc' suffix" - file))) + (tramp-error + v 'file-error + "File `%s' does not include a `.el' or `.elc' suffix" file))) (unless noerror (when (not (file-exists-p file)) - (error "Cannot load nonexistent file `%s'" file))) + (tramp-error v 'file-error "Cannot load nonexistent file `%s'" file))) (if (not (file-exists-p file)) nil - (unless nomessage - (message "Loading %s..." file)) + (unless nomessage (tramp-message v 0 "Loading %s..." file)) (let ((local-copy (file-local-copy file))) ;; MUST-SUFFIX doesn't exist on XEmacs, so let it default to nil. (load local-copy noerror t t) (delete-file local-copy)) - (unless nomessage - (message "Loading %s...done" file)) + (unless nomessage (tramp-message v 0 "Loading %s...done" file)) t))) -;; Localname manipulation functions that grok TRAMP localnames... +;; Localname manipulation functions that grok Tramp localnames... (defun tramp-handle-file-name-directory (file) - "Like `file-name-directory' but aware of TRAMP files." - ;; Everything except the last filename thing is the directory. - (with-parsed-tramp-file-name file nil + "Like `file-name-directory' but aware of Tramp files." + ;; Everything except the last filename thing is the directory. We + ;; cannot apply `with-parsed-tramp-file-name', because this expands + ;; the remote file name parts. This is a problem when we are in + ;; file name completion. + (let ((v (tramp-dissect-file-name file t))) ;; Run the command on the localname portion only. (tramp-make-tramp-file-name - multi-method method user host (file-name-directory (or localname ""))))) + (tramp-file-name-method v) + (tramp-file-name-user v) + (tramp-file-name-host v) + (file-name-directory (or (tramp-file-name-localname v) ""))))) (defun tramp-handle-file-name-nondirectory (file) - "Like `file-name-nondirectory' but aware of TRAMP files." + "Like `file-name-nondirectory' but aware of Tramp files." (with-parsed-tramp-file-name file nil (file-name-nondirectory localname))) (defun tramp-handle-file-truename (filename &optional counter prev-dirs) - "Like `file-truename' for tramp files." + "Like `file-truename' for Tramp files." (with-parsed-tramp-file-name (expand-file-name filename) nil - (let* ((steps (tramp-split-string localname "/")) - (localnamedir (tramp-let-maybe directory-sep-char ?/ ;for XEmacs - (file-name-as-directory 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) - (result nil) ;result steps in reverse order - symlink-target) - (tramp-message-for-buffer - multi-method method user host - 10 "Finding true name for `%s'" filename) - (while (and steps (< numchase numchase-limit)) - (setq thisstep (pop steps)) - (tramp-message-for-buffer - multi-method method user host - 10 "Check %s" - (mapconcat 'identity - (append '("") (reverse result) (list thisstep)) - "/")) - (setq symlink-target - (nth 0 (file-attributes - (tramp-make-tramp-file-name - multi-method method user host - (mapconcat 'identity - (append '("") - (reverse result) - (list thisstep)) - "/"))))) - (cond ((string= "." thisstep) - (tramp-message-for-buffer multi-method method user host - 10 "Ignoring step `.'")) - ((string= ".." thisstep) - (tramp-message-for-buffer multi-method method user host - 10 "Processing step `..'") - (pop result)) - ((stringp symlink-target) - ;; It's a symlink, follow it. - (tramp-message-for-buffer - multi-method method user host - 10 "Follow symlink to %s" symlink-target) - (setq numchase (1+ numchase)) - (when (file-name-absolute-p symlink-target) - (setq result nil)) - ;; 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) - (with-parsed-tramp-file-name symlink-target sym - (unless (equal (list multi-method method user host) - (list sym-multi-method sym-method - sym-user sym-host)) - (error "Symlink target `%s' on wrong host" - symlink-target)) - (setq symlink-target localname))) - (setq steps - (append (tramp-split-string symlink-target "/") steps))) - (t - ;; It's a file. - (setq result (cons thisstep result))))) - (when (>= numchase numchase-limit) - (error "Maximum number (%d) of symlinks exceeded" numchase-limit)) - (setq result (reverse result)) - ;; 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-for-buffer - multi-method method user host - 10 "True name of `%s' is `%s'" filename result) - (tramp-make-tramp-file-name - multi-method method user host result)))) + (with-file-property v localname "file-truename" + (let* ((steps (tramp-split-string localname "/")) + (localnamedir (tramp-let-maybe directory-sep-char ?/ ;for XEmacs + (file-name-as-directory 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) + (result nil) ;result steps in reverse order + symlink-target) + (tramp-message v 4 "Finding true name for `%s'" filename) + (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-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-handle-file-exists-p (filename) - "Like `file-exists-p' for tramp files." + "Like `file-exists-p' for Tramp files." (with-parsed-tramp-file-name filename nil - (save-excursion + (with-file-property v localname "file-exists-p" (zerop (tramp-send-command-and-check - multi-method method user host + v (format - (tramp-get-file-exists-command multi-method method user host) + "%s %s" + (tramp-get-file-exists-command v) (tramp-shell-quote-argument localname))))))) +;; Inodes don't exist for some file systems. Therefore we must +;; generate virtual ones. Used in `find-buffer-visiting'. The method +;; applied might be not so efficient (Ange-FTP uses hashes). But +;; performance isn't the major issue given that file transfer will +;; take time. +(defvar tramp-inodes nil + "Keeps virtual inodes numbers.") + ;; Devices must distinguish physical file systems. The device numbers ;; provided by "lstat" aren't unique, because we operate on different hosts. ;; So we use virtual device numbers, generated by Tramp. Both Ange-FTP and ;; EFS use device number "-1". In order to be different, we use device number -;; (-1 x), whereby "x" is unique for a given (multi-method method user host). +;; (-1 x), whereby "x" is unique for a given (method user host). (defvar tramp-devices nil "Keeps virtual device numbers.") @@ -2351,123 +2299,133 @@ target of the symlink differ." ;; when something goes wrong. ;; Daniel Pittman <daniel@danann.net> (defun tramp-handle-file-attributes (filename &optional id-format) - "Like `file-attributes' for tramp files." - (when (file-exists-p filename) - ;; file exists, find out stuff - (unless id-format (setq id-format 'integer)) - (with-parsed-tramp-file-name filename nil - (save-excursion - (tramp-convert-file-attributes - multi-method method user host - (if (tramp-get-remote-perl multi-method method user host) - (tramp-handle-file-attributes-with-perl multi-method method user host - localname id-format) - (tramp-handle-file-attributes-with-ls multi-method method user host - localname id-format))))))) - -(defun tramp-handle-file-attributes-with-ls - (multi-method method user host localname &optional id-format) - "Implement `file-attributes' for tramp files using the ls(1) command." + "Like `file-attributes' for Tramp files." + (unless id-format (setq id-format 'integer)) + (with-parsed-tramp-file-name (expand-file-name filename) nil + (with-file-property v localname (format "file-attributes-%s" id-format) + (when (file-exists-p filename) + ;; file exists, find out stuff + (save-excursion + (tramp-convert-file-attributes + v + (if (tramp-get-remote-stat v) + (tramp-handle-file-attributes-with-stat v localname id-format) + (if (tramp-get-remote-perl v) + (tramp-handle-file-attributes-with-perl v localname id-format) + (tramp-handle-file-attributes-with-ls + v localname id-format))))))))) + +(defun tramp-handle-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-for-buffer multi-method method user host 10 - "file attributes with ls: %s" - (tramp-make-tramp-file-name - multi-method method user host localname)) + (tramp-message vec 5 "file attributes with ls: %s" localname) (tramp-send-command - multi-method method user host + vec (format "%s %s %s" - (tramp-get-ls-command multi-method method user host) + (tramp-get-ls-command vec) (if (eq id-format 'integer) "-ildn" "-ild") (tramp-shell-quote-argument localname))) - (tramp-wait-for-output) ;; parse `ls -l' output ... - ;; ... inode - (setq res-inode - (condition-case err - (read (current-buffer)) - (invalid-read-syntax - (when (and (equal (cadr err) - "Integer constant overflow in reader") - (string-match - "^[0-9]+\\([0-9][0-9][0-9][0-9][0-9]\\)\\'" - (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))) - (when (eq id-format 'integer) - (unless (numberp res-uid) (setq res-uid -1)) - (unless (numberp res-gid) (setq res-gid -1))) - ;; ... size - (setq res-size (read (current-buffer))) - ;; From the file modes, figure out other stuff. - (setq symlinkp (eq ?l (aref res-filemodes 0))) - (setq dirp (eq ?d (aref res-filemodes 0))) - ;; if symlink, find out file name pointed to - (when symlinkp - (search-forward "-> ") - (setq res-symlink-target - (buffer-substring (point) - (tramp-line-end-position)))) - ;; return data gathered - (list - ;; 0. t for directory, string (name linked to) for symbolic - ;; link, or nil. - (or dirp res-symlink-target nil) - ;; 1. Number of links to file. - res-numlinks - ;; 2. File uid. - res-uid - ;; 3. File gid. - res-gid - ;; 4. Last access time, as a list of two integers. First - ;; integer has high-order 16 bits of time, second has low 16 - ;; bits. - ;; 5. Last modification time, likewise. - ;; 6. Last status change time, likewise. - '(0 0) '(0 0) '(0 0) ;CCC how to find out? - ;; 7. Size in bytes (-1, if number is out of range). - res-size - ;; 8. File modes, as a string of ten letters or dashes as in ls -l. - res-filemodes - ;; 9. t 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 - ))) + (with-current-buffer (tramp-get-buffer vec) + (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) (tramp-compat-line-end-position)))) + ;; return data gathered + (list + ;; 0. t for directory, string (name linked to) for symbolic + ;; link, or nil. + (or dirp res-symlink-target) + ;; 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-handle-file-attributes-with-perl - (multi-method method user host localname &optional id-format) - "Implement `file-attributes' for tramp files using a Perl script." - (tramp-message-for-buffer multi-method method user host 10 - "file attributes with perl: %s" - (tramp-make-tramp-file-name - multi-method method user host localname)) - (tramp-maybe-send-perl-script multi-method method user host - tramp-perl-file-attributes - "tramp_file_attributes") - (tramp-send-command multi-method method user host - (format "tramp_file_attributes %s %s" - (tramp-shell-quote-argument localname) id-format)) - (tramp-wait-for-output) - (read (current-buffer))) + (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-handle-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 + "%s -c '((\"%%N\") %%h %s %s %%X.0 %%Y.0 %%Z.0 %%s.0 \"%%A\" t %%i.0 -1)' %s" + (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-handle-set-visited-file-modtime (&optional time-list) - "Like `set-visited-file-modtime' for tramp files." + "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))) @@ -2485,16 +2443,16 @@ target of the symlink differ." ;; `tramp-handle-file-attributes-with-ls'. (if (not (equal modtime '(0 0))) (tramp-run-real-handler 'set-visited-file-modtime (list modtime)) - (save-excursion + (progn (tramp-send-command - multi-method method user host + v (format "%s -ild %s" - (tramp-get-ls-command multi-method method user host) + (tramp-get-ls-command v) (tramp-shell-quote-argument localname))) - (tramp-wait-for-output) (setq attr (buffer-substring (point) (progn (end-of-line) (point))))) - (setq tramp-buffer-file-attributes attr)) + (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))))) @@ -2504,7 +2462,7 @@ target of the symlink differ." ;; This function makes the same assumption as ;; `tramp-handle-set-visited-file-modtime'. (defun tramp-handle-verify-visited-file-modtime (buf) - "Like `verify-visited-file-modtime' for tramp files. + "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 @@ -2518,6 +2476,7 @@ of." t (let ((f (buffer-file-name))) (with-parsed-tramp-file-name f nil + (tramp-flush-file-property v localname) (let* ((attr (file-attributes f)) (modtime (nth 5 attr)) (mt (visited-file-modtime))) @@ -2536,53 +2495,111 @@ of." 2)) ;; modtime has the don't know value. (attr - (save-excursion - (tramp-send-command - multi-method method user host - (format "%s -ild %s" - (tramp-get-ls-command multi-method method user host) - (tramp-shell-quote-argument localname))) - (tramp-wait-for-output) + (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 tramp-buffer-file-attributes attr)) + (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-handle-set-file-modes (filename mode) - "Like `set-file-modes' for tramp files." + "Like `set-file-modes' for Tramp files." (with-parsed-tramp-file-name filename nil - (save-excursion - (unless (zerop (tramp-send-command-and-check - multi-method method user host - (format "chmod %s %s" - (tramp-decimal-to-octal mode) - (tramp-shell-quote-argument localname)))) - (signal 'file-error - (list "Doing chmod" - ;; FIXME: extract the proper text from chmod's stderr. - "error while changing file's mode" - filename)))))) + (tramp-flush-file-property v localname) + (unless (zerop (tramp-send-command-and-check + v + (format "chmod %s %s" + (tramp-decimal-to-octal mode) + (tramp-shell-quote-argument localname)))) + ;; FIXME: extract the proper text from chmod's stderr. + (tramp-error + v 'file-error "Error while changing file's mode %s" filename)))) + +(defun tramp-handle-set-file-times (filename &optional time) + "Like `set-file-times' for Tramp files." + (zerop + (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)) + (utc + ;; 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. + (and (functionp 'subr-arity) + (subrp (symbol-function 'format-time-string)) + (= 3 (cdr (funcall (symbol-function 'subr-arity) + (symbol-function + 'format-time-string))))))) + (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. + (tramp-local-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." + ;; CCC: 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. + (if (file-remote-p filename) + (with-parsed-tramp-file-name filename nil + (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 Win32 "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-local-call-process + "chown" nil nil nil + (format "%d:%d" uid gid) (tramp-shell-quote-argument filename))))) ;; Simple functions using the `test' command. (defun tramp-handle-file-executable-p (filename) - "Like `file-executable-p' for tramp files." + "Like `file-executable-p' for Tramp files." (with-parsed-tramp-file-name filename nil - (zerop (tramp-run-test "-x" filename)))) + (with-file-property v localname "file-executable-p" + (zerop (tramp-run-test "-x" filename))))) (defun tramp-handle-file-readable-p (filename) - "Like `file-readable-p' for tramp files." + "Like `file-readable-p' for Tramp files." (with-parsed-tramp-file-name filename nil - (zerop (tramp-run-test "-r" filename)))) - -(defun tramp-handle-file-accessible-directory-p (filename) - "Like `file-accessible-directory-p' for tramp files." - (with-parsed-tramp-file-name filename nil - (and (zerop (tramp-run-test "-d" filename)) - (zerop (tramp-run-test "-r" filename)) - (zerop (tramp-run-test "-x" filename))))) + (with-file-property v localname "file-readable-p" + (zerop (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 @@ -2590,7 +2607,7 @@ of." ;; newer than). If this breaks, tell me about it and I'll try to do ;; something smarter about it. (defun tramp-handle-file-newer-than-file-p (file1 file2) - "Like `file-newer-than-file-p' for tramp files." + "Like `file-newer-than-file-p' for Tramp files." (cond ((not (file-exists-p file1)) nil) ((not (file-exists-p file2)) @@ -2611,44 +2628,27 @@ of." ;; However, this only works if both files are Tramp ;; files and both have the same method, same user, same ;; host. - (unless (and (tramp-tramp-file-p file1) - (tramp-tramp-file-p file2)) - (signal - 'file-error - (list - "Cannot check if Tramp file is newer than non-Tramp file" - file1 file2))) - (with-parsed-tramp-file-name file1 v1 - (with-parsed-tramp-file-name file2 v2 - (unless (and (equal v1-multi-method v2-multi-method) - (equal v1-method v2-method) - (equal v1-user v2-user) - (equal v1-host v2-host)) - (signal 'file-error - (list "Files must have same method, user, host" - file1 file2))) - (unless (and (tramp-tramp-file-p file1) - (tramp-tramp-file-p file2)) - (signal 'file-error - (list "Files must be tramp files on same host" - file1 file2))) - (if (tramp-get-test-groks-nt - v1-multi-method v1-method v1-user v1-host) - (zerop (tramp-run-test2 "test" file1 file2 "-nt")) - (zerop (tramp-run-test2 - "tramp_test_nt" file1 file2))))))))))) + (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 + (zerop (tramp-run-test2 + (tramp-get-test-nt-command v) file1 file2))))))))) ;; Functions implemented using the basic functions above. (defun tramp-handle-file-modes (filename) - "Like `file-modes' for tramp files." - (with-parsed-tramp-file-name filename nil - (when (file-exists-p filename) - (tramp-mode-string-to-int - (nth 8 (file-attributes filename)))))) + "Like `file-modes' for Tramp files." + (when (file-exists-p filename) + (tramp-mode-string-to-int + (nth 8 (file-attributes filename))))) (defun tramp-handle-file-directory-p (filename) - "Like `file-directory-p' for tramp files." + "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. @@ -2658,78 +2658,52 @@ of." ;; ;; Alternatives: `cd %s', `test -d %s' (with-parsed-tramp-file-name filename nil - (save-excursion - (zerop - (tramp-send-command-and-check - multi-method method user host - (format "test -d %s" - (tramp-shell-quote-argument localname)) - t))))) ;run command in subshell + (with-file-property v localname "file-directory-p" + (zerop (tramp-run-test "-d" filename))))) (defun tramp-handle-file-regular-p (filename) - "Like `file-regular-p' for tramp files." - (with-parsed-tramp-file-name filename nil - (and (file-exists-p filename) - (eq ?- (aref (nth 8 (file-attributes filename)) 0))))) + "Like `file-regular-p' for Tramp files." + (and (file-exists-p filename) + (eq ?- (aref (nth 8 (file-attributes filename)) 0)))) (defun tramp-handle-file-symlink-p (filename) - "Like `file-symlink-p' for tramp files." + "Like `file-symlink-p' for Tramp files." (with-parsed-tramp-file-name filename nil (let ((x (car (file-attributes filename)))) (when (stringp x) ;; When Tramp is running on VMS, then `file-name-absolute-p' ;; might do weird things. (if (file-name-absolute-p x) - (tramp-make-tramp-file-name - multi-method method user host x) + (tramp-make-tramp-file-name method user host x) x))))) (defun tramp-handle-file-writable-p (filename) - "Like `file-writable-p' for tramp files." + "Like `file-writable-p' for Tramp files." (with-parsed-tramp-file-name filename nil - (if (file-exists-p filename) - ;; Existing files must be writable. - (zerop (tramp-run-test "-w" filename)) - ;; If file doesn't exist, check if directory is writable. - (and (zerop (tramp-run-test - "-d" (file-name-directory filename))) - (zerop (tramp-run-test - "-w" (file-name-directory filename))))))) + (with-file-property v localname "file-writable-p" + (if (file-exists-p filename) + ;; Existing files must be writable. + (zerop (tramp-run-test "-w" filename)) + ;; If file doesn't exist, check if directory is writable. + (and (zerop (tramp-run-test + "-d" (file-name-directory filename))) + (zerop (tramp-run-test + "-w" (file-name-directory filename)))))))) (defun tramp-handle-file-ownership-preserved-p (filename) - "Like `file-ownership-preserved-p' for tramp files." + "Like `file-ownership-preserved-p' for Tramp files." (with-parsed-tramp-file-name filename nil - (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 multi-method method user host)))))) + (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))))))) ;; Other file name ops. -;; ;; Matthias K,Av(Bppe <mkoeppe@mail.math.uni-magdeburg.de> -;; (defun tramp-handle-directory-file-name (directory) -;; "Like `directory-file-name' for tramp files." -;; (if (and (eq (aref directory (- (length directory) 1)) ?/) -;; (not (eq (aref directory (- (length directory) 2)) ?:))) -;; (substring directory 0 (- (length directory) 1)) -;; directory)) - -;; ;; Philippe Troin <phil@fifi.org> -;; (defun tramp-handle-directory-file-name (directory) -;; "Like `directory-file-name' for tramp files." -;; (with-parsed-tramp-file-name directory nil -;; (let ((directory-length-1 (1- (length directory)))) -;; (save-match-data -;; (if (and (eq (aref directory directory-length-1) ?/) -;; (eq (string-match tramp-file-name-regexp directory) 0) -;; (/= (match-end 0) directory-length-1)) -;; (substring directory 0 directory-length-1) -;; directory))))) - (defun tramp-handle-directory-file-name (directory) - "Like `directory-file-name' for tramp files." + "Like `directory-file-name' for Tramp files." ;; If localname component of filename is "/", leave it unchanged. ;; Otherwise, remove any trailing slash from localname component. ;; Method, host, etc, are unchanged. Does it make sense to try @@ -2743,145 +2717,150 @@ of." ;; Directory listings. -(defun tramp-handle-directory-files (directory - &optional full match nosort files-only) - "Like `directory-files' for tramp files." - (with-parsed-tramp-file-name directory nil - (let (result x) - (save-excursion - (tramp-barf-unless-okay - multi-method method user host - (concat "cd " (tramp-shell-quote-argument localname)) - nil - 'file-error - "tramp-handle-directory-files: couldn't `cd %s'" - (tramp-shell-quote-argument localname)) - (tramp-send-command - multi-method method user host - (concat (tramp-get-ls-command multi-method method user host) - " -a | cat")) - (tramp-wait-for-output) - (goto-char (point-max)) - (while (zerop (forward-line -1)) - (setq x (buffer-substring (point) - (tramp-line-end-position))) - (when (or (not match) (string-match match x)) - (if full - (push (concat (file-name-as-directory directory) - x) - result) - (push x result)))) - (tramp-send-command multi-method method user host "cd") - (tramp-wait-for-output) - ;; Remove non-files or non-directories if necessary. Using - ;; the remote shell for this would probably be way faster. - ;; Maybe something could be adapted from - ;; tramp-handle-file-name-all-completions. - (when files-only - (let ((temp (nreverse result)) - item) - (setq result nil) - (if (equal files-only t) - ;; files only - (while temp - (setq item (pop temp)) - (when (file-regular-p item) - (push item result))) - ;; directories only - (while temp - (setq item (pop temp)) - (when (file-directory-p item) - (push item result))))))) +(defun tramp-handle-directory-files + (directory &optional full match nosort files-only) + "Like `directory-files' for Tramp files." + ;; FILES-ONLY is valid for XEmacs only. + (when (file-directory-p directory) + (setq directory (expand-file-name directory)) + (let ((temp (nreverse (file-name-all-completions "" directory))) + result item) + + (while temp + (setq item (directory-file-name (pop temp))) + (when (and (or (null match) (string-match match item)) + (or (null files-only) + ;; files only + (and (equal files-only t) (file-regular-p item)) + ;; directories only + (file-directory-p item))) + (push (if full (expand-file-name item directory) item) + result))) result))) (defun tramp-handle-directory-files-and-attributes (directory &optional full match nosort id-format) - "Like `directory-files-and-attributes' for tramp files." - (when (tramp-handle-file-exists-p directory) - (save-excursion - (setq directory (tramp-handle-expand-file-name directory)) - (with-parsed-tramp-file-name directory nil - (tramp-maybe-send-perl-script multi-method method user host - tramp-perl-directory-files-and-attributes - "tramp_directory_files_and_attributes") - (tramp-send-command multi-method method user host - (format "tramp_directory_files_and_attributes %s %s" - (tramp-shell-quote-argument localname) - (or id-format 'integer))) - (tramp-wait-for-output) - (let* ((root (cons nil (let ((object (read (current-buffer)))) - (when (stringp object) - (error object)) - object))) - (cell root)) - (while (cdr cell) - (if (and match (not (string-match match (car (cadr cell))))) - ;; Remove from list - (setcdr cell (cddr cell)) - ;; Include in list - (setq cell (cdr cell)) - (let ((l (car cell))) - (tramp-convert-file-attributes multi-method method user host - (cdr l)) - ;; If FULL, make file name absolute - (when full (setcar l (concat directory "/" (car l))))))) - (if nosort - (cdr root) - (sort (cdr root) (lambda (x y) (string< (car x) (car y)))))))))) + "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 + (tramp-compat-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)))) + (if (tramp-get-remote-stat v) + (tramp-handle-directory-files-and-attributes-with-stat + v localname id-format) + (if (tramp-get-remote-perl v) + (tramp-handle-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-handle-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-handle-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 + "cd %s; echo \"(\"; (%s -ab | xargs " + "%s -c '(\"%%n\" (\"%%N\") %%h %s %s %%X.0 %%Y.0 %%Z.0 %%s.0 \"%%A\" t %%i.0 -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. We use `ls -ad' to get a list of files (including -;; directories), and `find . -type d \! -name . -prune' to get a list -;; of directories. +;; files. (defun tramp-handle-file-name-all-completions (filename directory) - "Like `file-name-all-completions' for tramp files." - (with-parsed-tramp-file-name directory nil - (unless (save-match-data (string-match "/" filename)) - (let* ((nowild tramp-completion-without-shell-p) - result) - (save-excursion - (tramp-barf-unless-okay - multi-method method user host - (format "cd %s" (tramp-shell-quote-argument localname)) - nil 'file-error - "tramp-handle-file-name-all-completions: Couldn't `cd %s'" - (tramp-shell-quote-argument localname)) - - ;; Get a list of directories and files, including reliably - ;; tagging the directories with a trailing '/'. Because I - ;; rock. --daniel@danann.net - (tramp-send-command - multi-method method user host - (format (concat "%s -a %s 2>/dev/null | while read f; do " - "if test -d \"$f\" 2>/dev/null; " - "then echo \"$f/\"; else echo \"$f\"; fi; done") - (tramp-get-ls-command multi-method method user host) - (if (or nowild (zerop (length filename))) - "" - (format "-d %s*" - (tramp-shell-quote-argument filename))))) - - ;; Now grab the output. - (tramp-wait-for-output) - (goto-char (point-max)) - (while (zerop (forward-line -1)) - (push (buffer-substring (point) - (tramp-line-end-position)) - result)) - - (tramp-send-command multi-method method user host "cd") - (tramp-wait-for-output) - - ;; Return the list. - (if nowild - (all-completions filename (mapcar 'list result)) - result)))))) - + "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 + ;; Flush the directory cache. There could be changed directory + ;; contents. + (when (and (integerp tramp-completion-reread-directory-timeout) + (> (tramp-time-diff + (current-time) + (tramp-get-file-property + v localname "last-completion" '(0 0 0))) + tramp-completion-reread-directory-timeout)) + (tramp-flush-file-property v localname)) + + (all-completions + filename + (mapcar + 'list + (with-file-property v localname "file-name-all-completions" + (let (result) + (tramp-barf-unless-okay + v + (format "cd %s" (tramp-shell-quote-argument localname)) + "tramp-handle-file-name-all-completions: Couldn't `cd %s'" + (tramp-shell-quote-argument localname)) + + ;; Get a list of directories and files, including reliably + ;; tagging the directories with a trailing '/'. Because I + ;; rock. --daniel@danann.net + (tramp-send-command + v + (format (concat "%s -ab 2>/dev/null | while read f; do " + "if %s -d \"$f\" 2>/dev/null; " + "then echo \"$f/\"; else echo \"$f\"; fi; done") + (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)) + (while (zerop (forward-line -1)) + (push (buffer-substring + (point) (tramp-compat-line-end-position)) + result))) + + (tramp-set-file-property + v localname "last-completion" (current-time)) + result))))))) ;; The following isn't needed for Emacs 20 but for 19.34? (defun tramp-handle-file-name-completion (filename directory &optional predicate) - "Like `file-name-completion' for tramp files." + "Like `file-name-completion' for Tramp files." (unless (tramp-tramp-file-p directory) (error "tramp-handle-file-name-completion invoked on non-tramp directory `%s'" @@ -2896,18 +2875,17 @@ of." (defun tramp-handle-add-name-to-file (filename newname &optional ok-if-already-exists) - "Like `add-name-to-file' for tramp files." + "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-multi-method v1-method v1-user v1-host)))) - (unless (and v1-method v2-method v1-user v2-user v1-host v2-host - (equal v1-multi-method v2-multi-method) - (equal v1-method v2-method) - (equal v1-user v2-user) - (equal v1-host v2-host)) - (error "add-name-to-file: %s" - "only implemented for same method, same user, same host")) + (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)) @@ -2915,224 +2893,295 @@ of." (format "File %s already exists; make it a new name anyway? " newname))) - (error "add-name-to-file: file %s already exists" newname)) + (tramp-error + v2 'file-error + "add-name-to-file: file %s already exists" newname)) + (tramp-flush-file-property v2 v2-localname) (tramp-barf-unless-okay - v1-multi-method v1-method v1-user v1-host + v1 (format "%s %s %s" ln (tramp-shell-quote-argument v1-localname) (tramp-shell-quote-argument v2-localname)) - nil 'file-error "error with add-name-to-file, see buffer `%s' for details" (buffer-name)))))) (defun tramp-handle-copy-file - (filename newname &optional ok-if-already-exists keep-date) - "Like `copy-file' for tramp files." + (filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) + "Like `copy-file' for Tramp files." ;; Check if both files are local -- invoke normal copy-file. - ;; Otherwise, use tramp from local system. + ;; Otherwise, use Tramp from local system. (setq filename (expand-file-name filename)) (setq newname (expand-file-name newname)) - ;; At least one file a tramp file? - (if (or (tramp-tramp-file-p filename) - (tramp-tramp-file-p newname)) - (tramp-do-copy-or-rename-file - 'copy filename newname ok-if-already-exists keep-date) + (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)) + ;; Compat section. + (preserve-uid-gid (tramp-run-real-handler 'copy-file - (list filename newname ok-if-already-exists keep-date)))) + (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-handle-rename-file (filename newname &optional ok-if-already-exists) - "Like `rename-file' for tramp files." + "Like `rename-file' for Tramp files." ;; Check if both files are local -- invoke normal rename-file. - ;; Otherwise, use tramp from local system. + ;; 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? + ;; At least one file a Tramp file? (if (or (tramp-tramp-file-p filename) (tramp-tramp-file-p newname)) (tramp-do-copy-or-rename-file - 'rename filename newname ok-if-already-exists) - (tramp-run-real-handler 'rename-file - (list filename newname ok-if-already-exists)))) + '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) + (op filename newname &optional ok-if-already-exists keep-date preserve-uid-gid) "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. +as FILENAME. PRESERVE-UID-GID, when non-nil, instructs to keep +the uid and gid if both files are on the same host. This function is invoked by `tramp-handle-copy-file' and `tramp-handle-rename-file'. It is an error if OP is neither of `copy' and `rename'. FILENAME and NEWNAME must be absolute file names." (unless (memq op '(copy rename)) (error "Unknown operation `%s', must be `copy' or `rename'" op)) - (unless ok-if-already-exists - (when (file-exists-p newname) - (signal 'file-already-exists - (list "File already exists" newname)))) (let ((t1 (tramp-tramp-file-p filename)) - (t2 (tramp-tramp-file-p newname)) - v1-multi-method v1-method v1-user v1-host v1-localname - v2-multi-method v2-method v2-user v2-host v2-localname) - - ;; Check which ones of source and target are Tramp files. - ;; We cannot invoke `with-parsed-tramp-file-name'; - ;; it fails if the file isn't a Tramp file name. - (if t1 - (with-parsed-tramp-file-name filename l - (setq v1-multi-method l-multi-method - v1-method l-method - v1-user l-user - v1-host l-host - v1-localname l-localname)) - (setq v1-localname filename)) - (if t2 - (with-parsed-tramp-file-name newname l - (setq v2-multi-method l-multi-method - v2-method l-method - v2-user l-user - v2-host l-host - v2-localname l-localname)) - (setq v2-localname newname)) + (t2 (tramp-tramp-file-p newname))) - (cond - ;; Both are Tramp files. - ((and t1 t2) - (cond - ;; Shortcut: if method, host, user are the same for both - ;; files, we invoke `cp' or `mv' on the remote host - ;; directly. - ((and (equal v1-multi-method v2-multi-method) - (equal v1-method v2-method) - (equal v1-user v2-user) - (equal v1-host v2-host)) - (tramp-do-copy-or-rename-file-directly - op v1-multi-method v1-method v1-user v1-host - v1-localname v2-localname keep-date)) - ;; If both source and target are Tramp files, - ;; both are using the same copy-program, then we - ;; can invoke rcp directly. Note that - ;; default-directory should point to a local - ;; directory if we want to invoke rcp. - ((and (not v1-multi-method) - (not v2-multi-method) - (equal v1-method v2-method) - (tramp-method-out-of-band-p - v1-multi-method v1-method v1-user v1-host) - (not (string-match "\\([^#]*\\)#\\(.*\\)" v1-host)) - (not (string-match "\\([^#]*\\)#\\(.*\\)" v2-host))) - (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) - ;; If the Tramp file has an out-of-band method, the corresponding - ;; copy-program can be invoked. - (if (and (not v1-multi-method) - (not v2-multi-method) - (or (and t1 (tramp-method-out-of-band-p - v1-multi-method v1-method v1-user v1-host)) - (and t2 (tramp-method-out-of-band-p - v2-multi-method v2-method v2-user v2-host)))) - (tramp-do-copy-or-rename-file-out-of-band - op filename newname keep-date) - ;; Use the generic method via a Tramp buffer. - (tramp-do-copy-or-rename-file-via-buffer - op filename newname keep-date))) + (unless ok-if-already-exists + (when (and t2 (file-exists-p newname)) + (with-parsed-tramp-file-name newname nil + (tramp-error + v 'file-already-exists "File %s already exists" newname)))) - (t - ;; One of them must be a Tramp file. - (error "Tramp implementation says this cannot happen"))))) + (prog1 + (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)) + + ;; If both source and target are Tramp files, + ;; both are using the same copy-program, then we + ;; can invoke rcp directly. Note that + ;; default-directory should point to a local + ;; directory if we want to invoke rcp. + ((and (equal v1-method v2-method) + (tramp-method-out-of-band-p v1) + (> (nth 7 (file-attributes filename)) + tramp-copy-size-limit)) + (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) + (with-parsed-tramp-file-name (if t1 filename newname) nil + (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. + ((and (tramp-method-out-of-band-p v) + (> (nth 7 (file-attributes filename)) + tramp-copy-size-limit)) + (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"))) + + ;; When newname did exist, we have wrong cached values. + (when t2 + (with-parsed-tramp-file-name newname nil + (tramp-flush-file-property v 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." - (let ((trampbuf (get-buffer-create "*tramp output*")) - (modtime (nth 5 (file-attributes filename)))) - (when (and keep-date (or (null modtime) (equal modtime '(0 0)))) - (tramp-message - 1 (concat "Warning: cannot preserve file time stamp" - " with inline copying across machines"))) - (save-excursion - (set-buffer trampbuf) (erase-buffer) - (insert-file-contents-literally filename) - ;; 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 - (when (and (not (null modtime)) - (not (equal modtime '(0 0)))) - (tramp-touch newname modtime))) - ;; Set the mode. - (set-file-modes newname (file-modes filename))) + (let ((modtime (nth 5 (file-attributes filename)))) + (unwind-protect + (with-temp-buffer + (let ((coding-system-for-read 'binary)) + (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 modtime)) + ;; Set the mode. + (set-file-modes newname (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 multi-method method user host localname1 localname2 keep-date) + (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. METHOD, USER, and HOST specify the connection. -LOCALNAME1 and LOCALNAME2 specify the two arguments of `cp' or `mv'. -If KEEP-DATE is non-nil, preserve the time stamp when copying." - ;; CCC: What happens to the timestamp when renaming? - (let ((cmd (cond ((and (eq op 'copy) keep-date) "cp -f -p") - ((eq op 'copy) "cp -f") - ((eq op 'rename) "mv -f") - (t (error - "Unknown operation `%s', must be `copy' or `rename'" - op))))) - (save-excursion - (tramp-send-command - multi-method method user host - (format "%s %s %s" - cmd - (tramp-shell-quote-argument localname1) - (tramp-shell-quote-argument localname2))) - (tramp-wait-for-output) - (goto-char (point-min)) - (unless - (or - (and (eq op 'copy) keep-date - ;; Mask cp -f error. - (re-search-forward tramp-operation-not-permitted-regexp nil t)) - (zerop (tramp-send-command-and-check - multi-method method user host nil nil))) - (pop-to-buffer (current-buffer)) - (signal 'file-error - (format "Copying directly failed, see buffer `%s' for details." - (buffer-name))))) - ;; Set the mode. - ;; CCC: Maybe `chmod --reference=localname1 localname2' could be used - ;; where available? - (unless (or (eq op 'rename) keep-date) - (set-file-modes - (tramp-make-tramp-file-name multi-method method user host localname2) - (file-modes - (tramp-make-tramp-file-name - multi-method method user host localname1)))))) +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))) + (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-handle-file-remote-p filename 'localname) filename)) + (localname2 + (if t2 (tramp-handle-file-remote-p newname 'localname) newname)) + (prefix (file-remote-p (if t1 filename newname)))) + + (cond + ;; Both files are on a remote host, with same user. + ((and t1 t2) + (tramp-send-command + 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)) + (zerop (tramp-send-command-and-check v nil))) + (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. + ((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) + (rename-file 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)))) + (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))) + (cond + (t1 + (tramp-send-command + v (format + "%s %s %s" cmd + (tramp-shell-quote-argument localname1) + (tramp-shell-quote-argument tmpfile))) + ;; We must change the ownership as remote user. + (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 ok-if-already-exists + keep-date preserve-uid-gid) + (rename-file localname1 tmpfile ok-if-already-exists)) + ;; We must change the ownership as local user. + (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-send-command + v (format + "mv -f %s %s" + (tramp-shell-quote-argument tmpfile) + (tramp-shell-quote-argument localname2)))) + (t1 + (rename-file tmpfile localname2 ok-if-already-exists))))))))) + + ;; Set the time and mode. Mask possible errors. + ;; Won't be applied for 'rename. + (condition-case nil + (when (and keep-date (not preserve-uid-gid)) + (set-file-times newname (nth 5 (file-attributes filename))) + (set-file-modes newname (file-modes filename))) + (error))))) + (defun tramp-do-copy-or-rename-file-out-of-band (op filename newname keep-date) "Invoke rcp program to copy. @@ -3140,234 +3189,171 @@ One of FILENAME and NEWNAME must be a Tramp name, the other must be a local filename. The method used must be an out-of-band method." (let ((t1 (tramp-tramp-file-p filename)) (t2 (tramp-tramp-file-p newname)) - v1-multi-method v1-method v1-user v1-host v1-localname - v2-multi-method v2-method v2-user v2-host v2-localname - multi-method method user host copy-program copy-args - source target trampbuf) - - ;; Check which ones of source and target are Tramp files. - ;; We cannot invoke `with-parsed-tramp-file-name'; - ;; it fails if the file isn't a Tramp file name. - (if t1 - (with-parsed-tramp-file-name filename l - (setq v1-multi-method l-multi-method - v1-method l-method - v1-user l-user - v1-host l-host - v1-localname l-localname - multi-method l-multi-method - method (tramp-find-method - v1-multi-method v1-method v1-user v1-host) - user l-user - host l-host - copy-program (tramp-get-method-parameter - v1-multi-method method - v1-user v1-host 'tramp-copy-program) - copy-args (tramp-get-method-parameter - v1-multi-method method - v1-user v1-host 'tramp-copy-args))) - (setq v1-localname filename)) - - (if t2 - (with-parsed-tramp-file-name newname l - (setq v2-multi-method l-multi-method - v2-method l-method - v2-user l-user - v2-host l-host - v2-localname l-localname - multi-method l-multi-method - method (tramp-find-method - v2-multi-method v2-method v2-user v2-host) - user l-user - host l-host - copy-program (tramp-get-method-parameter - v2-multi-method method - v2-user v2-host 'tramp-copy-program) - copy-args (tramp-get-method-parameter - v2-multi-method method - v2-user v2-host 'tramp-copy-args))) - (setq v2-localname newname)) - - ;; The following should be changed. We need a more general - ;; mechanism to parse extra host args. - (if (not t1) - (setq source v1-localname) - (when (string-match "\\([^#]*\\)#\\(.*\\)" v1-host) - (setq copy-args (cons "-P" (cons (match-string 2 v1-host) copy-args))) - (setq v1-host (match-string 1 v1-host))) - (setq source - (tramp-make-copy-program-file-name - v1-user v1-host - (tramp-shell-quote-argument v1-localname)))) - - (if (not t2) - (setq target v2-localname) - (when (string-match "\\([^#]*\\)#\\(.*\\)" v2-host) - (setq copy-args (cons "-P" (cons (match-string 2 v2-host) copy-args))) - (setq v2-host (match-string 1 v2-host))) - (setq target - (tramp-make-copy-program-file-name - v2-user v2-host - (tramp-shell-quote-argument v2-localname)))) - - ;; Handle ControlMaster/ControlPath - (setq copy-args - (mapcar - (lambda (x) - (format-spec - x `((?t . ,(format "/tmp/%s" tramp-temp-name-prefix))))) - copy-args)) - - ;; Handle keep-date argument - (when keep-date - (if t1 - (setq copy-args - (cons (tramp-get-method-parameter - v1-multi-method method - v1-user v1-host 'tramp-copy-keep-date-arg) - copy-args)) - (setq copy-args - (cons (tramp-get-method-parameter - v2-multi-method method - v2-user v2-host 'tramp-copy-keep-date-arg) - copy-args)))) - - (setq copy-args (append copy-args (list source target)) - trampbuf (generate-new-buffer - (tramp-buffer-name multi-method method user host))) - - ;; Use an asynchronous process. By this, password can be handled. - (save-excursion + copy-program copy-args copy-keep-date port spec + source target) + + (with-parsed-tramp-file-name (if t1 filename newname) nil + + ;; 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 (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 `((?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 + (delq + nil + (mapcar + '(lambda (x) + (setq + ;; " " is indication for keep-date argument. + x (delete " " (mapcar '(lambda (y) (format-spec y spec)) x))) + (unless (member "" x) (mapconcat 'identity x " "))) + (tramp-get-method-parameter method 'tramp-copy-args)))) ;; Check for program. (when (and (fboundp 'executable-find) - (not (executable-find copy-program))) - (error "Cannot find copy program: %s" copy-program)) + (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)) - (set-buffer trampbuf) - (setq tramp-current-multi-method multi-method - tramp-current-method method - tramp-current-user user - tramp-current-host host) - (message "Transferring %s to %s..." filename newname) + (tramp-message v 0 "Transferring %s to %s..." filename newname) - ;; Use rcp-like program for file transfer. (unwind-protect - (let* ((default-directory - (if (and (stringp default-directory) - (file-accessible-directory-p default-directory)) - default-directory - (tramp-temporary-file-directory))) - (p (apply 'start-process (buffer-name trampbuf) trampbuf - copy-program copy-args))) - (tramp-set-process-query-on-exit-flag p nil) - (tramp-process-actions p multi-method method user host - tramp-actions-copy-out-of-band)) - (kill-buffer trampbuf)) - (message "Transferring %s to %s...done" filename newname) + (with-temp-buffer + ;; The default directory must be remote. + (let ((default-directory + (file-name-directory (if t1 filename newname)))) + ;; 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)) + + ;; 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-property + v "process-name" nil) + (tramp-get-connection-property + v "process-buffer" nil) + copy-program + (append copy-args (list source target)))))) + (tramp-message + v 6 "%s" (mapconcat 'identity (process-command p) " ")) + (set-process-sentinel p 'tramp-flush-connection-property) + (tramp-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-set-connection-property v "process-name" nil) + (tramp-set-connection-property v "process-buffer" nil)) + + (tramp-message v 0 "Transferring %s to %s...done" filename newname) + + ;; 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 keep-date + (unless (and keep-date copy-keep-date) (set-file-modes newname (file-modes filename)))) ;; If the operation was `rename', delete the original file. (unless (eq op 'copy) (delete-file filename)))) -;; mkdir (defun tramp-handle-make-directory (dir &optional parents) - "Like `make-directory' for tramp files." + "Like `make-directory' for Tramp files." (setq dir (expand-file-name dir)) (with-parsed-tramp-file-name dir nil (save-excursion (tramp-barf-unless-okay - multi-method method user host - (format " %s %s" + v + (format "%s %s" (if parents "mkdir -p" "mkdir") (tramp-shell-quote-argument localname)) - nil 'file-error "Couldn't make directory %s" dir)))) -;; CCC error checking? (defun tramp-handle-delete-directory (directory) - "Like `delete-directory' for tramp files." + "Like `delete-directory' for Tramp files." (setq directory (expand-file-name directory)) (with-parsed-tramp-file-name directory nil - (save-excursion - (tramp-send-command - multi-method method user host - (format "rmdir %s ; echo ok" - (tramp-shell-quote-argument localname))) - (tramp-wait-for-output)))) + (tramp-flush-directory-property v localname) + (unless (zerop (tramp-send-command-and-check + v + (format "rmdir -f %s" + (tramp-shell-quote-argument localname)))) + (tramp-error v 'file-error "Couldn't delete %s" directory)))) (defun tramp-handle-delete-file (filename) - "Like `delete-file' for tramp files." + "Like `delete-file' for Tramp files." (setq filename (expand-file-name filename)) (with-parsed-tramp-file-name filename nil - (save-excursion - (unless (zerop (tramp-send-command-and-check - multi-method method user host - (format "rm -f %s" - (tramp-shell-quote-argument localname)))) - (signal 'file-error "Couldn't delete Tramp file"))))) + (tramp-flush-file-property v localname) + (unless (zerop (tramp-send-command-and-check + v + (format "rm -f %s" + (tramp-shell-quote-argument localname)))) + (tramp-error v 'file-error "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 :/ +;; we try and delete two directories under Tramp :/ (defun tramp-handle-dired-recursive-delete-directory (filename) "Recursively delete the directory given. -This is like `dired-recursive-delete-directory' for tramp files." +This is like `dired-recursive-delete-directory' for Tramp files." (with-parsed-tramp-file-name filename nil - ;; run a shell command 'rm -r <localname>' + (tramp-flush-directory-property v filename) + ;; Run a shell command 'rm -r <localname>' ;; Code shamelessly stolen for the dired implementation and, um, hacked :) - (or (file-exists-p filename) - (signal - 'file-error - (list "Removing old file name" "no such directory" filename))) + (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 <daniel@danann.net>) - (tramp-send-command multi-method method user host - (format "rm -r %s" (tramp-shell-quote-argument localname))) + (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 120) + (tramp-wait-for-output (tramp-get-connection-process v) 120) ;; Make sure that it worked... (and (file-exists-p filename) - (error "Failed to recursively delete %s" filename)))) - -(defun tramp-handle-dired-call-process (program discard &rest arguments) - "Like `dired-call-process' for tramp files." - (with-parsed-tramp-file-name default-directory nil - (save-excursion - (tramp-barf-unless-okay - multi-method method user host - (format "cd %s" (tramp-shell-quote-argument localname)) - nil 'file-error - "tramp-handle-dired-call-process: Couldn't `cd %s'" - (tramp-shell-quote-argument localname)) - (tramp-send-command - multi-method method user host - (mapconcat #'tramp-shell-quote-argument (cons program arguments) " ")) - (tramp-wait-for-output)) - (unless discard - ;; We cannot use `insert-buffer' because the tramp buffer - ;; changes its contents before insertion due to calling - ;; `expand-file' and alike. - (insert - (with-current-buffer - (tramp-get-buffer multi-method method user host) - (buffer-string)))) - (save-excursion - (prog1 - (tramp-send-command-and-check multi-method method user host nil) - (tramp-send-command multi-method method user host "cd") - (tramp-wait-for-output))))) + (tramp-error + v 'file-error "Failed to recursively delete %s" filename)))) (defun tramp-handle-dired-compress-file (file &rest ok-flag) - "Like `dired-compress-file' for tramp files." + "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)) @@ -3393,11 +3379,10 @@ This is like `dired-recursive-delete-directory' for tramp files." nil) ((and suffix (nth 2 suffix)) ;; We found an uncompression rule. - (message "Uncompressing %s..." file) + (tramp-message v 0 "Uncompressing %s..." file) (when (zerop (tramp-send-command-and-check - multi-method method user host - (concat (nth 2 suffix) " " localname))) - (message "Uncompressing %s...done" file) + v (concat (nth 2 suffix) " " localname))) + (tramp-message v 0 "Uncompressing %s...done" file) ;; `dired-remove-file' is not defined in XEmacs (funcall (symbol-function 'dired-remove-file) file) (string-match (car suffix) file) @@ -3405,11 +3390,10 @@ This is like `dired-recursive-delete-directory' for tramp files." (t ;; We don't recognize the file as compressed, so compress it. ;; Try gzip. - (message "Compressing %s..." file) + (tramp-message v 0 "Compressing %s..." file) (when (zerop (tramp-send-command-and-check - multi-method method user host - (concat "gzip -f " localname))) - (message "Compressing %s...done" file) + v (concat "gzip -f " localname))) + (tramp-message v 0 "Compressing %s...done" file) ;; `dired-remove-file' is not defined in XEmacs (funcall (symbol-function 'dired-remove-file) file) (cond ((file-exists-p (concat file ".gz")) @@ -3433,21 +3417,21 @@ This is like `dired-recursive-delete-directory' for tramp files." (defun tramp-handle-insert-directory (filename switches &optional wildcard full-directory-p) - "Like `insert-directory' for tramp files." - (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)) - ;; For the moment, we assume that the remote "ls" program does not - ;; grok "--dired". In the future, we should detect this on - ;; connection setup. - (when (string-match "^--dired\\s-+" switches) - (setq switches (replace-match "" nil t switches))) - (setq filename (expand-file-name filename)) - (with-parsed-tramp-file-name filename nil - (tramp-message-for-buffer - multi-method method user host 10 - "Inserting directory `ls %s %s', wildcard %s, fulldir %s" + "Like `insert-directory' for Tramp files." + (setq filename (expand-file-name filename)) + (with-parsed-tramp-file-name filename nil + (tramp-flush-file-property v localname) + (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)) + ;; For the moment, we assume that the remote "ls" program does not + ;; grok "--dired". In the future, we should detect this on + ;; connection setup. + (when (string-match "^--dired\\s-+" switches) + (setq switches (replace-match "" nil t switches))) + (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")) (when wildcard @@ -3459,80 +3443,44 @@ This is like `dired-recursive-delete-directory' for tramp files." (setq switches (concat "-d " switches))) (when wildcard (setq switches (concat switches " " wildcard))) - (save-excursion - ;; If `full-directory-p', we just say `ls -l FILENAME'. - ;; Else we chdir to the parent directory, then say `ls -ld BASENAME'. - (if full-directory-p - (tramp-send-command - multi-method method user host - (format "%s %s %s" - (tramp-get-ls-command multi-method method user host) - switches - (if wildcard - localname - (tramp-shell-quote-argument (concat localname "."))))) - (tramp-barf-unless-okay - multi-method method user host - (format "cd %s" (tramp-shell-quote-argument - (file-name-directory localname))) - nil 'file-error - "Couldn't `cd %s'" - (tramp-shell-quote-argument (file-name-directory localname))) - (tramp-send-command - multi-method method user host - (format "%s %s %s" - (tramp-get-ls-command multi-method method user host) - switches - (if wildcard - localname - (if (zerop (length (file-name-nondirectory localname))) - "" - (tramp-shell-quote-argument - (file-name-nondirectory localname))))))) - (sit-for 1) ;needed for rsh but not ssh? - (tramp-wait-for-output)) - ;; The following let-binding is used by code that's commented - ;; out. Let's leave the let-binding in for a while to see - ;; that the commented-out code is really not needed. Commenting-out - ;; happened on 2003-03-13. - (let ((old-pos (point))) - ;; We cannot use `insert-buffer' because the tramp buffer - ;; changes its contents before insertion due to calling - ;; `expand-file' and alike. - (insert - (with-current-buffer - (tramp-get-buffer multi-method method user host) - (buffer-string))) - ;; On XEmacs, we want to call (exchange-point-and-mark t), but - ;; that doesn't exist on Emacs, so we use this workaround instead. - ;; Since zmacs-region-stays doesn't exist in Emacs, this ought to - ;; be safe. Thanks to Daniel Pittman <daniel@danann.net>. - ;; (let ((zmacs-region-stays t)) - ;; (exchange-point-and-mark)) - (save-excursion - (tramp-send-command multi-method method user host "cd") - (tramp-wait-for-output)) - ;; For the time being, the XEmacs kludge is commented out. - ;; Please test it on various XEmacs versions to see if it works. - ;; ;; Another XEmacs specialty follows. What's the right way to do - ;; ;; it? - ;; (when (and (featurep 'xemacs) - ;; (eq major-mode 'dired-mode)) - ;; (save-excursion - ;; (require 'dired) - ;; (dired-insert-set-properties old-pos (point)))) - )))) - -;; Continuation of kluge to pacify byte-compiler. -;;(eval-when-compile -;; (when (eq (symbol-function 'dired-insert-set-properties) 'ignore) -;; (fmakunbound 'dired-insert-set-properties))) + ;; 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" + (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 + (file-name-directory localname))) + "Couldn't `cd %s'" + (tramp-shell-quote-argument (file-name-directory localname))) + (tramp-send-command + v + (format "%s %s %s" + (tramp-get-ls-command v) + switches + (if (or wildcard + (zerop (length (file-name-nondirectory localname)))) + "" + (tramp-shell-quote-argument + (file-name-nondirectory localname)))))) + ;; 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)))))) ;; CCC is this the right thing to do? (defun tramp-handle-unhandled-file-name-directory (filename) - "Like `unhandled-file-name-directory' for tramp files." - (with-parsed-tramp-file-name filename nil - (expand-file-name "~/"))) + "Like `unhandled-file-name-directory' for Tramp files." + (expand-file-name "~/")) ;; Canonicalization of file names. @@ -3553,7 +3501,7 @@ Doesn't do anything if the NAME does not start with a drive letter." name)) (defun tramp-handle-expand-file-name (name &optional dir) - "Like `expand-file-name' for tramp files. + "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 "/". @@ -3561,381 +3509,466 @@ the result will be a local, non-Tramp, filename." ;; 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 NAME is not a Tramp file, run the real handler. (if (not (tramp-tramp-file-p name)) - (tramp-run-real-handler 'expand-file-name - (list name nil)) + (tramp-run-real-handler 'expand-file-name (list name nil)) ;; Dissect NAME. (with-parsed-tramp-file-name name nil (unless (file-name-absolute-p localname) (setq localname (concat "~/" localname))) - (save-excursion - ;; Tilde expansion if necessary. This needs a shell which - ;; groks tilde expansion! The function `tramp-find-shell' is - ;; supposed to find such a shell on the remote host. Please - ;; tell me about it when this doesn't work on your system. - (when (string-match "\\`\\(~[^/]*\\)\\(.*\\)\\'" 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\\)?\\'" - (tramp-find-method multi-method method user host))) - (setq uname (concat uname (or user "root")))) - ;; CCC fanatic error checking? - (set-buffer (tramp-get-buffer multi-method method user host)) - (erase-buffer) - (tramp-send-command - multi-method method user host - (format "cd %s; pwd" uname) - t) - (tramp-wait-for-output) - (goto-char (point-min)) - (setq uname (buffer-substring (point) (tramp-line-end-position))) - (setq localname (concat uname fname)) - (erase-buffer))) - ;; 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 to - ;; "/", because on Windows there would be problems with UNC - ;; shares or Cygwin mounts. - (tramp-let-maybe directory-sep-char ?/ - (let ((default-directory "/")) - (tramp-make-tramp-file-name - multi-method (or method (tramp-find-default-method user host)) - user host - (tramp-drop-volume-letter - (tramp-run-real-handler 'expand-file-name - (list localname)))))))))) - -;; old version follows. it uses ".." to cross file handler -;; boundaries. -;; ;; Look if localname starts with "/../" construct. If this is -;; ;; the case, then we return a local name instead of a remote name. -;; (if (string-match "^/\\.\\./" localname) -;; (expand-file-name (substring localname 3)) -;; ;; No tilde characters in file name, do normal -;; ;; expand-file-name (this does "/./" and "/../"). We bind -;; ;; directory-sep-char here for XEmacs on Windows, which -;; ;; would otherwise use backslash. -;; (let ((directory-sep-char ?/)) -;; (tramp-make-tramp-file-name -;; multi-method method user host -;; (tramp-drop-volume-letter -;; (tramp-run-real-handler 'expand-file-name -;; (list localname)))))))))) - -;; Remote commands. - -(defvar tramp-async-proc nil - "Global variable keeping asynchronous process object. -Used in `tramp-handle-shell-command'") - -(defvar tramp-display-shell-command-buffer t - "Whether to display output buffer of `shell-command'. -This is necessary for handling DISPLAY of `process-file'.") - -(defun tramp-handle-shell-command (command &optional output-buffer error-buffer) - "Like `shell-command' for tramp files. -This will break if COMMAND prints a newline, followed by the value of -`tramp-end-of-output', followed by another newline." - ;; Asynchronous processes are far from being perfect. But it works at least - ;; for `find-grep-dired' and `find-name-dired' in Emacs 22. - (if (tramp-tramp-file-p default-directory) - (with-parsed-tramp-file-name default-directory nil - (let ((curbuf (current-buffer)) - (asynchronous (string-match "[ \t]*&[ \t]*\\'" command)) - status) - (unless output-buffer - (setq output-buffer - (get-buffer-create - (if asynchronous - "*Async Shell Command*" - "*Shell Command Output*"))) - (set-buffer output-buffer) - (erase-buffer)) - (unless (bufferp output-buffer) - (setq output-buffer (current-buffer))) - (set-buffer output-buffer) - ;; Tramp doesn't handle the asynchronous case by an asynchronous - ;; process. Instead of, another asynchronous process is opened - ;; which gets the output of the (synchronous) Tramp process - ;; via process-filter. ERROR-BUFFER is disabled. - (when asynchronous - (setq command (substring command 0 (match-beginning 0)) - error-buffer nil - tramp-async-proc (start-process (buffer-name output-buffer) - output-buffer "cat"))) - (save-excursion - (tramp-barf-unless-okay - multi-method method user host - (format "cd %s" (tramp-shell-quote-argument localname)) - nil 'file-error - "tramp-handle-shell-command: Couldn't `cd %s'" - (tramp-shell-quote-argument localname)) - ;; Define the process filter - (when asynchronous - (set-process-filter - (get-buffer-process - (tramp-get-buffer multi-method method user host)) - '(lambda (process string) - ;; Write the output into the Tramp Process - (save-current-buffer - (set-buffer (process-buffer process)) - (goto-char (point-max)) - (insert string)) - ;; Hand-over output to asynchronous process. - (let ((end - (string-match - (regexp-quote tramp-end-of-output) string))) - (when end - (setq string - (substring string 0 (1- (match-beginning 0))))) - (process-send-string tramp-async-proc string) - (when end - (set-process-filter process nil) - (process-send-eof tramp-async-proc)))))) - ;; Send the command - (tramp-send-command - multi-method method user host - (if error-buffer - (format "( %s ) 2>/tmp/tramp.$$.err; tramp_old_status=$?" - command) - (format "%s; tramp_old_status=$?" command))) - (unless asynchronous - (tramp-wait-for-output))) - (unless asynchronous - ;; We cannot use `insert-buffer' because the tramp buffer - ;; changes its contents before insertion due to calling - ;; `expand-file' and alike. - (insert - (with-current-buffer - (tramp-get-buffer multi-method method user host) - (buffer-string)))) - (when error-buffer - (save-excursion - (unless (bufferp error-buffer) - (setq error-buffer (get-buffer-create error-buffer))) - (tramp-send-command - multi-method method user host - "cat /tmp/tramp.$$.err") - (tramp-wait-for-output) - (set-buffer error-buffer) - ;; Same comment as above - (insert - (with-current-buffer - (tramp-get-buffer multi-method method user host) - (buffer-string))) - (tramp-send-command-and-check - multi-method method user host "rm -f /tmp/tramp.$$.err"))) - (save-excursion - (tramp-send-command multi-method method user host "cd") - (unless asynchronous - (tramp-wait-for-output)) - (tramp-send-command - multi-method method user host - (concat "tramp_set_exit_status $tramp_old_status;" - " echo tramp_exit_status $?")) - (unless asynchronous - (tramp-wait-for-output) - (goto-char (point-max)) - (unless (search-backward "tramp_exit_status " nil t) - (error "Couldn't find exit status of `%s'" command)) - (skip-chars-forward "^ ") - (setq status (read (current-buffer))))) - (unless (zerop (buffer-size)) - (when tramp-display-shell-command-buffer - (display-buffer output-buffer))) - (set-buffer curbuf) - status)) - ;; The following is only executed if something strange was - ;; happening. Emit a helpful message and do it anyway. - (message "tramp-handle-shell-command called with non-tramp directory: `%s'" - default-directory) - (tramp-run-real-handler 'shell-command - (list command output-buffer error-buffer)))) - -(defun tramp-handle-process-file (program &optional infile buffer display &rest args) + ;; 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" uname)) + (with-current-buffer (tramp-get-buffer v) + (goto-char (point-min)) + (buffer-substring (point) (tramp-compat-line-end-position))))) + (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. + (tramp-let-maybe directory-sep-char ?/ + (let ((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))))))))) + +(defun tramp-handle-substitute-in-file-name (filename) + "Like `substitute-in-file-name' for Tramp files. +\"//\" and \"/~\" substitute only in the local filename part. +If the URL Tramp syntax is chosen, \"//\" as method delimeter and \"/~\" at +beginning of local filename are not substituted." + (with-parsed-tramp-file-name filename nil + (if (equal tramp-syntax 'url) + ;; We need to check localname only. The other parts cannot contain + ;; "//" or "/~". + (if (and (> (length localname) 1) + (or (string-match "//" localname) + (string-match "/~" localname 1))) + (tramp-run-real-handler 'substitute-in-file-name (list filename)) + (tramp-make-tramp-file-name + (when method (substitute-in-file-name method)) + (when user (substitute-in-file-name user)) + (when host (substitute-in-file-name host)) + (when localname (substitute-in-file-name localname)))) + ;; Ignore in LOCALNAME everything before "//" or "/~". + (when (and (stringp localname) (string-match ".+?/\\(/\\|~\\)" localname)) + (setq filename + (concat (file-remote-p filename) + (replace-match "\\1" nil nil localname))) + ;; "/m:h:~" does not work for completion. We use "/m:h:~/". + (when (string-match "~$" filename) + (setq filename (concat filename "/")))) + (tramp-run-real-handler 'substitute-in-file-name (list filename))))) + +;; In XEmacs, electricity is implemented via a key map for ?/ and ?~, +;; which calls corresponding functions (see minibuf.el). +(when (fboundp 'minibuffer-electric-separator) + (mapc + '(lambda (x) + (eval + `(defadvice ,x + (around ,(intern (format "tramp-advice-%s" x)) activate) + "Invoke `substitute-in-file-name' for Tramp files." + (if (and (symbol-value 'minibuffer-electric-file-name-behavior) + (tramp-tramp-file-p (buffer-substring))) + ;; We don't need to handle `last-input-event', because + ;; due to the key map we know it must be ?/ or ?~. + (let ((s (concat (buffer-substring (point-min) (point)) + (string last-command-char)))) + (delete-region (point-min) (point)) + (insert (substitute-in-file-name s)) + (setq ad-return-value last-command-char)) + ad-do-it)))) + + '(minibuffer-electric-separator + minibuffer-electric-tilde))) + + +;;; Remote commands: + +(defun tramp-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))) + +;; 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-handle-start-file-process (name buffer program &rest args) + "Like `start-file-process' for Tramp files." + (with-parsed-tramp-file-name default-directory nil + (unwind-protect + (progn + ;; Set the new process properties. + (tramp-set-connection-property v "process-name" name) + (tramp-set-connection-property + v "process-buffer" + ;; BUFFER can be nil. + (get-buffer-create (or buffer (current-buffer)))) + ;; Activate narrowing in order to save BUFFER contents. + (with-current-buffer (tramp-get-connection-buffer v) + (narrow-to-region (point-max) (point-max))) + ;; Goto working directory. `tramp-send-command' opens a new + ;; connection. + (tramp-send-command + v (format "cd %s" (tramp-shell-quote-argument localname))) + ;; Send the command. + (tramp-send-command + v + (format "%s; exit" + (mapconcat 'tramp-shell-quote-argument + (cons program args) " ")) + nil t) ; nooutput + ;; Return process. + (tramp-get-connection-process v)) + ;; Save exit. + (with-current-buffer (tramp-get-connection-buffer v) + (widen) + (goto-char (point-max))) + (tramp-set-connection-property v "process-name" nil) + (tramp-set-connection-property v "process-buffer" nil)))) + +(defun tramp-handle-process-file + (program &optional infile destination display &rest args) "Like `process-file' for Tramp files." - (when infile (error "Implementation does not handle input from file")) - (when (and (numberp buffer) (zerop buffer)) + ;; The implementation is not complete yet. + (when (and (numberp destination) (zerop destination)) (error "Implementation does not handle immediate return")) - (when (consp buffer) (error "Implementation does not handle error files")) - (let ((tramp-display-shell-command-buffer display)) - (shell-command - (mapconcat 'tramp-shell-quote-argument (cons program args) " ") - buffer))) + + (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))) + + ;; Goto working directory. + (tramp-send-command + v (format "cd %s" (tramp-shell-quote-argument localname))) + ;; Send the command. It might not return in time, so we protect it. + (condition-case nil + (unwind-protect + (tramp-send-command v command) + ;; We should show the output anyway. + (when outbuf + (let ((output-string + (with-current-buffer (tramp-get-connection-buffer v) + (buffer-substring (point-min) (point-max))))) + (with-current-buffer outbuf + (insert output-string))) + (when display (display-buffer outbuf)))) + ;; When the user did interrupt, we should do it also. + (error + (kill-buffer (tramp-get-connection-buffer v)) + (setq ret 1))) + + ;; Check return code. + (unless ret (setq ret (tramp-send-command-and-check v nil))) + ;; Provide error file. + (when tmpstderr (rename-file tmpstderr (cadr destination) t)) + ;; Cleanup. + (when tmpinput (delete-file tmpinput)) + ;; Return exit status. + ret))) + +(defun tramp-local-call-process + (program &optional infile destination display &rest args) + "Calls `call-process' on the local host. +This is needed because for some Emacs flavors Tramp has +defadviced `call-process' to behave like `process-file'. The +Lisp error raised when PROGRAM is nil is trapped also, returning 1." + (let ((default-directory + (if (file-remote-p default-directory) + (tramp-compat-temporary-file-directory) + default-directory))) + (if (executable-find program) + (apply 'call-process program infile destination display args) + 1))) + +(defun tramp-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-handle-shell-command + (command &optional output-buffer error-buffer) + "Like `shell-command' for Tramp files." + (let* ((asynchronous (string-match "[ \t]*&[ \t]*\\'" command)) + (args (split-string (substring command 0 asynchronous) " ")) + (output-buffer + (cond + ((bufferp output-buffer) output-buffer) + ((stringp output-buffer) (get-buffer-create output-buffer)) + (output-buffer (current-buffer)) + (t (generate-new-buffer + (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))) + + (prog1 + ;; Run the process. + (if (integerp asynchronous) + (apply 'start-file-process "*Async Shell*" buffer args) + (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))) + ;; There's some output, display it. + (when (with-current-buffer output-buffer (> (point-max) (point-min))) + (if (functionp 'display-message-or-buffer) + (funcall (symbol-function 'display-message-or-buffer) output-buffer) + (pop-to-buffer output-buffer)))))) ;; File Editing. -(defsubst tramp-make-temp-file (filename) - (concat - (funcall (if (fboundp 'make-temp-file) 'make-temp-file 'make-temp-name) - (expand-file-name tramp-temp-name-prefix - (tramp-temporary-file-directory))) - (file-name-extension filename t))) +(defvar tramp-handle-file-local-copy-hook nil + "Normal hook to be run at the end of `tramp-handle-file-local-copy'.") (defun tramp-handle-file-local-copy (filename) - "Like `file-local-copy' for tramp files." + "Like `file-local-copy' for Tramp files." + (with-parsed-tramp-file-name filename nil - (let ((tramp-buf (tramp-get-buffer multi-method method user host)) - ;; We used to bind the following as late as possible. - ;; loc-enc and loc-dec were bound directly before the if - ;; statement that checks them. But the functions - ;; tramp-get-* might invoke the "are you awake" check in - ;; tramp-maybe-open-connection, which is an unfortunate time - ;; since we rely on the buffer contents at that spot. - (rem-enc (tramp-get-remote-encoding multi-method method user host)) - (rem-dec (tramp-get-remote-decoding multi-method method user host)) - (loc-enc (tramp-get-local-encoding multi-method method user host)) - (loc-dec (tramp-get-local-decoding multi-method method user host)) - tmpfil) + (let ((rem-enc (tramp-get-remote-coding v "remote-encoding")) + (loc-dec (tramp-get-local-coding v "local-decoding")) + (tmpfile (tramp-compat-make-temp-file filename))) (unless (file-exists-p filename) - (error "Cannot make local copy of non-existing file `%s'" - filename)) - (setq tmpfil (tramp-make-temp-file filename)) - - (cond ((tramp-method-out-of-band-p multi-method method user host) - ;; `copy-file' handles out-of-band methods - (copy-file filename tmpfil t t)) - - ((and rem-enc rem-dec) - ;; Use inline encoding for file transfer. - (save-excursion - ;; Following line for setting tramp-current-method, - ;; tramp-current-user, tramp-current-host. - (set-buffer tramp-buf) - (tramp-message 5 "Encoding remote file %s..." filename) - (tramp-barf-unless-okay - multi-method method user host - (concat rem-enc " < " (tramp-shell-quote-argument localname)) - nil 'file-error - "Encoding remote file failed, see buffer `%s' for details" - tramp-buf) - ;; Remove trailing status code - (goto-char (point-max)) - (delete-region (point) (progn (forward-line -1) (point))) - - (tramp-message 5 "Decoding remote file %s..." filename) - - ;; Here is where loc-enc and loc-dec used to be let-bound. - (if (and (symbolp loc-dec) (fboundp loc-dec)) - ;; If local decoding is a function, we call it. We - ;; must disable multibyte, because - ;; `uudecode-decode-region' doesn't handle it - ;; correctly. - (let ((tmpbuf (get-buffer-create " *tramp tmp*"))) - (set-buffer tmpbuf) - (erase-buffer) - (set-buffer-multibyte nil) - (insert-buffer-substring tramp-buf) - (tramp-message-for-buffer - multi-method method user host - 6 "Decoding remote file %s with function %s..." - filename loc-dec) - (set-buffer tmpbuf) - ;; Douglas Gray Stephens <DGrayStephens@slb.com> - ;; says that we need to strip tramp_exit_status - ;; line from the output here. Go to point-max, - ;; search backward for tramp_exit_status, delete - ;; between point and point-max if found. - (let ((coding-system-for-write 'binary)) - (funcall loc-dec (point-min) (point-max)) - (write-region (point-min) (point-max) tmpfil)) - (kill-buffer tmpbuf)) - ;; If tramp-decoding-function is not defined for this - ;; method, we invoke tramp-decoding-command instead. - (let ((tmpfil2 (tramp-make-temp-file filename))) - (write-region (point-min) (point-max) tmpfil2) - (tramp-message - 6 "Decoding remote file %s with command %s..." - filename loc-dec) - (tramp-call-local-coding-command - loc-dec tmpfil2 tmpfil) - (delete-file tmpfil2))) - (tramp-message-for-buffer - multi-method method user host - 5 "Decoding remote file %s...done" filename) - ;; Set proper permissions. - (set-file-modes tmpfil (file-modes filename)))) - - (t (error "Wrong method specification for `%s'" method))) - tmpfil))) + (tramp-error + v 'file-error + "Cannot make local copy of non-existing file `%s'" filename)) + + (cond + ;; `copy-file' handles direct copy and out-of-band methods. + ((or (tramp-local-host-p v) + (and (tramp-method-out-of-band-p v) + (> (nth 7 (file-attributes filename)) tramp-copy-size-limit))) + (copy-file filename tmpfile t t)) + + ;; Use inline encoding for file transfer. + (rem-enc + (save-excursion + (tramp-message v 5 "Encoding remote file %s..." filename) + (tramp-barf-unless-okay + v (format "%s < %s" rem-enc (tramp-shell-quote-argument localname)) + "Encoding remote file failed") + (tramp-message v 5 "Encoding remote file %s...done" filename) + + (tramp-message v 5 "Decoding remote file %s..." filename) + (if (and (symbolp loc-dec) (fboundp loc-dec)) + ;; If local decoding is a function, we call it. We must + ;; disable multibyte, because `uudecode-decode-region' + ;; doesn't handle it correctly. + (unwind-protect + (with-temp-buffer + (set-buffer-multibyte nil) + (insert-buffer-substring (tramp-get-buffer v)) + (tramp-message + v 5 "Decoding remote file %s with function %s..." + filename loc-dec) + (funcall loc-dec (point-min) (point-max)) + (let ((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))) + (let ((coding-system-for-write 'binary)) + (write-region (point-min) (point-max) tmpfile2)) + (tramp-message + v 5 "Decoding remote file %s with command %s..." + filename loc-dec) + (tramp-call-local-coding-command loc-dec tmpfile2 tmpfile) + (delete-file tmpfile2))) + (tramp-message v 5 "Decoding remote file %s...done" filename) + ;; Set proper permissions. + (set-file-modes tmpfile (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))) + + (run-hooks 'tramp-handle-file-local-copy-hook) + tmpfile))) (defun tramp-handle-file-remote-p (filename &optional identification connected) "Like `file-remote-p' for Tramp files." (when (tramp-tramp-file-p filename) (with-parsed-tramp-file-name filename nil (and (or (not connected) - (let ((p (get-buffer-process - (tramp-get-buffer multi-method method user host)))) + (let ((p (tramp-get-connection-process v))) (and p (processp p) (memq (process-status p) '(run open))))) (cond ((eq identification 'method) method) ((eq identification 'user) user) ((eq identification 'host) host) - (t (tramp-make-tramp-file-name - multi-method method user host ""))))))) + ((eq identification 'localname) localname) + (t (tramp-make-tramp-file-name method user host ""))))))) (defun tramp-handle-insert-file-contents (filename &optional visit beg end replace) - "Like `insert-file-contents' for tramp files." + "Like `insert-file-contents' for Tramp files." (barf-if-buffer-read-only) (setq filename (expand-file-name filename)) - (with-parsed-tramp-file-name filename nil - (if (not (file-exists-p filename)) - (progn - (when visit - (setq buffer-file-name filename) - (set-visited-file-modtime) - (set-buffer-modified-p nil)) - (signal 'file-error - (format "File `%s' not found on remote host" filename)) - (list (expand-file-name filename) 0)) - ;; `insert-file-contents-literally' takes care to avoid calling - ;; jka-compr. By let-binding inhibit-file-name-operation, we - ;; propagate that care to the file-local-copy operation. - (let ((local-copy - (let ((inhibit-file-name-operation - (when (eq inhibit-file-name-operation - 'insert-file-contents) - 'file-local-copy))) - (file-local-copy filename))) - coding-system-used result) - (tramp-message-for-buffer - multi-method method user host - 9 "Inserting local temp file `%s'..." local-copy) - (setq result (insert-file-contents local-copy nil beg end replace)) + (let (coding-system-used result) + (with-parsed-tramp-file-name filename nil + + (if (not (file-exists-p filename)) + (progn + (when visit + (setq buffer-file-name filename) + (set-visited-file-modtime) + (set-buffer-modified-p nil)) + ;; We don't raise a Tramp error, because it might be + ;; suppressed, like in `find-file-noselect-1'. + (signal 'file-error (list "File not found on remote host" filename)) + (list (expand-file-name filename) 0)) + + (if (and (tramp-local-host-p v) + (file-readable-p localname)) + ;; Short track: if we are on the local host, we can run directly. + (setq result (insert-file-contents localname visit beg end replace)) + + ;; `insert-file-contents-literally' takes care to avoid calling + ;; jka-compr. By let-binding inhibit-file-name-operation, we + ;; propagate that care to the file-local-copy operation. + (let ((local-copy + (let ((inhibit-file-name-operation + (when (eq inhibit-file-name-operation + 'insert-file-contents) + 'file-local-copy))) + (file-local-copy filename)))) + (tramp-message v 4 "Inserting local temp file `%s'..." local-copy) + (setq result (insert-file-contents local-copy nil beg end replace)) + ;; Now `last-coding-system-used' has right value. Remember it. + (when (boundp 'last-coding-system-used) + (setq coding-system-used (symbol-value 'last-coding-system-used))) + (tramp-message v 4 "Inserting local temp file `%s'...done" local-copy) + (delete-file local-copy) + (when (boundp 'last-coding-system-used) + (set 'last-coding-system-used coding-system-used)))) + (when visit + (setq buffer-read-only (file-writable-p filename)) (setq buffer-file-name filename) (set-visited-file-modtime) (set-buffer-modified-p nil)) - ;; Now `last-coding-system-used' has right value. Remember it. - (when (boundp 'last-coding-system-used) - (setq coding-system-used (symbol-value 'last-coding-system-used))) - (tramp-message-for-buffer - multi-method method user host - 9 "Inserting local temp file `%s'...done" local-copy) - (delete-file local-copy) - (when (boundp 'last-coding-system-used) - (set 'last-coding-system-used coding-system-used)) (list (expand-file-name filename) (cadr result)))))) +;; This is needed for XEmacs only. Code stolen from files.el. +(defun tramp-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)) + (if find-buffer-file-type-function + (fset 'find-buffer-file-type find-buffer-file-type-function) + (fmakunbound 'find-buffer-file-type))))) (defun tramp-handle-find-backup-file-name (filename) - "Like `find-backup-file-name' for tramp files." + "Like `find-backup-file-name' for Tramp files." (with-parsed-tramp-file-name filename nil ;; We set both variables. It doesn't matter whether it is ;; Emacs or XEmacs @@ -3950,8 +3983,7 @@ This will break if COMMAND prints a newline, followed by the value of (if (and (stringp (cdr x)) (file-name-absolute-p (cdr x)) (not (tramp-file-name-p (cdr x)))) - (tramp-make-tramp-file-name - multi-method method user host (cdr x)) + (tramp-make-tramp-file-name method user host (cdr x)) (cdr x)))) (symbol-value 'tramp-backup-directory-alist)) (symbol-value 'backup-directory-alist)))) @@ -3969,7 +4001,7 @@ This will break if COMMAND prints a newline, followed by the value of (file-name-absolute-p (car (cdr x))) (not (tramp-file-name-p (car (cdr x))))) (tramp-make-tramp-file-name - multi-method method user host (car (cdr x))) + method user host (car (cdr x))) (car (cdr x)))) (cdr (cdr x)))) (symbol-value 'tramp-bkup-backup-directory-info)) @@ -3978,255 +4010,246 @@ This will break if COMMAND prints a newline, followed by the value of (tramp-run-real-handler 'find-backup-file-name (list filename))))) (defun tramp-handle-make-auto-save-file-name () - "Like `make-auto-save-file-name' for tramp files. + "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)) + (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) - (mapcar + (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-temporary-file-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))) - ;; jka-compr doesn't like auto-saving, so by appending "~" to the - ;; file name we make sure that jka-compr isn't used for the - ;; auto-save file. - (let ((buffer-file-name - (if tramp-auto-save-directory - (expand-file-name - (tramp-subst-strs-in-string - '(("_" . "|") - ("/" . "_a") - (":" . "_b") - ("|" . "__") - ("[" . "_l") - ("]" . "_r")) - (buffer-file-name)) - tramp-auto-save-directory) - (buffer-file-name)))) - ;; 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)))))) + ;; 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))))) +(defvar tramp-handle-write-region-hook nil + "Normal hook to be run at the end of `tramp-handle-write-region'.") -;; CCC grok APPEND, LOCKNAME, CONFIRM +;; CCC grok APPEND, LOCKNAME (defun tramp-handle-write-region (start end filename &optional append visit lockname confirm) - "Like `write-region' for tramp files." - (unless (eq append nil) - (error "Cannot append to file using tramp (`%s')" filename)) + "Like `write-region' for Tramp files." (setq filename (expand-file-name filename)) - ;; Following part commented out because we don't know what to do about - ;; file locking, and it does not appear to be a problem to ignore it. - ;; Ange-ftp ignores it, too. - ;; (when (and lockname (stringp lockname)) - ;; (setq lockname (expand-file-name lockname))) - ;; (unless (or (eq lockname nil) - ;; (string= lockname filename)) - ;; (error - ;; "tramp-handle-write-region: LOCKNAME must be nil or equal FILENAME")) - ;; XEmacs takes a coding system as the 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)) - (error "File not overwritten"))) (with-parsed-tramp-file-name filename nil - (let ((curbuf (current-buffer)) - (rem-enc (tramp-get-remote-encoding multi-method method user host)) - (rem-dec (tramp-get-remote-decoding multi-method method user host)) - (loc-enc (tramp-get-local-encoding multi-method method user host)) - (loc-dec (tramp-get-local-decoding multi-method method user host)) - (trampbuf (get-buffer-create "*tramp output*")) - (modes (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 - tmpfil) - ;; Write region into a tmp file. This isn't really needed if we - ;; use an encoding function, but currently we use it always - ;; because this makes the logic simpler. - (setq tmpfil (tramp-make-temp-file filename)) - ;; Set current buffer. If connection wasn't open, `file-modes' has - ;; changed it accidently. - (set-buffer curbuf) - ;; We say `no-message' here because we don't want the visited file - ;; modtime data to be clobbered from the temp file. We call - ;; `set-visited-file-modtime' ourselves later on. - (tramp-run-real-handler - 'write-region - (if confirm ; don't pass this arg unless defined for backward compat. - (list start end tmpfil append 'no-message lockname confirm) - (list start end tmpfil append 'no-message lockname))) - ;; Now, `last-coding-system-used' has the right value. Remember it. - (when (boundp 'last-coding-system-used) - (setq coding-system-used (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. - (when modes (set-file-modes tmpfil modes)) - ;; This is a bit lengthy due to the different methods possible for - ;; file transfer. First, we check whether the method uses an rcp - ;; program. If so, we call it. Otherwise, both encoding and - ;; decoding command must be specified. However, if the method - ;; _also_ specifies an encoding function, then that is used for - ;; encoding the contents of the tmp file. - (cond ((tramp-method-out-of-band-p multi-method method user host) - ;; `copy-file' handles out-of-band methods - (copy-file tmpfil filename t t)) - - ((and rem-enc rem-dec) - ;; Use inline file transfer - (let ((tmpbuf (get-buffer-create " *tramp file transfer*"))) - (save-excursion - ;; Encode tmpfil into tmpbuf - (tramp-message-for-buffer multi-method method user host - 5 "Encoding region...") - (set-buffer tmpbuf) - (erase-buffer) - ;; Use encoding function or command. - (if (and (symbolp loc-enc) (fboundp loc-enc)) - (progn - (tramp-message-for-buffer - multi-method method user host - 6 "Encoding region using function `%s'..." - (symbol-name loc-enc)) - (insert-file-contents-literally tmpfil) - ;; CCC. The following `let' is a workaround for - ;; the base64.el that comes with pgnus-0.84. If - ;; both of the following conditions are - ;; satisfied, it tries to write to a local file - ;; in default-directory, but at this point, - ;; default-directory is remote. - ;; (CALL-PROCESS-REGION can't write to remote - ;; files, it seems.) The file in question is a - ;; tmp file anyway. - (let ((default-directory - (tramp-temporary-file-directory))) - (funcall loc-enc (point-min) (point-max))) - (goto-char (point-max)) - (unless (bolp) - (newline))) - (tramp-message-for-buffer - multi-method method user host - 6 "Encoding region using command `%s'..." loc-enc) - (unless (equal 0 (tramp-call-local-coding-command - loc-enc tmpfil t)) - (pop-to-buffer trampbuf) - (error (concat "Cannot write to `%s', local encoding" - " command `%s' failed") - filename loc-enc))) - ;; Send tmpbuf into remote decoding command which - ;; writes to remote file. Because this happens on the - ;; remote host, we cannot use the function. - (tramp-message-for-buffer - multi-method method user host - 5 "Decoding region into remote file %s..." filename) - (tramp-send-command - multi-method method user host - (format "%s >%s <<'EOF'" - rem-dec - (tramp-shell-quote-argument localname))) - (set-buffer tmpbuf) - (tramp-message-for-buffer - multi-method method user host - 6 "Sending data to remote host...") - (tramp-send-string multi-method method user host - (buffer-string)) - ;; wait for remote decoding to complete - (tramp-message-for-buffer - multi-method method user host - 6 "Sending end of data token...") - (tramp-send-command - multi-method method user host "EOF" nil t) - (tramp-message-for-buffer - multi-method method user host 6 - "Waiting for remote host to process data...") - (set-buffer (tramp-get-buffer multi-method method user host)) - (tramp-wait-for-output) - (tramp-barf-unless-okay - multi-method method user host nil nil 'file-error - (concat "Couldn't write region to `%s'," - " decode using `%s' failed") - filename rem-dec) - (tramp-message 5 "Decoding region into remote file %s...done" - filename) - (kill-buffer tmpbuf)))) - (t - (error - (concat "Method `%s' should specify both encoding and " - "decoding command or an rcp program") - method))) - (delete-file tmpfil) - (unless (equal curbuf (current-buffer)) - (error "Buffer has changed from `%s' to `%s'" - curbuf (current-buffer))) + (unless (null append) + (tramp-error + v 'file-error "Cannot append to file using Tramp (`%s')" filename)) + ;; Following part commented out because we don't know what to do about + ;; file locking, and it does not appear to be a problem to ignore it. + ;; Ange-ftp ignores it, too. + ;; (when (and lockname (stringp lockname)) + ;; (setq lockname (expand-file-name lockname))) + ;; (unless (or (eq lockname nil) + ;; (string= lockname filename)) + ;; (error + ;; "tramp-handle-write-region: LOCKNAME must be nil or equal FILENAME")) + + ;; XEmacs takes a coding system as the 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 (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. + (write-region start end localname append 'no-message lockname confirm) + + (let ((rem-dec (tramp-get-remote-coding v "remote-decoding")) + (loc-enc (tramp-get-local-coding v "local-encoding")) + (modes (save-excursion (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 (tramp-compat-make-temp-file filename))) + + ;; We say `no-message' here because we don't want the + ;; visited file modtime data to be clobbered from the temp + ;; file. We call `set-visited-file-modtime' ourselves later + ;; on. + (tramp-run-real-handler + 'write-region + (list start end tmpfile append 'no-message lockname confirm)) + ;; 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. + (when modes (set-file-modes tmpfile modes)) + + ;; This is a bit lengthy due to the different methods + ;; possible for file transfer. First, we check whether the + ;; method uses an rcp program. If so, we call it. + ;; Otherwise, both encoding and decoding command must be + ;; specified. However, if the method _also_ specifies an + ;; encoding function, then that is used for encoding the + ;; contents of the tmp file. + (cond + ;; `rename-file' handles direct copy and out-of-band methods. + ((or (tramp-local-host-p v) + (and (tramp-method-out-of-band-p v) + (integerp start) + (> (- end start) tramp-copy-size-limit))) + (rename-file tmpfile filename t)) + + ;; Use inline file transfer + (rem-dec + ;; Encode tmpfile + (tramp-message v 5 "Encoding region...") + (unwind-protect + (with-temp-buffer + ;; Use encoding function or command. + (if (and (symbolp loc-enc) (fboundp loc-enc)) + (progn + (tramp-message + v 5 "Encoding region using function `%s'..." + (symbol-name loc-enc)) + (let ((coding-system-for-read 'binary)) + (insert-file-contents-literally tmpfile)) + ;; CCC. The following `let' is a workaround + ;; for the base64.el that comes with + ;; pgnus-0.84. If both of the following + ;; conditions are satisfied, it tries to write + ;; to a local file in default-directory, but + ;; at this point, default-directory is remote. + ;; (CALL-PROCESS-REGION can't write to remote + ;; files, it seems.) The file in question is + ;; a tmp file anyway. + (let ((default-directory + (tramp-compat-temporary-file-directory))) + (funcall loc-enc (point-min) (point-max)))) + + (tramp-message + v 5 "Encoding region using command `%s'..." loc-enc) + (unless (equal 0 (tramp-call-local-coding-command + loc-enc tmpfile t)) + (tramp-error + v 'file-error + "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. + (goto-char (point-max)) + (unless (bolp) (newline)) + (tramp-message + v 5 "Decoding region into remote file %s..." filename) + (tramp-send-command + v + (format + "%s >%s <<'EOF'\n%sEOF" + rem-dec + (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-local-call-process "cksum" tmpfile t)) + ;; cksum runs remotely. + (zerop + (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))) + (tramp-message + v 5 "Decoding region into remote file %s...done" filename) + (tramp-flush-file-property v localname)) + + ;; 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)))) + + ;; Set file modification time. (when (or (eq visit t) (stringp visit)) (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. + ;; 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-attributes filename)))) - ;; Make `last-coding-system-used' have the right value. - (when (boundp 'last-coding-system-used) - (set 'last-coding-system-used coding-system-used)) - (when (or (eq visit t) - (eq visit nil) - (stringp visit)) - (message "Wrote %s" filename))))) - -;; Call down to the real handler. -;; Because EFS does not play nicely with TRAMP (both systems match a -;; TRAMP file name) it is needed to disable efs as well as tramp for the -;; operation. -;; -;; Other than that, this is the canon file-handler code that the doco -;; says should be used here. Which is nice. -;; -;; Under XEmacs current, EFS also hooks in as -;; efs-sifn-handler-function to handle any filename with environment -;; variables. This has two implications: -;; 1) That EFS may not be completely dead (yet) for TRAMP filenames -;; 2) That TRAMP might want to do the same thing. -;; Details as they come in. -;; -;; Daniel Pittman <daniel@danann.net> -;; (defun tramp-run-real-handler (operation args) -;; "Invoke normal file name handler for OPERATION. -;; This inhibits EFS and Ange-FTP, too, because they conflict with tramp. -;; First arg specifies the OPERATION, remaining ARGS are passed to the -;; OPERATION." -;; (let ((inhibit-file-name-handlers -;; (list 'tramp-file-name-handler -;; 'efs-file-handler-function -;; 'ange-ftp-hook-function -;; (and (eq inhibit-file-name-operation operation) -;; inhibit-file-name-handlers))) -;; (inhibit-file-name-operation operation)) -;; (apply operation args))) + ;; Set the ownership. + (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)))) ;;;###autoload (progn (defun tramp-run-real-handler (operation args) @@ -4244,10 +4267,6 @@ pass to the OPERATION." (inhibit-file-name-operation operation)) (apply operation args)))) -;; This function is used from `tramp-completion-file-name-handler' -;; functions only, if `tramp-completion-mode-p' is true. But this -;; cannot be checked here because the check is based on a full -;; filename, not available for all basic I/O operations. ;;;###autoload (progn (defun tramp-completion-run-real-handler (operation args) "Invoke `tramp-file-name-handler' for OPERATION. @@ -4292,6 +4311,8 @@ ARGS are the arguments OPERATION has been called with." 'load 'make-directory 'make-directory-internal 'set-file-modes 'substitute-in-file-name 'unhandled-file-name-directory 'vc-registered + ; Emacs 22 only + 'set-file-times ; XEmacs only 'abbreviate-file-name 'create-file-buffer 'dired-file-modtime 'dired-make-compressed-filename @@ -4320,53 +4341,64 @@ ARGS are the arguments OPERATION has been called with." (nth 2 args)) ; BUF ((member operation - (list 'make-auto-save-file-name - 'set-visited-file-modtime 'verify-visited-file-modtime - ; XEmacs only + (list 'set-visited-file-modtime 'verify-visited-file-modtime + ; since Emacs 22 only + 'make-auto-save-file-name + ; XEmacs only 'backup-buffer)) (buffer-file-name (if (bufferp (nth 0 args)) (nth 0 args) (current-buffer)))) ; COMMAND ((member operation - (list 'dired-call-process + (list ; not in Emacs 23 + 'dired-call-process ; Emacs only 'shell-command - ; Emacs 22 only + ; since Emacs 22 only 'process-file + ; since Emacs 23 only + 'start-file-process ; XEmacs only - 'dired-print-file 'dired-shell-call-process)) + 'dired-print-file 'dired-shell-call-process + ; nowhere yet + 'executable-find 'start-process 'call-process)) default-directory) ; unknown file primitive (t (error "unknown file I/O primitive: %s" operation)))) (defun tramp-find-foreign-file-name-handler (filename) "Return foreign file name handler if exists." - (when (tramp-tramp-file-p filename) - (let (elt - res - (handler-alist tramp-foreign-file-name-handler-alist)) - (while handler-alist - (setq elt (car handler-alist) - handler-alist (cdr handler-alist)) - (when (funcall (car elt) filename) - (setq handler-alist nil) - (setq res (cdr elt)))) - res))) + (when (and (stringp filename) (tramp-tramp-file-p filename)) + (let ((v (tramp-dissect-file-name filename t)) + (handler tramp-foreign-file-name-handler-alist) + elt res) + ;; When we are not fully sure that filename completion is safe, + ;; we should not return a handler. + (when (or (tramp-file-name-method v) (tramp-file-name-user v) + (and (tramp-file-name-host v) + (not (member (tramp-file-name-host v) + (mapcar 'car tramp-methods)))) + (not (tramp-completion-mode-p))) + (while handler + (setq elt (car handler) + handler (cdr handler)) + (when (funcall (car elt) filename) + (setq handler nil + res (cdr elt)))) + res)))) ;; Main function. ;;;###autoload (defun tramp-file-name-handler (operation &rest args) "Invoke Tramp file name handler. -Falls back to normal file name handler if no tramp file name handler exists." -;; (setq edebug-trace t) -;; (edebug-trace "%s" (with-output-to-string (backtrace))) +Falls back to normal file name handler if no Tramp file name handler exists." (save-match-data (let* ((filename (apply 'tramp-file-name-for-operation operation args)) - (completion (tramp-completion-mode-p filename)) + (completion (tramp-completion-mode-p)) (foreign (tramp-find-foreign-file-name-handler filename))) (with-parsed-tramp-file-name filename nil (cond - ;; When we are in completion mode, some operations shouldn' be + ;; When we are in completion mode, some operations shouldn't be ;; handled by backend. ((and completion (zerop (length localname)) (memq operation '(file-exists-p file-directory-p))) @@ -4379,7 +4411,6 @@ Falls back to normal file name handler if no tramp file name handler exists." ;; Nothing to do for us. (t (tramp-run-real-handler operation args))))))) - ;; In Emacs, there is some concurrency due to timers. If a timer ;; interrupts Tramp and wishes to use the same connection buffer as ;; the "main" Emacs, then garbage might occur in the connection @@ -4411,7 +4442,7 @@ preventing reentrant calls of Tramp.") "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)) - (signal 'file-error "Forbidden reentrant call of Tramp")) + (signal 'file-error (list "Forbidden reentrant call of Tramp"))) (let ((tl tramp-locked)) (unwind-protect (progn @@ -4426,10 +4457,15 @@ Fall back to normal file name handler if no Tramp handler exists." ;;;###autoload (progn (defun tramp-completion-file-name-handler (operation &rest args) - "Invoke tramp file name completion handler. -Falls back to normal file name handler if no tramp file name handler exists." + "Invoke Tramp file name completion handler. +Falls back to normal file name handler if no Tramp file name handler exists." ;; (setq edebug-trace t) ;; (edebug-trace "%s" (with-output-to-string (backtrace))) + +;; (mapcar 'trace-function-background +;; (mapcar 'intern +;; (all-completions "tramp-" obarray 'functionp))) + (let ((fn (assoc operation tramp-completion-file-name-handler-alist))) (if fn (save-match-data (apply (cdr fn) args)) @@ -4437,7 +4473,12 @@ Falls back to normal file name handler if no tramp file name handler exists." ;;;###autoload (defsubst tramp-register-file-name-handler () - "Add tramp file name handler to `file-name-handler-alist'." + "Add Tramp file name handler to `file-name-handler-alist'." + ;; Remove autoloaded handler from file name handler alist. Useful, + ;; if `tramp-syntax' has been changed. + (let ((a1 (rassq 'tramp-file-name-handler file-name-handler-alist))) + (setq file-name-handler-alist (delete a1 file-name-handler-alist))) + ;; Add the handler. (add-to-list 'file-name-handler-alist (cons tramp-file-name-regexp 'tramp-file-name-handler)) ;; If jka-compr is already loaded, move it to the front of @@ -4447,15 +4488,29 @@ Falls back to normal file name handler if no tramp file name handler exists." (setq file-name-handler-alist (cons jka (delete jka file-name-handler-alist)))))) +;; `tramp-file-name-handler' must be registered before evaluation of +;; site-start and init files, because there might exist remote files +;; already, f.e. files kept via recentf-mode. +;;;###autoload(tramp-register-file-name-handler) +(tramp-register-file-name-handler) + ;;;###autoload (defsubst tramp-register-completion-file-name-handler () - "Add tramp completion file name handler to `file-name-handler-alist'." + "Add Tramp completion file name handler to `file-name-handler-alist'." + ;; Remove autoloaded handler from file name handler alist. Useful, + ;; if `tramp-syntax' has been changed. + (let ((a1 (rassq + 'tramp-completion-file-name-handler file-name-handler-alist))) + (setq file-name-handler-alist (delete a1 file-name-handler-alist))) ;; `partial-completion-mode' is unknown in XEmacs. So we should ;; load it unconditionally there. In the GNU Emacs case, method/ ;; user/host name completion shall be bound to `partial-completion-mode'. + ;; `ido-mode' and `icy-mode' are other packages which extend file + ;; name completion. (when (or (not (boundp 'partial-completion-mode)) (symbol-value 'partial-completion-mode) - (featurep 'ido)) + (featurep 'ido) + (featurep 'icicles)) (add-to-list 'file-name-handler-alist (cons tramp-completion-file-name-regexp 'tramp-completion-file-name-handler)) @@ -4467,17 +4522,12 @@ Falls back to normal file name handler if no tramp file name handler exists." (setq file-name-handler-alist (cons jka (delete jka file-name-handler-alist)))))) -;; `tramp-file-name-handler' must be registered before evaluation of -;; site-start and init files, because there might exist remote files -;; already, f.e. files kept via recentf-mode. -;;;###autoload(tramp-register-file-name-handler) ;; During autoload, it shall be checked whether ;; `partial-completion-mode' is active. Therefore registering of ;; `tramp-completion-file-name-handler' will be delayed. ;;;###autoload(add-hook ;;;###autoload 'after-init-hook ;;;###autoload '(lambda () (tramp-register-completion-file-name-handler))) -(tramp-register-file-name-handler) (tramp-register-completion-file-name-handler) ;;;###autoload @@ -4491,67 +4541,7 @@ Falls back to normal file name handler if no tramp file name handler exists." (add-hook 'tramp-unload-hook 'tramp-unload-file-name-handlers) - -;;; Interactions with other packages: - -;; -- complete.el -- - -;; This function contributed by Ed Sabol -(defun tramp-handle-expand-many-files (name) - "Like `PC-expand-many-files' for tramp files." - (with-parsed-tramp-file-name name nil - (save-match-data - (if (or (string-match "\\*" name) - (string-match "\\?" name) - (string-match "\\[.*\\]" name)) - (save-excursion - (let (bufstr) - ;; CCC: To do it right, we should quote certain characters - ;; in the file name, but since the echo command is going to - ;; break anyway when there are spaces in the file names, we - ;; don't bother. - ;;-(let ((comint-file-name-quote-list - ;;- (set-difference tramp-file-name-quote-list - ;;- '(?\* ?\? ?[ ?])))) - ;;- (tramp-send-command - ;;- multi-method method user host - ;;- (format "echo %s" (comint-quote-filename localname))) - ;;- (tramp-wait-for-output)) - (tramp-send-command multi-method method user host - (format "echo %s" localname)) - (tramp-wait-for-output) - (setq bufstr (buffer-substring (point-min) - (tramp-line-end-position))) - (goto-char (point-min)) - (if (string-equal localname bufstr) - nil - (insert "(\"") - (while (search-forward " " nil t) - (delete-backward-char 1) - (insert "\" \"")) - (goto-char (point-max)) - (delete-backward-char 1) - (insert "\")") - (goto-char (point-min)) - (mapcar - (function (lambda (x) - (tramp-make-tramp-file-name multi-method method - user host x))) - (read (current-buffer)))))) - (list (expand-file-name name)))))) - -(eval-after-load "complete" - '(progn - (defadvice PC-expand-many-files - (around tramp-advice-PC-expand-many-files (name) activate) - "Invoke `tramp-handle-expand-many-files' for tramp files." - (if (tramp-tramp-file-p name) - (setq ad-return-value (tramp-handle-expand-many-files name)) - ad-do-it)) - (add-hook 'tramp-unload-hook - '(lambda () (ad-unadvise 'PC-expand-many-files))))) - -;;; File name handler functions for completion mode +;;; File name handler functions for completion mode: (defvar tramp-completion-mode nil "If non-nil, external packages signal that they are in file name completion. @@ -4562,139 +4552,121 @@ but <TAB>, <SPACE> or ?\\? for file name completion. This variable should never be set globally, the intention is to let-bind it.") ;; Necessary because `tramp-file-name-regexp-unified' and -;; `tramp-completion-file-name-regexp-unified' aren't different. -;; If nil, `tramp-completion-run-real-handler' is called (i.e. forwarding to -;; `tramp-file-name-handler'). Otherwise, it takes `tramp-run-real-handler'. -;; Using `last-input-event' is a little bit risky, because completing a file -;; might require loading other files, like "~/.netrc", and for them it -;; shouldn't be decided based on that variable. On the other hand, those files -;; shouldn't have partial tramp file name syntax. Maybe another variable should -;; be introduced overwriting this check in such cases. Or we change tramp -;; file name syntax in order to avoid ambiguities, like in XEmacs ... -;; In case of non unified file names it can be always true (and wouldn't be -;; necessary, because there are different regexp). -(defun tramp-completion-mode-p (file) +;; `tramp-completion-file-name-regexp-unified' aren't different. If +;; nil, `tramp-completion-run-real-handler' is called (i.e. forwarding +;; to `tramp-file-name-handler'). Otherwise, it takes +;; `tramp-run-real-handler'. Using `last-input-event' is a little bit +;; risky, because completing a file might require loading other files, +;; like "~/.netrc", and for them it shouldn't be decided based on that +;; variable. On the other hand, those files shouldn't have partial +;; Tramp file name syntax. Maybe another variable should be introduced +;; overwriting this check in such cases. Or we change Tramp file name +;; syntax in order to avoid ambiguities, like in XEmacs ... +(defun tramp-completion-mode-p () "Checks whether method / user name / host name completion is active." - (cond - (tramp-completion-mode t) - ((string-match "^/.*:.*:$" file) nil) - ((string-match - (concat tramp-prefix-regexp - "\\(" tramp-method-regexp "\\)" tramp-postfix-single-method-regexp "$") - file) - (member (match-string 1 file) (mapcar 'car tramp-methods))) - ((or - ;; Emacs. - (equal last-input-event 'tab) - (and (natnump last-input-event) - (or - ;; ?\t has event-modifier 'control - (char-equal last-input-event ?\t) - (and (not (event-modifiers last-input-event)) - (or (char-equal last-input-event ?\?) - (char-equal last-input-event ?\ ))))) - ;; XEmacs. - (and (featurep 'xemacs) - ;; `last-input-event' might be nil. - (not (null last-input-event)) - ;; `last-input-event' may have no character approximation. - (funcall (symbol-function 'event-to-character) last-input-event) - (or - ;; ?\t has event-modifier 'control - (char-equal - (funcall (symbol-function 'event-to-character) - last-input-event) ?\t) - (and (not (event-modifiers last-input-event)) - (or (char-equal - (funcall (symbol-function 'event-to-character) - last-input-event) ?\?) - (char-equal - (funcall (symbol-function 'event-to-character) - last-input-event) ?\ )))))) - t))) + (or + ;; Signal from outside. + tramp-completion-mode + ;; Emacs. + (equal last-input-event 'tab) + (and (natnump last-input-event) + (or + ;; ?\t has event-modifier 'control. + (char-equal last-input-event ?\t) + (and (not (event-modifiers last-input-event)) + (or (char-equal last-input-event ?\?) + (char-equal last-input-event ?\ ))))) + ;; XEmacs. + (and (featurep 'xemacs) + ;; `last-input-event' might be nil. + (not (null last-input-event)) + ;; `last-input-event' may have no character approximation. + (funcall (symbol-function 'event-to-character) last-input-event) + (or + ;; ?\t has event-modifier 'control. + (char-equal + (funcall (symbol-function 'event-to-character) + last-input-event) ?\t) + (and (not (event-modifiers last-input-event)) + (or (char-equal + (funcall (symbol-function 'event-to-character) + last-input-event) ?\?) + (char-equal + (funcall (symbol-function 'event-to-character) + last-input-event) ?\ ))))))) ;; Method, host name and user name completion. ;; `tramp-completion-dissect-file-name' returns a list of ;; tramp-file-name structures. For all of them we return possible completions. ;;;###autoload (defun tramp-completion-handle-file-name-all-completions (filename directory) - "Like `file-name-all-completions' for partial tramp files." - - (unwind-protect - ;; We need to reset `tramp-completion-mode'. - (progn - (setq tramp-completion-mode t) - (let* - ((fullname (concat directory filename)) - ;; possible completion structures - (v (tramp-completion-dissect-file-name fullname)) - result result1) - - (while v - (let* ((car (car v)) - (multi-method (tramp-file-name-multi-method car)) - (method (tramp-file-name-method car)) - (user (tramp-file-name-user car)) - (host (tramp-file-name-host car)) - (localname (tramp-file-name-localname car)) - (m (tramp-find-method multi-method method user host)) - (tramp-current-user user) ; see `tramp-parse-passwd' - all-user-hosts) - - (unless (or multi-method ;; Not handled (yet). - localname) ;; Nothing to complete - - (if (or user host) - - ;; Method dependent user / host combinations - (progn - (mapcar - (lambda (x) - (setq all-user-hosts - (append all-user-hosts - (funcall (nth 0 x) (nth 1 x))))) - (tramp-get-completion-function m)) - - (setq result (append result - (mapcar - (lambda (x) - (tramp-get-completion-user-host - method user host (nth 0 x) (nth 1 x))) - (delq nil all-user-hosts))))) - - ;; Possible methods - (setq result - (append result (tramp-get-completion-methods m))))) - - (setq v (cdr v)))) - - ;; unify list, remove nil elements - (while result - (let ((car (car result))) - (when car (add-to-list - 'result1 (substring car (length directory)))) - (setq result (cdr result)))) - - ;; Complete local parts - (append - result1 - (condition-case nil - (if result1 - ;; "/ssh:" does not need to be expanded as hostname. - (tramp-run-real-handler - 'file-name-all-completions (list filename directory)) - ;; No method/user/host found to be expanded. - (tramp-completion-run-real-handler - 'file-name-all-completions (list filename directory))) - (error nil))))) - ;; unwindform - (setq tramp-completion-mode nil))) + "Like `file-name-all-completions' for partial Tramp files." + + (let* ((fullname (tramp-drop-volume-letter + (expand-file-name filename directory))) + ;; Possible completion structures. + (v (tramp-completion-dissect-file-name fullname)) + result result1) + + (while v + (let* ((car (car v)) + (method (tramp-file-name-method car)) + (user (tramp-file-name-user car)) + (host (tramp-file-name-host car)) + (localname (tramp-file-name-localname car)) + (m (tramp-find-method method user host)) + (tramp-current-user user) ; see `tramp-parse-passwd' + all-user-hosts) + + (unless localname ;; Nothing to complete. + + (if (or user host) + + ;; Method dependent user / host combinations. + (progn + (mapc + (lambda (x) + (setq all-user-hosts + (append all-user-hosts + (funcall (nth 0 x) (nth 1 x))))) + (tramp-get-completion-function m)) + + (setq result + (append result + (mapcar + (lambda (x) + (tramp-get-completion-user-host + method user host (nth 0 x) (nth 1 x))) + (delq nil all-user-hosts))))) + + ;; Possible methods. + (setq result + (append result (tramp-get-completion-methods m))))) + + (setq v (cdr v)))) + + ;; Unify list, remove nil elements. + (while result + (let ((car (car result))) + (when car + (add-to-list + 'result1 + (substring car (length (tramp-drop-volume-letter directory))))) + (setq result (cdr result)))) + + ;; Complete local parts. + (append + result1 + (condition-case nil + (tramp-completion-run-real-handler + 'file-name-all-completions (list filename directory)) + (error nil))))) ;; Method, host name and user name completion for a file. ;;;###autoload (defun tramp-completion-handle-file-name-completion (filename directory &optional predicate) - "Like `file-name-completion' for tramp files." + "Like `file-name-completion' for Tramp files." (try-completion filename (mapcar 'list (file-name-all-completions filename directory)) @@ -4704,26 +4676,26 @@ should never be set globally, the intention is to let-bind it.") ;; I misuse a little bit the tramp-file-name structure in order to handle ;; completion possibilities for partial methods / user names / host names. ;; Return value is a list of tramp-file-name structures according to possible -;; completions. If "multi-method" or "localname" is non-nil it means there +;; completions. If "localname" is non-nil it means there ;; shouldn't be a completion anymore. ;; Expected results: -;; "/x" "/[x" "/x@" "/[x@" "/x@y" "/[x@y" -;; [nil nil nil "x" nil] [nil nil "x" nil nil] [nil nil "x" "y" nil] -;; [nil nil "x" nil nil] -;; [nil "x" nil nil nil] - -;; "/x:" "/x:y" "/x:y:" -;; [nil nil nil "x" ""] [nil nil nil "x" "y"] [nil "x" nil "y" ""] -;; "/[x/" "/[x/y" -;; [nil "x" nil "" nil] [nil "x" nil "y" nil] -;; [nil "x" "" nil nil] [nil "x" "y" nil nil] - -;; "/x:y@" "/x:y@z" "/x:y@z:" -;; [nil nil nil "x" "y@"] [nil nil nil "x" "y@z"] [nil "x" "y" "z" ""] -;; "/[x/y@" "/[x/y@z" -;; [nil "x" nil "y" nil] [nil "x" "y" "z" nil] +;; "/x" "/[x" "/x@" "/[x@" "/x@y" "/[x@y" +;; [nil nil "x" nil] [nil "x" nil nil] [nil "x" "y" nil] +;; [nil "x" nil nil] +;; ["x" nil nil nil] + +;; "/x:" "/x:y" "/x:y:" +;; [nil nil "x" ""] [nil nil "x" "y"] ["x" nil "y" ""] +;; "/[x/" "/[x/y" +;; ["x" nil "" nil] ["x" nil "y" nil] +;; ["x" "" nil nil] ["x" "y" nil nil] + +;; "/x:y@" "/x:y@z" "/x:y@z:" +;; [nil nil "x" "y@"] [nil nil "x" "y@z"] ["x" "y" "z" ""] +;; "/[x/y@" "/[x/y@z" +;; ["x" nil "y" nil] ["x" "y" "z" nil] (defun tramp-completion-dissect-file-name (name) "Returns a list of `tramp-file-name' structures. They are collected by `tramp-completion-dissect-file-name1'." @@ -4748,27 +4720,51 @@ They are collected by `tramp-completion-dissect-file-name1'." "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp "\\(" tramp-host-regexp x-nil "\\)$") nil 1 2 nil)) - ;; "/method:user" "/[method/user" + ;; "/method:user" "/[method/user" "/method://user" (tramp-completion-file-name-structure5 (list (concat tramp-prefix-regexp - "\\(" tramp-method-regexp "\\)" tramp-postfix-single-method-regexp + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp "\\(" tramp-user-regexp x-nil "\\)$") 1 2 nil nil)) - ;; "/method:host" "/[method/host" + ;; "/method:host" "/[method/host" "/method://host" (tramp-completion-file-name-structure6 (list (concat tramp-prefix-regexp - "\\(" tramp-method-regexp "\\)" tramp-postfix-single-method-regexp + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp "\\(" tramp-host-regexp x-nil "\\)$") 1 nil 2 nil)) - ;; "/method:user@host" "/[method/user@host" + ;; "/method:user@host" "/[method/user@host" "/method://user@host" (tramp-completion-file-name-structure7 (list (concat tramp-prefix-regexp - "\\(" tramp-method-regexp "\\)" tramp-postfix-single-method-regexp + "\\(" tramp-method-regexp "\\)" tramp-postfix-method-regexp "\\(" tramp-user-regexp "\\)" tramp-postfix-user-regexp "\\(" tramp-host-regexp x-nil "\\)$") - 1 2 3 nil))) - - (mapcar (lambda (regexp) + 1 2 3 nil)) + ;; "/method: "/method:/" + (tramp-completion-file-name-structure8 + (list + (if (equal tramp-syntax 'url) + (concat tramp-prefix-regexp + "\\(" tramp-method-regexp "\\)" + "\\(" (substring tramp-postfix-method-regexp 0 1) + "\\|" (substring tramp-postfix-method-regexp 1 2) "\\)" + "\\(" "\\)$") + ;; Should not match if not URL syntax. + (concat tramp-prefix-regexp "/$")) + 1 3 nil nil)) + ;; "/method: "/method:/" + (tramp-completion-file-name-structure9 + (list + (if (equal tramp-syntax 'url) + (concat tramp-prefix-regexp + "\\(" tramp-method-regexp "\\)" + "\\(" (substring tramp-postfix-method-regexp 0 1) + "\\|" (substring tramp-postfix-method-regexp 1 2) "\\)" + "\\(" "\\)$") + ;; Should not match if not URL syntax. + (concat tramp-prefix-regexp "/$")) + 1 nil 3 nil))) + + (mapc (lambda (regexp) (add-to-list 'result (tramp-completion-dissect-file-name1 regexp name))) (list @@ -4779,30 +4775,28 @@ They are collected by `tramp-completion-dissect-file-name1'." tramp-completion-file-name-structure5 tramp-completion-file-name-structure6 tramp-completion-file-name-structure7 + tramp-completion-file-name-structure8 + tramp-completion-file-name-structure9 tramp-file-name-structure)) (delq nil result))) (defun tramp-completion-dissect-file-name1 (structure name) "Returns a `tramp-file-name' structure matching STRUCTURE. -The structure consists of multi-method, remote method, remote user, +The structure consists of remote method, remote user, remote host and localname (filename on remote host)." - (let (method) - (save-match-data - (when (string-match (nth 0 structure) name) - (setq method (and (nth 1 structure) - (match-string (nth 1 structure) name))) - (if (and method (member method tramp-multi-methods)) - ;; Not handled (yet). - (vector method nil nil nil nil) - (let ((user (and (nth 2 structure) - (match-string (nth 2 structure) name))) - (host (and (nth 3 structure) - (match-string (nth 3 structure) name))) - (localname (and (nth 4 structure) - (match-string (nth 4 structure) name)))) - (vector nil method user host localname))))))) + (save-match-data + (when (string-match (nth 0 structure) name) + (let ((method (and (nth 1 structure) + (match-string (nth 1 structure) name))) + (user (and (nth 2 structure) + (match-string (nth 2 structure) name))) + (host (and (nth 3 structure) + (match-string (nth 3 structure) name))) + (localname (and (nth 4 structure) + (match-string (nth 4 structure) name)))) + (vector method user host localname))))) ;; This function returns all possible method completions, adding the ;; trailing method delimeter. @@ -4812,8 +4806,8 @@ remote host and localname (filename on remote host)." (lambda (method) (and method (string-match (concat "^" (regexp-quote partial-method)) method) - (tramp-make-tramp-file-name nil method nil nil nil))) - (delete "multi" (mapcar 'car tramp-methods)))) + (tramp-completion-make-tramp-file-name method nil nil nil))) + (mapcar 'car tramp-methods))) ;; Compares partial user and host names with possible completions. (defun tramp-get-completion-user-host (method partial-user partial-host user host) @@ -4845,13 +4839,15 @@ PARTIAL-USER must match USER, PARTIAL-HOST must match HOST." host nil))) (unless (zerop (+ (length user) (length host))) - (tramp-make-tramp-file-name nil method user host nil))) + (tramp-completion-make-tramp-file-name method user host nil))) (defun tramp-parse-rhosts (filename) "Return a list of (user host) tuples allowed to access. Either user or host may be nil." - - (let (res) + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let ((default-directory (tramp-compat-temporary-file-directory)) + res) (when (file-readable-p filename) (with-temp-buffer (insert-file-contents filename) @@ -4860,24 +4856,15 @@ Either user or host may be nil." (push (tramp-parse-rhosts-group) res)))) res)) -;; Taken from gnus/netrc.el -(eval-and-compile - (defalias 'tramp-point-at-eol - (if (fboundp 'point-at-eol) - 'point-at-eol - 'line-end-position))) - (defun tramp-parse-rhosts-group () "Return a (user host) tuple allowed to access. Either user or host may be nil." - (let ((result) (regexp (concat "^\\(" tramp-host-regexp "\\)" "\\([ \t]+" "\\(" tramp-user-regexp "\\)" "\\)?"))) - - (narrow-to-region (point) (tramp-point-at-eol)) + (narrow-to-region (point) (tramp-compat-line-end-position)) (when (re-search-forward regexp nil t) (setq result (append (list (match-string 3) (match-string 1))))) (widen) @@ -4887,8 +4874,10 @@ Either user or host may be nil." (defun tramp-parse-shosts (filename) "Return a list of (user host) tuples allowed to access. User is always nil." - - (let (res) + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let ((default-directory (tramp-compat-temporary-file-directory)) + res) (when (file-readable-p filename) (with-temp-buffer (insert-file-contents filename) @@ -4900,11 +4889,9 @@ User is always nil." (defun tramp-parse-shosts-group () "Return a (user host) tuple allowed to access. User is always nil." - (let ((result) (regexp (concat "^\\(" tramp-host-regexp "\\)"))) - - (narrow-to-region (point) (tramp-point-at-eol)) + (narrow-to-region (point) (tramp-compat-line-end-position)) (when (re-search-forward regexp nil t) (setq result (list nil (match-string 1)))) (widen) @@ -4916,8 +4903,10 @@ User is always nil." (defun tramp-parse-sconfig (filename) "Return a list of (user host) tuples allowed to access. User is always nil." - - (let (res) + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let ((default-directory (tramp-compat-temporary-file-directory)) + res) (when (file-readable-p filename) (with-temp-buffer (insert-file-contents filename) @@ -4929,11 +4918,9 @@ User is always nil." (defun tramp-parse-sconfig-group () "Return a (user host) tuple allowed to access. User is always nil." - (let ((result) (regexp (concat "^[ \t]*Host[ \t]+" "\\(" tramp-host-regexp "\\)"))) - - (narrow-to-region (point) (tramp-point-at-eol)) + (narrow-to-region (point) (tramp-compat-line-end-position)) (when (re-search-forward regexp nil t) (setq result (list nil (match-string 1)))) (widen) @@ -4945,11 +4932,12 @@ User is always nil." (defun tramp-parse-shostkeys (dirname) "Return a list of (user host) tuples allowed to access. User is always nil." - - (let ((regexp (concat "^key_[0-9]+_\\(" tramp-host-regexp "\\)\\.pub$")) - (files (when (file-directory-p dirname) (directory-files dirname))) - result) - + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let* ((default-directory (tramp-compat-temporary-file-directory)) + (regexp (concat "^key_[0-9]+_\\(" tramp-host-regexp "\\)\\.pub$")) + (files (when (file-directory-p dirname) (directory-files dirname))) + result) (while files (when (string-match regexp (car files)) (push (list nil (match-string 1 (car files))) result)) @@ -4959,12 +4947,13 @@ User is always nil." (defun tramp-parse-sknownhosts (dirname) "Return a list of (user host) tuples allowed to access. User is always nil." - - (let ((regexp (concat "^\\(" tramp-host-regexp - "\\)\\.ssh-\\(dss\\|rsa\\)\\.pub$")) - (files (when (file-directory-p dirname) (directory-files dirname))) - result) - + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let* ((default-directory (tramp-compat-temporary-file-directory)) + (regexp (concat "^\\(" tramp-host-regexp + "\\)\\.ssh-\\(dss\\|rsa\\)\\.pub$")) + (files (when (file-directory-p dirname) (directory-files dirname))) + result) (while files (when (string-match regexp (car files)) (push (list nil (match-string 1 (car files))) result)) @@ -4974,8 +4963,10 @@ User is always nil." (defun tramp-parse-hosts (filename) "Return a list of (user host) tuples allowed to access. User is always nil." - - (let (res) + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let ((default-directory (tramp-compat-temporary-file-directory)) + res) (when (file-readable-p filename) (with-temp-buffer (insert-file-contents filename) @@ -4987,11 +4978,9 @@ User is always nil." (defun tramp-parse-hosts-group () "Return a (user host) tuple allowed to access. User is always nil." - (let ((result) (regexp (concat "^\\(" tramp-host-regexp "\\)"))) - - (narrow-to-region (point) (tramp-point-at-eol)) + (narrow-to-region (point) (tramp-compat-line-end-position)) (when (re-search-forward regexp nil t) (unless (char-equal (or (char-after) ?\n) ?:) ; no IPv6 (setq result (list nil (match-string 1))))) @@ -5003,13 +4992,15 @@ User is always nil." ;; For su-alike methods it would be desirable to return "root@localhost" ;; as default. Unfortunately, we have no information whether any user name -;; has been typed already. So we (mis-)use tramp-current-user as indication, +;; has been typed already. So we use `tramp-current-user' as indication, ;; assuming it is set in `tramp-completion-handle-file-name-all-completions'. (defun tramp-parse-passwd (filename) "Return a list of (user host) tuples allowed to access. Host is always \"localhost\"." - - (let (res) + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let ((default-directory (tramp-compat-temporary-file-directory)) + res) (if (zerop (length tramp-current-user)) '(("root" nil)) (when (file-readable-p filename) @@ -5023,11 +5014,9 @@ Host is always \"localhost\"." (defun tramp-parse-passwd-group () "Return a (user host) tuple allowed to access. Host is always \"localhost\"." - (let ((result) (regexp (concat "^\\(" tramp-user-regexp "\\):"))) - - (narrow-to-region (point) (tramp-point-at-eol)) + (narrow-to-region (point) (tramp-compat-line-end-position)) (when (re-search-forward regexp nil t) (setq result (list (match-string 1) "localhost"))) (widen) @@ -5037,8 +5026,10 @@ Host is always \"localhost\"." (defun tramp-parse-netrc (filename) "Return a list of (user host) tuples allowed to access. User may be nil." - - (let (res) + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let ((default-directory (tramp-compat-temporary-file-directory)) + res) (when (file-readable-p filename) (with-temp-buffer (insert-file-contents filename) @@ -5050,49 +5041,63 @@ User may be nil." (defun tramp-parse-netrc-group () "Return a (user host) tuple allowed to access. User may be nil." - (let ((result) (regexp (concat "^[ \t]*machine[ \t]+" "\\(" tramp-host-regexp "\\)" "\\([ \t]+login[ \t]+" "\\(" tramp-user-regexp "\\)" "\\)?"))) - - (narrow-to-region (point) (tramp-point-at-eol)) + (narrow-to-region (point) (tramp-compat-line-end-position)) (when (re-search-forward regexp nil t) (setq result (list (match-string 3) (match-string 1)))) (widen) (forward-line 1) result)) +(defun tramp-parse-putty (registry) + "Return a list of (user host) tuples allowed to access. +User is always nil." + ;; On Windows, there are problems in completion when + ;; `default-directory' is remote. + (let ((default-directory (tramp-compat-temporary-file-directory)) + res) + (with-temp-buffer + (when (zerop (tramp-local-call-process "reg" nil t nil "query" registry)) + (goto-char (point-min)) + (while (not (eobp)) + (push (tramp-parse-putty-group registry) res)))) + res)) + +(defun tramp-parse-putty-group (registry) + "Return a (user host) tuple allowed to access. +User is always nil." + (let ((result) + (regexp (concat (regexp-quote registry) "\\\\\\(.+\\)"))) + (narrow-to-region (point) (tramp-compat-line-end-position)) + (when (re-search-forward regexp nil t) + (setq result (list nil (match-string 1)))) + (widen) + (forward-line 1) + result)) + ;;; Internal Functions: -(defun tramp-maybe-send-perl-script (multi-method method user host script name) - "Define in remote shell function NAME implemented as perl SCRIPT. -Only send the definition if it has not already been done. -Function may have 0-3 parameters." - (let ((remote-perl (tramp-get-remote-perl multi-method method user host))) - (unless remote-perl (error "No remote perl")) - (let ((perl-scripts (tramp-get-connection-property "perl-scripts" nil - multi-method method user host))) - (unless (memq name perl-scripts) - (with-current-buffer (tramp-get-buffer multi-method method user host) - (tramp-message 5 (concat "Sending the Perl script `" name "'...")) - (tramp-send-string multi-method method user host - (concat name - " () {\n" - remote-perl - " -e '" - script - "' \"$1\" \"$2\" \"$3\" 2>/dev/null\n}")) - (tramp-wait-for-output) - (tramp-set-connection-property "perl-scripts" (cons name perl-scripts) - multi-method method user host) - (tramp-message 5 (concat "Sending the Perl script `" name "'...done."))))))) +(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) + (tramp-message vec 5 "Sending script `%s'..." name) + ;; The script could contain a call of Perl. This is masked with `%s'. + (tramp-send-command-and-check + vec + (format "%s () {\n%s\n}" name + (format script (tramp-get-remote-perl vec)))) + (tramp-set-connection-property p "scripts" (cons name scripts)) + (tramp-message vec 5 "Sending script `%s'...done." name)))) (defun tramp-set-auto-save () - (when (and (buffer-file-name) - (tramp-tramp-file-p (buffer-file-name)) - ;; ange-ftp has its own auto-save mechanism + (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) @@ -5105,340 +5110,280 @@ Function may have 0-3 parameters." (defun tramp-run-test (switch filename) "Run `test' on the remote system, given a SWITCH and a FILENAME. Returns the exit code of the `test' program." - (let ((v (tramp-dissect-file-name filename))) - (save-excursion - (tramp-send-command-and-check - (tramp-file-name-multi-method v) (tramp-file-name-method v) - (tramp-file-name-user v) (tramp-file-name-host v) - (format "test %s %s" switch - (tramp-shell-quote-argument (tramp-file-name-localname v))))))) - -(defun tramp-run-test2 (program file1 file2 &optional switch) - "Run `test'-like PROGRAM on the remote system, given FILE1, FILE2. -The optional SWITCH is inserted between the two files. -Returns the exit code of the `test' PROGRAM. Barfs if the methods, + (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." - (let* ((v1 (tramp-dissect-file-name file1)) - (v2 (tramp-dissect-file-name file2)) - (mmethod1 (tramp-file-name-multi-method v1)) - (mmethod2 (tramp-file-name-multi-method v2)) - (method1 (tramp-file-name-method v1)) - (method2 (tramp-file-name-method v2)) - (user1 (tramp-file-name-user v1)) - (user2 (tramp-file-name-user v2)) - (host1 (tramp-file-name-host v1)) - (host2 (tramp-file-name-host v2)) - (localname1 (tramp-file-name-localname v1)) - (localname2 (tramp-file-name-localname v2))) - (unless (and method1 method2 host1 host2 - (equal mmethod1 mmethod2) - (equal method1 method2) - (equal user1 user2) - (equal host1 host2)) - (error "tramp-run-test2: %s" - "only implemented for same method, same user, same host")) - (save-excursion + (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 - mmethod1 method1 user1 host1 - (format "%s %s %s %s" - program - (tramp-shell-quote-argument localname1) - (or switch "") - (tramp-shell-quote-argument localname2)))))) - -(defun tramp-touch (file time) - "Set the last-modified timestamp of the given file. -TIME is an Emacs internal time value as returned by `current-time'." - (let* ((utc - ;; With GNU Emacs, `format-time-string' has an optional - ;; parameter UNIVERSAL. This is preferred. - (and (functionp 'subr-arity) - (= 3 (cdr (funcall (symbol-function 'subr-arity) - (symbol-function 'format-time-string)))))) - (touch-time - (if utc - (format-time-string "%Y%m%d%H%M.%S" time t) - (format-time-string "%Y%m%d%H%M.%S" time)))) - (if (tramp-tramp-file-p file) - (with-parsed-tramp-file-name file nil - (let ((buf (tramp-get-buffer multi-method method user host))) - (unless (zerop (tramp-send-command-and-check - multi-method method user host - (format "%s touch -t %s %s" - (if utc "TZ=UTC; export TZ;" "") - touch-time - (tramp-shell-quote-argument localname)) - t)) - (pop-to-buffer buf) - (error "tramp-touch: touch failed, see buffer `%s' for details" - buf)))) - ;; It's a local file - (with-temp-buffer - (unless (zerop (call-process - "touch" nil (current-buffer) nil "-t" touch-time file)) - (pop-to-buffer (current-buffer)) - (error "tramp-touch: touch failed")))))) - -(defun tramp-buffer-name (multi-method method user host) - "A name for the connection buffer for USER at HOST using METHOD." - (if multi-method - (tramp-buffer-name-multi-method "tramp" multi-method method user host) - (let ((method (tramp-find-method multi-method method user host))) - (if user - (format "*tramp/%s %s@%s*" method user host) - (format "*tramp/%s %s*" method host))))) - -(defun tramp-buffer-name-multi-method (prefix multi-method method user host) - "A name for the multi method connection buffer. -MULTI-METHOD gives the multi method, METHOD the array of methods, -USER the array of user names, HOST the array of host names." - (unless (and (= (length method) (length user)) - (= (length method) (length host))) - (error "Syntax error in multi method (implementation error)")) - (let ((len (length method)) - (i 0) - string-list) - (while (< i len) - (setq string-list - (cons (if (aref user i) - (format "%s#%s@%s:" (aref method i) - (aref user i) (aref host i)) - (format "%s@%s:" (aref method i) (aref host i))) - string-list)) - (setq i (1+ i))) - (format "*%s/%s %s*" - prefix multi-method - (apply 'concat (reverse string-list))))) - -(defun tramp-get-buffer (multi-method method user host) - "Get the connection buffer to be used for USER at HOST using METHOD." + v1 + (format format-string + (tramp-shell-quote-argument v1-localname) + (tramp-shell-quote-argument v2-localname)))))) + +(defun tramp-buffer-name (vec) + "A name for the connection buffer VEC." + ;; We must use `tramp-file-name-real-host', because for gateway + ;; methods the default port will be expanded later on, which would + ;; tamper the name. + (let ((method (tramp-file-name-method vec)) + (user (tramp-file-name-user vec)) + (host (tramp-file-name-real-host vec))) + (if (not (zerop (length user))) + (format "*tramp/%s %s@%s*" method user host) + (format "*tramp/%s %s*" method host)))) + +(defun tramp-get-buffer (vec) + "Get the connection buffer to be used for VEC." + (or (get-buffer (tramp-buffer-name vec)) + (with-current-buffer (get-buffer-create (tramp-buffer-name vec)) + (setq buffer-undo-list t) + (setq default-directory + (tramp-make-tramp-file-name + (tramp-file-name-method vec) + (tramp-file-name-user vec) + (tramp-file-name-host vec) + "/")) + (current-buffer)))) + +(defun tramp-get-connection-buffer (vec) + "Get the connection buffer to be used for VEC. +In case a second asynchronous communication has been started, it is different +from `tramp-get-buffer'." + (or (tramp-get-connection-property vec "process-buffer" nil) + (tramp-get-buffer vec))) + +(defun tramp-get-connection-process (vec) + "Get the connection process to be used for VEC. +In case a second asynchronous communication has been started, it is different +from the default one." + (get-process + (or (tramp-get-connection-property vec "process-name" nil) + (tramp-buffer-name vec)))) + +(defun tramp-debug-buffer-name (vec) + "A name for the debug buffer for VEC." + ;; We must use `tramp-file-name-real-host', because for gateway + ;; methods the default port will be expanded later on, which would + ;; tamper the name. + (let ((method (tramp-file-name-method vec)) + (user (tramp-file-name-user vec)) + (host (tramp-file-name-real-host vec))) + (if (not (zerop (length user))) + (format "*debug tramp/%s %s@%s*" method user host) + (format "*debug tramp/%s %s*" method host)))) + +(defun tramp-get-debug-buffer (vec) + "Get the debug buffer for VEC." (with-current-buffer - (get-buffer-create (tramp-buffer-name multi-method method user host)) - (setq buffer-undo-list t) + (get-buffer-create (tramp-debug-buffer-name vec)) + (when (bobp) + (setq buffer-undo-list t) + ;; Activate outline-mode. This runs `text-mode-hook' and + ;; `outline-mode-hook'. We must prevent that local processes + ;; die. Yes: I've seen `flyspell-mode', which starts "ispell" + ;; ... + (let ((default-directory (tramp-compat-temporary-file-directory))) + (outline-mode)) + (set (make-local-variable 'outline-regexp) + "[0-9]+:[0-9]+:[0-9]+ [a-z0-9-]+ (\\([0-9]+\\)) #") +; (set (make-local-variable 'outline-regexp) +; "[a-z.-]+:[0-9]+: [a-z0-9-]+ (\\([0-9]+\\)) #") + (set (make-local-variable 'outline-level) 'tramp-outline-level)) (current-buffer))) -(defun tramp-debug-buffer-name (multi-method method user host) - "A name for the debug buffer for USER at HOST using METHOD." - (if multi-method - (tramp-buffer-name-multi-method "debug tramp" - multi-method method user host) - (let ((method (tramp-find-method multi-method method user host))) - (if user - (format "*debug tramp/%s %s@%s*" method user host) - (format "*debug tramp/%s %s*" method host))))) - -(defun tramp-get-debug-buffer (multi-method method user host) - "Get the debug buffer for USER at HOST using METHOD." - (with-current-buffer - (get-buffer-create - (tramp-debug-buffer-name multi-method method user host)) - (setq buffer-undo-list t) - (current-buffer))) +(defun tramp-outline-level () + "Return the depth to which a statement is nested in the outline. +Point must be at the beginning of a header line. + +The outline level is equal to the verbosity of the Tramp message." + (1+ (string-to-number (match-string 1)))) -(defun tramp-find-executable (multi-method method user host - progname dirlist ignore-tilde) - "Searches for PROGNAME in all directories mentioned in DIRLIST. -First args METHOD, USER and HOST specify the connection, PROGNAME -is the program to search for, and DIRLIST gives the list of directories -to search. If IGNORE-TILDE is non-nil, directory names starting -with `~' will be ignored. +(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." - (let (result) - (when ignore-tilde - ;; Remove all ~/foo directories from dirlist. In Emacs 20, - ;; `remove' is in CL, and we want to avoid CL dependencies. - (let (newdl d) - (while dirlist - (setq d (car dirlist)) - (setq dirlist (cdr dirlist)) - (unless (char-equal ?~ (aref d 0)) - (setq newdl (cons d newdl)))) - (setq dirlist (nreverse newdl)))) - (tramp-send-command - multi-method method user host - (format (concat "while read d; " - "do if test -x $d/%s -a -f $d/%s; " - "then echo tramp_executable $d/%s; " - "break; fi; done <<'EOF'") - progname progname progname)) - (mapcar (lambda (d) - (tramp-send-command multi-method method user host d)) - dirlist) - (tramp-send-command multi-method method user host "EOF") - (tramp-wait-for-output) - (goto-char (point-max)) - (when (search-backward "tramp_executable " nil t) - (skip-chars-forward "^ ") - (skip-chars-forward " ") - (buffer-substring (point) (tramp-line-end-position))))) - -(defun tramp-set-remote-path (multi-method method user host var dirlist) - "Sets the remote environment VAR to existing directories from DIRLIST. -I.e., for each directory in DIRLIST, it is tested whether it exists and if -so, it is added to the environment variable VAR." - (let ((existing-dirs - (mapcar - (lambda (x) - (when (and - (file-exists-p - (tramp-make-tramp-file-name multi-method method user host x)) - (file-directory-p - (tramp-make-tramp-file-name multi-method method user host x))) - x)) - dirlist))) - (tramp-send-command - multi-method method user host - (concat var "=" - (mapconcat 'identity (delq nil existing-dirs) ":") - "; export " var)) - (tramp-wait-for-output))) + (with-current-buffer (tramp-get-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 "^1$") + (setq result (concat "\\" progname)))) + (unless result + (when ignore-tilde + ;; Remove all ~/foo directories from dirlist. In Emacs 20, + ;; `remove' is in CL, and we want to avoid CL dependencies. + (let (newdl d) + (while dirlist + (setq d (car dirlist)) + (setq dirlist (cdr dirlist)) + (unless (char-equal ?~ (aref d 0)) + (setq newdl (cons d newdl)))) + (setq dirlist (nreverse newdl)))) + (tramp-send-command + 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) (tramp-compat-line-end-position))))) + 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 (multi-method method user host) +(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." - (make-local-variable 'tramp-file-exists-command) - (tramp-message 9 "Finding command to check if file exists") - (let ((existing - (tramp-make-tramp-file-name - multi-method method user host - "/")) ;assume this file always exists + (let ((existing "/") (nonexisting - (tramp-make-tramp-file-name - multi-method method user host - "/ this file does not exist "))) ;assume this never exists + (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 + ;; `$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. + ;; `ls -d' This works on most systems, but NetBSD 1.4 + ;; has a bug: `ls' always returns zero exit + ;; status, even for files which don't exist. + ;; `test -e' Some Bourne shells have a `test' builtin + ;; which does not know the `-e' option. + ;; `/bin/test -e' For those, the `test' binary on disk normally + ;; provides the option. Alas, the binary + ;; is sometimes `/bin/test' and sometimes it's + ;; `/usr/bin/test'. + ;; `/usr/bin/test -e' In case `/bin/test' does not exist. (unless (or - (and (setq tramp-file-exists-command "test -e %s") - (file-exists-p existing) - (not (file-exists-p nonexisting))) - (and (setq tramp-file-exists-command "/bin/test -e %s") - (file-exists-p existing) - (not (file-exists-p nonexisting))) - (and (setq tramp-file-exists-command "/usr/bin/test -e %s") - (file-exists-p existing) - (not (file-exists-p nonexisting))) - (and (setq tramp-file-exists-command "ls -d %s") - (file-exists-p existing) - (not (file-exists-p nonexisting)))) - (error "Couldn't find command to check if file exists")))) + (and (setq result (format "%s -e" (tramp-get-test-command vec))) + (zerop (tramp-send-command-and-check + vec (format "%s %s" result existing))) + (not (zerop (tramp-send-command-and-check + vec (format "%s %s" result nonexisting))))) + (and (setq result "/bin/test -e") + (zerop (tramp-send-command-and-check + vec (format "%s %s" result existing))) + (not (zerop (tramp-send-command-and-check + vec (format "%s %s" result nonexisting))))) + (and (setq result "/usr/bin/test -e") + (zerop (tramp-send-command-and-check + vec (format "%s %s" result existing))) + (not (zerop (tramp-send-command-and-check + vec (format "%s %s" result nonexisting))))) + (and (setq result (format "%s -d" (tramp-get-ls-command vec))) + (zerop (tramp-send-command-and-check + vec (format "%s %s" result existing))) + (not (zerop (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)) ;; CCC test ksh or bash found for tilde expansion? -(defun tramp-find-shell (multi-method method user host) - "Find a shell on the remote host which groks tilde expansion." - (let ((shell nil)) - (tramp-send-command multi-method method user host "echo ~root") - (tramp-wait-for-output) - (cond - ((string-match "^~root$" (buffer-string)) - (setq shell - (or (tramp-find-executable multi-method method user host - "bash" tramp-remote-path t) - (tramp-find-executable multi-method method user host - "ksh" tramp-remote-path t))) - (unless shell - (error "Couldn't find a shell which groks tilde expansion")) - ;; Find arguments for this shell. - (let ((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-message - 5 "Starting remote shell `%s' for tilde expansion..." shell) - (tramp-send-command - multi-method method user host - (concat "PROMPT_COMMAND='' PS1='$ ' exec " shell)) ; - (tramp-barf-if-no-shell-prompt - (get-buffer-process (current-buffer)) - 60 "Couldn't find remote `%s' prompt" shell) - (tramp-message - 9 "Setting remote shell prompt...") - ;; Douglas Gray Stephens <DGrayStephens@slb.com> says that we - ;; must use "\n" here, not tramp-rsh-end-of-line. Kai left the - ;; last tramp-rsh-end-of-line, Douglas wanted to replace that, - ;; as well. - (process-send-string - nil (format "PROMPT_COMMAND=''; PS1='%s%s%s'; PS2=''; PS3=''%s" +(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 + ((string-match "^~root$" (buffer-string)) + (setq shell + (or (tramp-find-executable + vec "bash" (tramp-get-remote-path vec) t) + (tramp-find-executable + vec "ksh" (tramp-get-remote-path vec) t))) + (unless shell + (tramp-error + vec 'file-error + "Couldn't find a shell which groks tilde expansion")) + ;; Find arguments for this shell. + (let ((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-message + vec 5 "Starting remote shell `%s' for tilde expansion..." shell) + (let ((tramp-end-of-output "$ ")) + (tramp-send-command + vec + (format "PROMPT_COMMAND='' PS1='$ ' PS2='' PS3='' exec %s" shell) + t)) + (tramp-message vec 5 "Setting remote shell prompt...") + ;; Douglas Gray Stephens <DGrayStephens@slb.com> says that we + ;; must use "\n" here, not tramp-rsh-end-of-line. Kai left the + ;; last tramp-rsh-end-of-line, Douglas wanted to replace that, + ;; as well. + (tramp-send-command + vec + (format "PS1='%s%s%s'" tramp-rsh-end-of-line tramp-end-of-output - tramp-rsh-end-of-line - tramp-rsh-end-of-line)) - (tramp-wait-for-output) - (tramp-message - 9 "Setting remote shell prompt...done") - ) - (t (tramp-message 5 "Remote `%s' groks tilde expansion, good" - (tramp-get-method-parameter - multi-method method user host 'tramp-remote-sh)))))) - -(defun tramp-check-ls-command (multi-method method user host cmd) - "Checks whether the given `ls' executable groks `-n'. -METHOD, USER and HOST specify the connection, CMD (the absolute file name of) -the `ls' executable. Returns t if CMD supports the `-n' option, nil -otherwise." - (tramp-message 9 "Checking remote `%s' command for `-n' option" cmd) - (when (file-executable-p - (tramp-make-tramp-file-name multi-method method user host cmd)) - (let ((result nil)) - (tramp-message 7 "Testing remote command `%s' for -n..." cmd) - (setq result - (tramp-send-command-and-check - multi-method method user host - (format "%s -lnd / >/dev/null" - cmd))) - (tramp-message 7 "Testing remote command `%s' for -n...%s" - cmd - (if (zerop result) "okay" "failed")) - (zerop result)))) - -(defun tramp-check-ls-commands (multi-method method user host cmd dirlist) - "Checks whether the given `ls' executable in one of the dirs groks `-n'. -Returns nil if none was found, else the command is returned." - (let ((dl dirlist) - (result nil)) - (tramp-let-maybe directory-sep-char ?/ ;for XEmacs - ;; It would be better to use the CL function `find', but - ;; we don't want run-time dependencies on CL. - (while (and dl (not result)) - (let ((x (concat (file-name-as-directory (car dl)) cmd))) - (when (tramp-check-ls-command multi-method method user host x) - (setq result x))) - (setq dl (cdr dl))) - result))) - -(defun tramp-find-ls-command (multi-method method user host) - "Finds an `ls' command which groks the `-n' option, returning nil if failed. -\(This option prints numeric user and group ids in a long listing.)" - (tramp-message 9 "Finding a suitable `ls' command") - (or - (tramp-check-ls-commands multi-method method user host "ls" tramp-remote-path) - (tramp-check-ls-commands multi-method method user host "gnuls" tramp-remote-path) - (tramp-check-ls-commands multi-method method user host "gls" tramp-remote-path))) + tramp-rsh-end-of-line) + t) + (tramp-send-command vec "PS2=''" t) + (tramp-send-command vec "PS3=''" t) + (tramp-send-command vec "PROMPT_COMMAND=''" t) + (tramp-message vec 5 "Setting remote shell prompt...done")) + (t (tramp-message + vec 5 "Remote `%s' groks tilde expansion, good" + (tramp-get-method-parameter + (tramp-file-name-method vec) 'tramp-remote-sh)) + (tramp-set-connection-property + vec "remote-shell" + (tramp-get-method-parameter + (tramp-file-name-method vec) 'tramp-remote-sh)))))))) ;; ------------------------------------------------------------ ;; -- Functions for establishing connection -- @@ -5448,828 +5393,361 @@ Returns nil if none was found, else the command is returned." ;; prompts from the remote host. See the variable ;; `tramp-actions-before-shell' for usage of these functions. -(defun tramp-action-login (p multi-method method user host) +(defun tramp-action-login (proc vec) "Send the login name." - (tramp-message 9 "Sending login name `%s'" - (or user (user-login-name))) - (erase-buffer) - (process-send-string nil (concat (or user (user-login-name)) - tramp-rsh-end-of-line))) - -(defun tramp-action-password (p multi-method method user host) + (when (not (stringp tramp-current-user)) + (save-window-excursion + (let ((enable-recursive-minibuffers t)) + (pop-to-buffer (tramp-get-connection-buffer vec)) + (setq tramp-current-user (read-string (match-string 0)))))) + (tramp-message vec 3 "Sending login name `%s'" tramp-current-user) + (with-current-buffer (tramp-get-connection-buffer vec) + (tramp-message vec 6 "\n%s" (buffer-string))) + (tramp-send-string vec tramp-current-user)) + +(defun tramp-action-password (proc vec) "Query the user for a password." - (let ((pw-prompt - (format "Password for %s " - (tramp-make-tramp-file-name - nil method user host "")))) - (tramp-message 9 "Sending password") - (tramp-enter-password p pw-prompt user host))) - -(defun tramp-action-succeed (p multi-method method user host) + (tramp-message vec 3 "Sending password") + (tramp-enter-password proc)) + +(defun tramp-action-succeed (proc vec) "Signal success in finding shell prompt." - (tramp-message 9 "Found remote shell prompt.") - (erase-buffer) (throw 'tramp-action 'ok)) -(defun tramp-action-permission-denied (p multi-method method user host) +(defun tramp-action-permission-denied (proc vec) "Signal permission denied." - (pop-to-buffer (tramp-get-buffer multi-method method user host)) - (tramp-message 9 "Permission denied by remote host.") - (kill-process p) + (kill-process proc) (throw 'tramp-action 'permission-denied)) -(defun tramp-action-copy-failed (p multi-method method user host) - "Signal copy failed." - (kill-process p) - (error "%s" (match-string 1))) - -(defun tramp-action-yesno (p multi-method method user host) +(defun tramp-action-yesno (proc vec) "Ask the user for confirmation using `yes-or-no-p'. Send \"yes\" to remote process on confirmation, abort otherwise. See also `tramp-action-yn'." (save-window-excursion - (pop-to-buffer (tramp-get-buffer multi-method method user host)) - (unless (yes-or-no-p (match-string 0)) - (kill-process p) - (erase-buffer) - (throw 'tramp-action 'permission-denied)) - (process-send-string p (concat "yes" tramp-rsh-end-of-line)) - (erase-buffer))) - -(defun tramp-action-yn (p multi-method method user host) + (let ((enable-recursive-minibuffers t)) + (save-match-data (pop-to-buffer (tramp-get-connection-buffer vec))) + (unless (yes-or-no-p (match-string 0)) + (kill-process proc) + (throw 'tramp-action 'permission-denied)) + (with-current-buffer (tramp-get-connection-buffer vec) + (tramp-message vec 6 "\n%s" (buffer-string))) + (tramp-send-string vec "yes")))) + +(defun tramp-action-yn (proc vec) "Ask the user for confirmation using `y-or-n-p'. Send \"y\" to remote process on confirmation, abort otherwise. See also `tramp-action-yesno'." (save-window-excursion - (pop-to-buffer (tramp-get-buffer multi-method method user host)) - (unless (y-or-n-p (match-string 0)) - (kill-process p) - (throw 'tramp-action 'permission-denied)) - (erase-buffer) - (process-send-string p (concat "y" tramp-rsh-end-of-line)))) - -(defun tramp-action-terminal (p multi-method method user host) + (let ((enable-recursive-minibuffers t)) + (save-match-data (pop-to-buffer (tramp-get-connection-buffer vec))) + (unless (y-or-n-p (match-string 0)) + (kill-process proc) + (throw 'tramp-action 'permission-denied)) + (with-current-buffer (tramp-get-connection-buffer vec) + (tramp-message vec 6 "\n%s" (buffer-string))) + (tramp-send-string vec "y")))) + +(defun tramp-action-terminal (proc vec) "Tell the remote host which terminal type to use. The terminal type can be configured with `tramp-terminal-type'." - (tramp-message 9 "Setting `%s' as terminal type." - tramp-terminal-type) - (erase-buffer) - (process-send-string nil (concat tramp-terminal-type - tramp-rsh-end-of-line))) + (tramp-message vec 5 "Setting `%s' as terminal type." tramp-terminal-type) + (with-current-buffer (tramp-get-connection-buffer vec) + (tramp-message vec 6 "\n%s" (buffer-string))) + (tramp-send-string vec tramp-terminal-type)) -(defun tramp-action-process-alive (p multi-method method user host) +(defun tramp-action-process-alive (proc vec) "Check whether a process has finished." - (unless (memq (process-status p) '(run open)) + (unless (memq (process-status proc) '(run open)) (throw 'tramp-action 'process-died))) -(defun tramp-action-out-of-band (p multi-method method user host) +(defun tramp-action-out-of-band (proc vec) "Check whether an out-of-band copy has finished." - (cond ((and (memq (process-status p) '(stop exit)) - (zerop (process-exit-status p))) - (tramp-message 9 "Process has finished.") + (cond ((and (memq (process-status proc) '(stop exit)) + (zerop (process-exit-status proc))) + (tramp-message vec 3 "Process has finished.") (throw 'tramp-action 'ok)) - ((or (and (memq (process-status p) '(stop exit)) - (not (zerop (process-exit-status p)))) - (memq (process-status p) '(signal))) + ((or (and (memq (process-status proc) '(stop exit)) + (not (zerop (process-exit-status proc)))) + (memq (process-status proc) '(signal))) ;; `scp' could have copied correctly, but set modes could have failed. ;; This can be ignored. - (goto-char (point-min)) - (if (re-search-forward tramp-operation-not-permitted-regexp nil t) - (progn - (tramp-message 10 "'set mode' error ignored.") - (tramp-message 9 "Process has finished.") - (throw 'tramp-action 'ok)) - (tramp-message 9 "Process has died.") - (throw 'tramp-action 'process-died))) + (with-current-buffer (process-buffer proc) + (goto-char (point-min)) + (if (re-search-forward tramp-operation-not-permitted-regexp nil t) + (progn + (tramp-message vec 5 "'set mode' error ignored.") + (tramp-message vec 3 "Process has finished.") + (throw 'tramp-action 'ok)) + (tramp-message vec 3 "Process has died.") + (throw 'tramp-action 'process-died)))) (t nil))) -;; The following functions are specifically for multi connections. - -(defun tramp-multi-action-login (p method user host) - "Send the login name." - (tramp-message 9 "Sending login name `%s'" user) - (erase-buffer) - (process-send-string p (concat user tramp-rsh-end-of-line))) - -(defun tramp-multi-action-password (p method user host) - "Query the user for a password." - (let ((pw-prompt - (format "Password for %s " - (tramp-make-tramp-file-name - nil method user host "")))) - (tramp-message 9 "Sending password") - (tramp-enter-password p pw-prompt user host))) - -(defun tramp-multi-action-succeed (p method user host) - "Signal success in finding shell prompt." - (tramp-message 9 "Found shell prompt on `%s'" host) - (erase-buffer) - (throw 'tramp-action 'ok)) - -(defun tramp-multi-action-permission-denied (p method user host) - "Signal permission denied." - (tramp-message 9 "Permission denied by remote host `%s'" host) - (kill-process p) - (erase-buffer) - (throw 'tramp-action 'permission-denied)) - -(defun tramp-multi-action-process-alive (p method user host) - "Check whether a process has finished." - (unless (memq (process-status p) '(run open)) - (throw 'tramp-action 'process-died))) - ;; Functions for processing the actions. -(defun tramp-process-one-action (p multi-method method user host actions) +(defun tramp-process-one-action (proc vec actions) "Wait for output from the shell and perform one action." - (let (found item pattern action todo) - (erase-buffer) - (tramp-message 9 "Waiting 60s for prompt from remote shell") + (let (found todo item pattern action) (while (not found) - (tramp-accept-process-output p 1) - (goto-char (point-min)) + ;; Reread output once all actions have been performed. + ;; Obviously, the output was not complete. + (tramp-accept-process-output proc 1) (setq todo actions) (while todo - (goto-char (point-min)) (setq item (pop todo)) - (setq pattern (symbol-value (nth 0 item))) + (setq pattern (concat (symbol-value (nth 0 item)) "\\'")) (setq action (nth 1 item)) - (tramp-message 10 "Looking for regexp \"%s\" from remote shell" - pattern) - (when (re-search-forward (concat pattern "\\'") nil t) - (setq found (funcall action p multi-method method user host))))) + (tramp-message + vec 5 "Looking for regexp \"%s\" from remote shell" pattern) + (when (tramp-check-for-regexp proc pattern) + (tramp-message vec 5 "Call `%s'" (symbol-name action)) + (setq found (funcall action proc vec))))) found)) -(defun tramp-process-actions - (p multi-method method user host actions &optional timeout) +(defun tramp-process-actions (proc vec actions &optional timeout) "Perform actions until success or TIMEOUT." - (tramp-message 10 "%s" (mapconcat 'identity (process-command p) " ")) (let (exit) (while (not exit) - (tramp-message 9 "Waiting for prompts from remote shell") + (tramp-message proc 3 "Waiting for prompts from remote shell") (setq exit (catch 'tramp-action (if timeout (with-timeout (timeout) - (tramp-process-one-action - p multi-method method user host actions)) - (tramp-process-one-action - p multi-method method user host actions)) - nil))) + (tramp-process-one-action proc vec actions)) + (tramp-process-one-action proc vec actions))))) + (with-current-buffer (tramp-get-connection-buffer vec) + (tramp-message vec 6 "\n%s" (buffer-string))) (unless (eq exit 'ok) - (tramp-clear-passwd user host) - (error "Login failed")))) - -;; For multi-actions. - -(defun tramp-process-one-multi-action (p method user host actions) - "Wait for output from the shell and perform one action." - (let (found item pattern action todo) - (erase-buffer) - (tramp-message 9 "Waiting 60s for prompt from remote shell") - (with-timeout (60 (throw 'tramp-action 'timeout)) - (while (not found) - (tramp-accept-process-output p 1) - (setq todo actions) - (goto-char (point-min)) - (while todo - (goto-char (point-min)) - (setq item (pop todo)) - (setq pattern (symbol-value (nth 0 item))) - (setq action (nth 1 item)) - (tramp-message 10 "Looking for regexp \"%s\" from remote shell" - pattern) - (when (re-search-forward (concat pattern "\\'") nil t) - (setq found (funcall action p method user host))))) - found))) - -(defun tramp-process-multi-actions (p method user host actions) - "Perform actions until success." - (let (exit) - (while (not exit) - (tramp-message 9 "Waiting for prompts from remote shell") - (setq exit - (catch 'tramp-action - (tramp-process-one-multi-action p method user host actions) - nil))) - (unless (eq exit 'ok) - (tramp-clear-passwd user host) - (error "Login failed")))) - -;; Functions to execute when we have seen the remote shell prompt but -;; before we exec the Bourne-ish shell. Note that these commands -;; might be sent to any shell, not just a Bourne-ish shell. This -;; means that the commands need to work in all shells. (It is also -;; okay for some commands to just fail with an error message, but -;; please make sure that they at least don't crash the odd shell people -;; might be running...) -(defun tramp-process-initial-commands (p - multi-method method user host - commands) - "Send list of commands to remote host, in order." - (let (cmd) - (while commands - (setq cmd (pop commands)) - (erase-buffer) - (tramp-message 10 "Sending command to remote shell: %s" - cmd) - (tramp-send-command multi-method method user host cmd nil t) - (tramp-barf-if-no-shell-prompt - p 60 "Remote shell command failed: %s" cmd)) - (erase-buffer))) - -;; The actual functions for opening connections. - -(defun tramp-open-connection-telnet (multi-method method user host) - "Open a connection using a telnet METHOD. -This starts the command `telnet HOST ARGS'[*], then waits for a remote -login prompt, then sends the user name USER, then waits for a remote -password prompt. It queries the user for the password, then sends the -password to the remote host. - -If USER is nil, uses value returned by `(user-login-name)' instead. - -Recognition of the remote shell prompt is based on the variables -`shell-prompt-pattern' and `tramp-shell-prompt-pattern' which must be -set up correctly. - -Please note that it is NOT possible to use this connection method -together with an out-of-band transfer method! You must use an inline -transfer method. - -Maybe the different regular expressions need to be tuned. - -* Actually, the telnet program as well as the args to be used can be - specified in the method parameters, see the variable `tramp-methods'." - (save-match-data - (when (tramp-method-out-of-band-p multi-method method user host) - (error "Cannot use out-of-band method `%s' with telnet connection method" - method)) - (when multi-method - (error "Cannot multi-connect using telnet connection method")) - (tramp-pre-connection multi-method method user host tramp-chunksize) - (tramp-message 7 "Opening connection for %s@%s using %s..." - (or user (user-login-name)) host method) - (let ((process-environment (copy-sequence process-environment))) - (setenv "TERM" tramp-terminal-type) - (setenv "LC_ALL" "C") - (setenv "PROMPT_COMMAND") - (setenv "PS1" "$ ") - (let* ((default-directory (tramp-temporary-file-directory)) - ;; If we omit the conditional here, then we would use - ;; `undecided-dos' in some cases. With the conditional, - ;; we use nil in these cases. Which one is right? - (coding-system-for-read (unless (and (not (featurep 'xemacs)) - (> emacs-major-version 20)) - tramp-dos-coding-system)) - (p (apply 'start-process - (tramp-buffer-name multi-method method user host) - (tramp-get-buffer multi-method method user host) - (tramp-get-method-parameter - multi-method - (tramp-find-method multi-method method user host) - user host 'tramp-login-program) - host - (tramp-get-method-parameter - multi-method - (tramp-find-method multi-method method user host) - user host 'tramp-login-args))) - (found nil) - (pw nil)) - (tramp-set-process-query-on-exit-flag p nil) - (set-buffer (tramp-get-buffer multi-method method user host)) - (erase-buffer) - (tramp-process-actions p multi-method method user host - tramp-actions-before-shell 60) - (tramp-open-connection-setup-interactive-shell - p multi-method method user host) - (tramp-post-connection multi-method method user host))))) - - -(defun tramp-open-connection-rsh (multi-method method user host) - "Open a connection using an rsh METHOD. -This starts the command `rsh HOST -l USER'[*], then waits for a remote -password or shell prompt. If a password prompt is seen, the user is -queried for a password, this function sends the password to the remote -host and waits for a shell prompt. - -If USER is nil, start the command `rsh HOST'[*] instead - -Recognition of the remote shell prompt is based on the variables -`shell-prompt-pattern' and `tramp-shell-prompt-pattern' which must be -set up correctly. - -Kludgy feature: if HOST has the form \"xx#yy\", then yy is assumed to -be a port number for ssh, and \"-p yy\" will be added to the list of -arguments, and xx will be used as the host name to connect to. - -* Actually, the rsh program to be used can be specified in the - method parameters, see the variable `tramp-methods'." - (save-match-data - (when multi-method - (error "Cannot multi-connect using rsh connection method")) - (tramp-pre-connection multi-method method user host tramp-chunksize) - (if (and user (not (string= user ""))) - (tramp-message 7 "Opening connection for %s@%s using %s..." - user host method) - (tramp-message 7 "Opening connection at %s using %s..." host method)) - (let ((process-environment (copy-sequence process-environment)) - (bufnam (tramp-buffer-name multi-method method user host)) - (buf (tramp-get-buffer multi-method method user host)) - (login-program (tramp-get-method-parameter - multi-method - (tramp-find-method multi-method method user host) - user host 'tramp-login-program)) - (login-args (mapcar - (lambda (x) - (format-spec - x `((?t . ,(format "/tmp/%s" tramp-temp-name-prefix))))) - (tramp-get-method-parameter - multi-method - (tramp-find-method multi-method method user host) - user host 'tramp-login-args))) - (real-host host)) - ;; The following should be changed. We need a more general - ;; mechanism to parse extra host args. - (when (string-match "\\([^#]*\\)#\\(.*\\)" host) - (setq login-args (cons "-p" (cons (match-string 2 host) login-args))) - (setq real-host (match-string 1 host))) - (setenv "TERM" tramp-terminal-type) - (setenv "LC_ALL" "C") - (setenv "PROMPT_COMMAND") - (setenv "PS1" "$ ") - (let* ((default-directory (tramp-temporary-file-directory)) - ;; If we omit the conditional, we would use - ;; `undecided-dos' in some cases. With the conditional, - ;; we use nil in these cases. Which one is right? - (coding-system-for-read (unless (and (not (featurep 'xemacs)) - (> emacs-major-version 20)) - tramp-dos-coding-system)) - (p (if (and user (not (string= user ""))) - (apply #'start-process bufnam buf login-program - real-host "-l" user login-args) - (apply #'start-process bufnam buf login-program - real-host login-args))) - (found nil)) - (tramp-set-process-query-on-exit-flag p nil) - - (set-buffer buf) - (tramp-process-actions p multi-method method user host - tramp-actions-before-shell 60) - (tramp-message 7 "Initializing remote shell") - (tramp-open-connection-setup-interactive-shell - p multi-method method user host) - (tramp-post-connection multi-method method user host))))) - -(defun tramp-open-connection-su (multi-method method user host) - "Open a connection using the `su' program with METHOD. -This starts `su - USER', then waits for a password prompt. The HOST -name must be equal to the local host name or to `localhost'. - -If USER is nil, uses value returned by user-login-name instead. - -Recognition of the remote shell prompt is based on the variables -`shell-prompt-pattern' and `tramp-shell-prompt-pattern' which must be -set up correctly. Note that the other user may have a different shell -prompt than you do, so it is not at all unlikely that the variable -`shell-prompt-pattern' is set up wrongly!" - (save-match-data - (when (tramp-method-out-of-band-p multi-method method user host) - (error "Cannot use out-of-band method `%s' with `su' connection method" - method)) - (unless (or (string-match (concat "^" (regexp-quote host)) - (system-name)) - (string= "localhost" host) - (string= "" host)) - (error - "Cannot connect to different host `%s' with `su' connection method" - host)) - (tramp-pre-connection multi-method method user host tramp-chunksize) - (tramp-message 7 "Opening connection for `%s' using `%s'..." - (or user "<root>") method) - (let ((process-environment (copy-sequence process-environment))) - (setenv "TERM" tramp-terminal-type) - (setenv "LC_ALL" "C") - (setenv "PROMPT_COMMAND") - (setenv "PS1" "$ ") - (let* ((default-directory (tramp-temporary-file-directory)) - ;; If we omit the conditional, we use `undecided-dos' in - ;; some cases. With the conditional, we use nil in these - ;; cases. What's the difference? Which one is right? - (coding-system-for-read (unless (and (not (featurep 'xemacs)) - (> emacs-major-version 20)) - tramp-dos-coding-system)) - (p (apply 'start-process - (tramp-buffer-name multi-method method user host) - (tramp-get-buffer multi-method method user host) - (tramp-get-method-parameter - multi-method - (tramp-find-method multi-method method user host) - user host 'tramp-login-program) - (mapcar - (lambda (x) - (format-spec x `((?u . ,(or user "root"))))) - (tramp-get-method-parameter - multi-method - (tramp-find-method multi-method method user host) - user host 'tramp-login-args)))) - (found nil) - (pw nil)) - (tramp-set-process-query-on-exit-flag p nil) - (set-buffer (tramp-get-buffer multi-method method user host)) - (tramp-process-actions p multi-method method user host - tramp-actions-before-shell 60) - (tramp-open-connection-setup-interactive-shell - p multi-method method user host) - (tramp-post-connection multi-method method - user host))))) - -;; HHH: Not Changed. Multi method. It is not clear to me how this can -;; handle not giving a user name in the "file name". -;; -;; This is more difficult than for the single-hop method. In the -;; multi-hop-method, the desired behaviour should be that the -;; user must specify names for the telnet hops of which the user -;; name is different than the "original" name (or different from -;; the previous hop. -(defun tramp-open-connection-multi (multi-method method user host) - "Open a multi-hop connection using METHOD. -This uses a slightly changed file name syntax. The idea is to say - [multi/telnet:u1@h1/rsh:u2@h2]/path/to/file -This will use telnet to log in as u1 to h1, then use rsh from there to -log in as u2 to h2." - (save-match-data - (unless multi-method - (error "Multi-hop open connection function called on non-multi method")) - (when (tramp-method-out-of-band-p multi-method method user host) - (error "No out of band multi-hop connections")) - (unless (and (arrayp method) (not (stringp method))) - (error "METHOD must be an array of strings for multi methods")) - (unless (and (arrayp user) (not (stringp user))) - (error "USER must be an array of strings for multi methods")) - (unless (and (arrayp host) (not (stringp host))) - (error "HOST must be an array of strings for multi methods")) - (unless (and (= (length method) (length user)) - (= (length method) (length host))) - (error "Arrays METHOD, USER, HOST must have equal length")) - (tramp-pre-connection multi-method method user host tramp-chunksize) - (tramp-message 7 "Opening `%s' connection..." multi-method) - (let ((process-environment (copy-sequence process-environment))) - (setenv "TERM" tramp-terminal-type) - (setenv "LC_ALL" "C") - (setenv "PROMPT_COMMAND") - (setenv "PS1" "$ ") - (let* ((default-directory (tramp-temporary-file-directory)) - ;; If we omit the conditional, we use `undecided-dos' in - ;; some cases. With the conditional, we use nil in these - ;; cases. What's the difference? Which one is right? - (coding-system-for-read (unless (and (not (featurep 'xemacs)) - (> emacs-major-version 20)) - tramp-dos-coding-system)) - (p (start-process (tramp-buffer-name multi-method method user host) - (tramp-get-buffer multi-method method user host) - tramp-multi-sh-program)) - (num-hops (length method)) - (i 0)) - (tramp-set-process-query-on-exit-flag p nil) - (tramp-message 9 "Waiting 60s for local shell to come up...") - (unless (tramp-wait-for-regexp - p 60 (format "\\(%s\\)\\'\\|\\(%s\\)\\'" - shell-prompt-pattern tramp-shell-prompt-pattern)) - (pop-to-buffer (buffer-name)) - (kill-process p) - (error "Couldn't find local shell prompt")) - ;; Now do all the connections as specified. - (while (< i num-hops) - (let* ((m (aref method i)) - (u (aref user i)) - (h (aref host i)) - (entry (assoc m tramp-multi-connection-function-alist)) - (multi-func (nth 1 entry)) - (command (nth 2 entry))) - ;; The multi-funcs don't need to do save-match-data, as that - ;; is done here. - (funcall multi-func p m u h command) - (erase-buffer) - (setq i (1+ i)))) - (tramp-open-connection-setup-interactive-shell - p multi-method method user host) - (tramp-post-connection multi-method method user host))))) - -;; HHH: Changed. Multi method. Don't know how to handle this in the case -;; of no user name provided. Hack to make it work as it did before: -;; changed `user' to `(or user (user-login-name))' in the places where -;; the value is actually used. -(defun tramp-multi-connect-telnet (p method user host command) - "Issue `telnet' command. -Uses shell COMMAND to issue a `telnet' command to log in as USER to -HOST. You can use percent escapes in COMMAND: `%h' is replaced with -the host name, and `%n' is replaced with an end of line character, as -set in `tramp-rsh-end-of-line'. Use `%%' if you want a literal percent -character. - -If USER is nil, uses the return value of (user-login-name) instead." - (let ((cmd (format-spec command - `((?h . ,host) (?n . ,tramp-rsh-end-of-line)))) - (cmd1 (format-spec command `((?h . ,host) (?n . "")))) - found pw) - (erase-buffer) - (tramp-message 9 "Sending telnet command `%s'" cmd1) - (process-send-string p cmd) - (tramp-process-multi-actions p method user host - tramp-multi-actions))) - -;; HHH: Changed. Multi method. Don't know how to handle this in the case -;; of no user name provided. Hack to make it work as it did before: -;; changed `user' to `(or user (user-login-name))' in the places where -;; the value is actually used. -(defun tramp-multi-connect-rlogin (p method user host command) - "Issue `rlogin' command. -Uses shell COMMAND to issue an `rlogin' command to log in as USER to -HOST. You can use percent escapes in COMMAND. `%u' will be replaced -with the user name, `%h' will be replaced with the host name, and `%n' -will be replaced with the value of `tramp-rsh-end-of-line'. You can use -`%%' if you want to use a literal percent character. - -If USER is nil, uses the return value of (user-login-name) instead." - (let ((cmd (format-spec command `((?h . ,host) - (?u . ,(or user (user-login-name))) - (?n . ,tramp-rsh-end-of-line)))) - (cmd1 (format-spec command `((?h . ,host) - (?u . ,(or user (user-login-name))) - (?n . "")))) - found) - (erase-buffer) - (tramp-message 9 "Sending rlogin command `%s'" cmd1) - (process-send-string p cmd) - (tramp-process-multi-actions p method user host - tramp-multi-actions))) - -;; HHH: Changed. Multi method. Don't know how to handle this in the case -;; of no user name provided. Hack to make it work as it did before: -;; changed `user' to `(or user (user-login-name))' in the places where -;; the value is actually used. -(defun tramp-multi-connect-su (p method user host command) - "Issue `su' command. -Uses shell COMMAND to issue a `su' command to log in as USER on -HOST. The HOST name is ignored, this just changes the user id on the -host currently logged in to. - -If USER is nil, uses the return value of (user-login-name) instead. - -You can use percent escapes in the COMMAND. `%u' is replaced with the -user name, and `%n' is replaced with the value of -`tramp-rsh-end-of-line'. Use `%%' if you want a literal percent -character." - (let ((cmd (format-spec command `((?u . ,(or user (user-login-name))) - (?n . ,tramp-rsh-end-of-line)))) - (cmd1 (format-spec command `((?u . ,(or user (user-login-name))) - (?n . "")))) - found) - (erase-buffer) - (tramp-message 9 "Sending su command `%s'" cmd1) - (process-send-string p cmd) - (tramp-process-multi-actions p method user host - tramp-multi-actions))) + (tramp-clear-passwd vec) + (tramp-error-with-buffer + nil vec 'file-error + (cond + ((eq exit 'permission-denied) "Permission denied") + ((eq exit 'process-died) "Process died") + (t "Login failed")))))) ;; Utility functions. -(defun tramp-accept-process-output - (&optional process timeout timeout-msecs) +(defun tramp-accept-process-output (&optional proc timeout timeout-msecs) "Like `accept-process-output' for Tramp processes. This is needed in order to hide `last-coding-system-used', which is set for process communication also." - (let (last-coding-system-used) - (accept-process-output process timeout timeout-msecs))) + (with-current-buffer (process-buffer proc) + (tramp-message proc 10 "%s %s" proc (process-status proc)) + (let (buffer-read-only last-coding-system-used) + ;; Under Windows XP, accept-process-output doesn't return + ;; sometimes. So we add an additional timeout. + (with-timeout ((or timeout 1)) + (accept-process-output proc timeout timeout-msecs))) + (tramp-message proc 10 "\n%s" (buffer-string)))) + +(defun tramp-check-for-regexp (proc regexp) + "Check whether REGEXP is contained in process buffer of PROC. +Erase echoed commands if exists." + (with-current-buffer (process-buffer proc) + (goto-char (point-min)) + ;; Check whether we need to remove echo output. + (when (and (tramp-get-connection-property proc "check-remote-echo" nil) + (re-search-forward tramp-echoed-echo-mark-regexp nil t)) + (let ((begin (match-beginning 0))) + (when (re-search-forward tramp-echoed-echo-mark-regexp nil t) + ;; Discard echo from remote output. + (tramp-set-connection-property proc "check-remote-echo" nil) + (tramp-message proc 5 "echo-mark found") + (forward-line) + (delete-region begin (point)) + (goto-char (point-min))))) + ;; No echo to be handled, now we can look for the regexp. + (when (not (tramp-get-connection-property proc "check-remote-echo" nil)) + (re-search-forward regexp nil t)))) (defun tramp-wait-for-regexp (proc timeout regexp) "Wait for a REGEXP to appear from process PROC within TIMEOUT seconds. Expects the output of PROC to be sent to the current buffer. Returns the string that matched, or nil. Waits indefinitely if TIMEOUT is nil." - (let ((found nil) - (start-time (current-time))) - (cond (timeout - ;; Work around a bug in XEmacs 21, where the timeout - ;; expires faster than it should. This degenerates - ;; to polling for buggy XEmacsen, but oh, well. - (while (and (not found) - (< (tramp-time-diff (current-time) start-time) - timeout)) - (with-timeout (timeout) - (while (not found) - (tramp-accept-process-output proc 1) - (unless (memq (process-status proc) '(run open)) - (error "Process has died")) - (goto-char (point-min)) - (setq found (re-search-forward regexp nil t)))))) - (t - (while (not found) - (tramp-accept-process-output proc 1) - (unless (memq (process-status proc) '(run open)) - (error "Process has died")) - (goto-char (point-min)) - (setq found (re-search-forward regexp nil t))))) - (when tramp-debug-buffer - (append-to-buffer - (tramp-get-debug-buffer tramp-current-multi-method tramp-current-method - tramp-current-user tramp-current-host) - (point-min) (point-max)) + (with-current-buffer (process-buffer proc) + (let ((found (tramp-check-for-regexp proc regexp)) + (start-time (current-time))) + (cond (timeout + ;; Work around a bug in XEmacs 21, where the timeout + ;; expires faster than it should. This degenerates + ;; to polling for buggy XEmacsen, but oh, well. + (while (and (not found) + (< (tramp-time-diff (current-time) start-time) + timeout)) + (with-timeout (timeout) + (while (not found) + (tramp-accept-process-output proc 1) + (unless (memq (process-status proc) '(run open)) + (tramp-error-with-buffer + nil proc 'file-error "Process has died")) + (setq found (tramp-check-for-regexp proc regexp)))))) + (t + (while (not found) + (tramp-accept-process-output proc 1) + (unless (memq (process-status proc) '(run open)) + (tramp-error-with-buffer + nil proc 'file-error "Process has died")) + (setq found (tramp-check-for-regexp proc regexp))))) + (tramp-message proc 6 "\n%s" (buffer-string)) (when (not found) - (save-excursion - (set-buffer - (tramp-get-debug-buffer tramp-current-multi-method tramp-current-method - tramp-current-user tramp-current-host)) - (goto-char (point-max)) - (insert "[[Regexp `" regexp "' not found" - (if timeout (format " in %d secs" timeout) "") - "]]")))) - found)) - -(defun tramp-wait-for-shell-prompt (proc timeout) - "Wait for the shell prompt to appear from process PROC within TIMEOUT seconds. -See `tramp-wait-for-regexp' for more details. -Shell prompt pattern is determined by variables `shell-prompt-pattern' -and `tramp-shell-prompt-pattern'." - (tramp-wait-for-regexp - proc timeout - (format "\\(%s\\|%s\\)\\'" - shell-prompt-pattern tramp-shell-prompt-pattern))) + (if timeout + (tramp-error + proc 'file-error "[[Regexp `%s' not found in %d secs]]" + regexp timeout) + (tramp-error proc 'file-error "[[Regexp `%s' not found]]" regexp))) + found))) (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-shell-prompt proc timeout) - (pop-to-buffer (buffer-name)) - (apply 'error error-args))) - -(defun tramp-enter-password (p prompt user host) - "Prompt for a password and send it to the remote end. -Uses PROMPT as a prompt and sends the password to process P." - (let ((pw (tramp-read-passwd user host prompt))) - (erase-buffer) - (process-send-string - p (concat pw - (or (tramp-get-method-parameter - tramp-current-multi-method - tramp-current-method - tramp-current-user - tramp-current-host - 'tramp-password-end-of-line) - tramp-default-password-end-of-line))))) - -;; HHH: Not Changed. This might handle the case where USER is not -;; given in the "File name" very poorly. Then, the local -;; variable tramp-current-user will be set to nil. -(defun tramp-pre-connection (multi-method method user host chunksize) - "Do some setup before actually logging in. -METHOD, USER and HOST specify the connection." - (set-buffer (tramp-get-buffer multi-method method user host)) - (set (make-local-variable 'tramp-current-multi-method) multi-method) - (set (make-local-variable 'tramp-current-method) method) - (set (make-local-variable 'tramp-current-user) user) - (set (make-local-variable 'tramp-current-host) host) - (set (make-local-variable 'tramp-chunksize) chunksize) - (set (make-local-variable 'inhibit-eol-conversion) nil) - (erase-buffer)) - -(defun tramp-open-connection-setup-interactive-shell - (p multi-method method user host) + (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))) + +;; We don't call `tramp-send-string' in order to hide the password +;; from the debug buffer, and because end-of-line handling of the +;; string. +(defun tramp-enter-password (proc) + "Prompt for a password and send it to the remote end." + (process-send-string + proc (concat (tramp-read-passwd proc) + (or (tramp-get-method-parameter + tramp-current-method + 'tramp-password-end-of-line) + tramp-default-password-end-of-line)))) + +(defun tramp-open-connection-setup-interactive-shell (proc vec) "Set up an interactive shell. -Mainly sets the prompt and the echo correctly. P is the shell process -to set up. METHOD, USER and HOST specify the connection." - ;; Wait a bit in case the remote end feels like sending a little - ;; junk first. It seems that fencepost.gnu.org does this when doing - ;; a Kerberos login. - (sit-for 1) - (tramp-discard-garbage-erase-buffer p multi-method method user host) - (tramp-process-initial-commands p multi-method method user host - tramp-initial-commands) - ;; 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. $PROMP_COMMAND is another way to set the prompt - ;; in /bin/bash, it must be discarded as well. - (tramp-send-command-internal - multi-method method user host - (format "exec env 'ENV=' 'PROMPT_COMMAND=' 'PS1=$ ' %s" - (tramp-get-method-parameter - multi-method method user host 'tramp-remote-sh)) - (format "remote `%s' to come up" - (tramp-get-method-parameter - multi-method method user host 'tramp-remote-sh))) - (tramp-barf-if-no-shell-prompt - p 30 - "Remote `%s' didn't come up. See buffer `%s' for details" - (tramp-get-method-parameter multi-method method user host 'tramp-remote-sh) - (buffer-name)) - (tramp-message 8 "Setting up remote shell environment") - (tramp-discard-garbage-erase-buffer p multi-method method user host) - (tramp-send-command-internal multi-method method user host - "stty -inlcr -echo kill '^U'") - (erase-buffer) - ;; Ignore garbage after stty command. - (tramp-send-command-internal multi-method method user host - "echo foo") - (erase-buffer) - (tramp-send-command-internal multi-method method user host - "TERM=dumb; export TERM") - (erase-buffer) - ;; 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. - (when (or (not tramp-chunksize) (zerop tramp-chunksize)) - (tramp-message 9 "Checking remote host type for `send-process-string' bug") - (tramp-send-command-internal multi-method method user host - "(uname -sr) 2>/dev/null") - (goto-char (point-min)) - (when (looking-at "FreeBSD") - (setq tramp-chunksize 500))) +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 "$ ")) + ;; 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. $PROMP_COMMAND + ;; is another way to set the prompt in /bin/bash, it must be + ;; discarded as well. + (tramp-send-command + vec + (format + "exec env ENV='' PROMPT_COMMAND='' PS1='$ ' PS2='' PS3='' %s" + (tramp-get-method-parameter + (tramp-file-name-method vec) 'tramp-remote-sh)) + t) + + ;; 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") + ;; We can set $PS1 to `tramp-end-of-output' only when the echo has + ;; been disabled. Otherwise, the echo of the command would be + ;; regarded as prompt already. + (tramp-send-command + vec + (format "PS1='%s%s%s'" + tramp-rsh-end-of-line + tramp-end-of-output + tramp-rsh-end-of-line) + 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. - (save-excursion - (erase-buffer) - (tramp-message 9 "Determining coding system") - (tramp-send-command-internal multi-method method user host - "echo foo ; echo bar") + (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 (process-coding-system p) (cons 'undecided 'undecided))) - cs-decode cs-encode) - (when (symbolp cs) (setq cs (cons cs cs))) - (setq cs-decode (car cs)) - (setq cs-encode (cdr cs)) - (unless cs-decode (setq cs-decode 'undecided)) - (unless cs-encode (setq cs-encode 'undecided)) - (setq cs-encode (tramp-coding-system-change-eol-conversion - cs-encode 'unix)) - (when (search-forward "\r" nil t) - (setq cs-decode (tramp-coding-system-change-eol-conversion - cs-decode 'dos))) - (set-buffer-process-coding-system cs-decode cs-encode)) + ;; Use MULE to select the right EOL convention for communicating + ;; with the process. + (let* ((cs (or (funcall (symbol-function '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-coding-system-change-eol-conversion + cs-encode 'unix)) + (when (search-forward "\r" nil t) + (setq cs-decode (tramp-coding-system-change-eol-conversion + cs-decode 'dos))) + (funcall (symbol-function 'set-buffer-process-coding-system) + cs-decode cs-encode)) ;; Look for ^M and do something useful if found. (when (search-forward "\r" nil t) - ;; We have found a ^M but cannot frob the process coding system - ;; because we're running on a non-MULE Emacs. Let's try - ;; stty, instead. - (erase-buffer) - (tramp-message 9 "Trying `stty -onlcr'") - (tramp-send-command-internal multi-method method user host - "stty -onlcr")))) - (erase-buffer) - (tramp-message - 9 "Waiting 30s for `HISTFILE=$HOME/.tramp_history; HISTSIZE=1; export HISTFILE; export HISTSIZE'") - (tramp-send-command-internal - multi-method method user host - "HISTFILE=$HOME/.tramp_history; HISTSIZE=1; export HISTFILE; export HISTSIZE") - (erase-buffer) - (tramp-message 9 "Waiting 30s for `set +o vi +o emacs'") - (tramp-send-command-internal multi-method method user host - "set +o vi +o emacs") - (erase-buffer) - (tramp-message 9 "Waiting 30s for `unset MAIL MAILCHECK MAILPATH'") - (tramp-send-command-internal - multi-method method user host - "unset MAIL MAILCHECK MAILPATH 1>/dev/null 2>/dev/null") - (erase-buffer) - (tramp-message 9 "Waiting 30s for `unset CDPATH'") - (tramp-send-command-internal multi-method method user host - "unset CDPATH") - (erase-buffer) - (tramp-message 9 "Setting shell prompt") - ;; Douglas Gray Stephens <DGrayStephens@slb.com> says that we must - ;; use "\n" here, not tramp-rsh-end-of-line. We also manually frob - ;; the last time we sent a command, to avoid tramp-send-command to send - ;; "echo are you awake". - (setq tramp-last-cmd-time (current-time)) - (tramp-send-command - multi-method method user host - (format "PROMPT_COMMAND=''; PS1='%s%s%s'; PS2=''; PS3=''" - tramp-rsh-end-of-line - tramp-end-of-output - tramp-rsh-end-of-line)) - (tramp-wait-for-output)) - -(defun tramp-post-connection (multi-method method user host) - "Prepare a remote shell before being able to work on it. -METHOD, USER and HOST specify the connection. -Among other things, this finds a shell which groks tilde expansion, -tries to find an `ls' command which groks the `-n' option, sets the -locale to C and sets up the remote shell search path." + ;; 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. + (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))) + (funcall (symbol-function 'tramp-cleanup-connection) vec) + (signal + 'quit + (list (format + "Connection reset, because remote host changed from `%s' to `%s'" + old-uname new-uname))))) + + ;; 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 @@ -6278,168 +5756,25 @@ locale to C and sets up the remote shell search path." ;; the Solaris /bin/sh is a problem. I'm betting that all systems ;; with buggy /bin/sh implementations will have a working bash or ;; ksh. Whee... - (tramp-find-shell multi-method method user host) - ;; Without (sit-for 0.1) at least, my machine will almost always blow - ;; up on 'not numberp /root' - a race that causes the 'echo ~root' - ;; output of (tramp-find-shell) to show up along with the output of - ;; (tramp-find-ls-command) testing. - ;; - ;; I can't work out why this is a problem though. The (tramp-wait-for-output) - ;; call in (tramp-find-shell) *should* make this not happen, I thought. - ;; - ;; After much debugging I couldn't find any problem with the implementation - ;; of that function though. The workaround stays for me at least. :/ - ;; - ;; Daniel Pittman <daniel@danann.net> - (sleep-for 1) - (erase-buffer) - (tramp-find-file-exists-command multi-method method user host) - (make-local-variable 'tramp-ls-command) - (setq tramp-ls-command (tramp-find-ls-command multi-method method user host)) - (unless tramp-ls-command - (tramp-message - 1 - "Danger! Couldn't find ls which groks -n. Muddling through anyway") - (setq tramp-ls-command - (tramp-find-executable multi-method method user host - "ls" tramp-remote-path nil))) - (unless tramp-ls-command - (error "Fatal error: Couldn't find remote executable `ls'")) - (tramp-message 5 "Using remote command `%s' for getting directory listings" - tramp-ls-command) - (tramp-send-command multi-method method user host - (concat "tramp_set_exit_status () {" tramp-rsh-end-of-line - "return $1" tramp-rsh-end-of-line - "}")) - (tramp-wait-for-output) - ;; Set remote PATH variable. - (tramp-set-remote-path multi-method method user host "PATH" tramp-remote-path) - ;; Tell remote shell to use standard time format, needed for - ;; parsing `ls -l' output. - (tramp-send-command multi-method method user host - "LC_TIME=C; export LC_TIME; echo huhu") - (tramp-wait-for-output) - (tramp-send-command multi-method method user host - "mesg n; echo huhu") - (tramp-wait-for-output) - (tramp-send-command multi-method method user host - "biff n ; echo huhu") - (tramp-wait-for-output) - ;; Unalias ls(1) to work around issues with those silly people who make it - ;; spit out ANSI escapes or whatever. - (tramp-send-command multi-method method user host - "unalias ls; echo huhu") - (tramp-wait-for-output) - ;; Does `test A -nt B' work? Use abominable `find' construct if it - ;; doesn't. BSD/OS 4.0 wants the parentheses around the command, - ;; for otherwise the shell crashes. - (erase-buffer) - (make-local-variable 'tramp-test-groks-nt) - (tramp-send-command multi-method method user host - "( test / -nt / )") - (tramp-wait-for-output) - (goto-char (point-min)) - (setq tramp-test-groks-nt - (looking-at (format "\n%s\r?\n" (regexp-quote tramp-end-of-output)))) - (unless tramp-test-groks-nt - (tramp-send-command - multi-method method user host - (concat "tramp_test_nt () {" tramp-rsh-end-of-line - "test -n \"`find $1 -prune -newer $2 -print`\"" tramp-rsh-end-of-line - "}"))) - (tramp-wait-for-output) - ;; Send the fallback `uudecode' script. - (erase-buffer) - (tramp-send-string multi-method method user host tramp-uudecode) - (tramp-wait-for-output) - ;; Find a `perl'. - (erase-buffer) - (tramp-set-connection-property "perl-scripts" nil multi-method method user host) - (let ((tramp-remote-perl - (or (tramp-find-executable multi-method method user host - "perl5" tramp-remote-path nil) - (tramp-find-executable multi-method method user host - "perl" tramp-remote-path nil)))) - (when tramp-remote-perl - (tramp-set-connection-property "perl" tramp-remote-perl - multi-method method user host) - (unless (tramp-method-out-of-band-p multi-method method user host) - (tramp-message 5 "Sending the Perl `mime-encode' implementations.") - (tramp-send-string - multi-method method user host - (concat "tramp_encode () {\n" - (format tramp-perl-encode tramp-remote-perl) - " 2>/dev/null" - "\n}")) - (tramp-wait-for-output) - (tramp-send-string - multi-method method user host - (concat "tramp_encode_with_module () {\n" - (format tramp-perl-encode-with-module tramp-remote-perl) - " 2>/dev/null" - "\n}")) - (tramp-wait-for-output) - (tramp-message 5 "Sending the Perl `mime-decode' implementations.") - (tramp-send-string - multi-method method user host - (concat "tramp_decode () {\n" - (format tramp-perl-decode tramp-remote-perl) - " 2>/dev/null" - "\n}")) - (tramp-wait-for-output) - (tramp-send-string - multi-method method user host - (concat "tramp_decode_with_module () {\n" - (format tramp-perl-decode-with-module tramp-remote-perl) - " 2>/dev/null" - "\n}")) - (tramp-wait-for-output)))) - ;; Find ln(1) - (erase-buffer) - (let ((ln (tramp-find-executable multi-method method user host - "ln" tramp-remote-path nil))) - (when ln - (tramp-set-connection-property "ln" ln multi-method method user host))) - ;; Set uid and gid. - (erase-buffer) - (tramp-send-command multi-method method user host "id -u; id -g") - (tramp-wait-for-output) - (goto-char (point-min)) - (tramp-set-connection-property - "uid" (read (current-buffer)) multi-method method user host) - (tramp-set-connection-property - "gid" (read (current-buffer)) multi-method method user host) - ;; Find the right encoding/decoding commands to use. - (erase-buffer) - (unless (tramp-method-out-of-band-p multi-method method user host) - (tramp-find-inline-encoding multi-method method user host)) - ;; If encoding/decoding command are given, test to see if they work. - ;; CCC: Maybe it would be useful to run the encoder both locally and - ;; remotely to see if they produce the same result. - (let ((rem-enc (tramp-get-remote-encoding multi-method method user host)) - (rem-dec (tramp-get-remote-decoding multi-method method user host)) - (magic-string "xyzzy")) - (when (and (or rem-dec rem-enc) (not (and rem-dec rem-enc))) - (tramp-kill-process multi-method method user host) - ;; Improve error message and/or error check. - (error - "Must give both decoding and encoding command in method definition")) - (when (and rem-enc rem-dec) - (tramp-message - 5 - "Checking to see if encoding/decoding commands work on remote host...") + (tramp-find-shell vec) + + ;; Disable unexpected output. + (tramp-send-command vec "mesg n; biff n" 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 (split-string (car env) "=")) + (if (and (stringp (cadr item)) (not (string-equal (cadr item) ""))) + (tramp-send-command + vec (format "%s=%s; export %s" (car item) (cadr item) (car item)) t) + (push (car item) unset)) + (setq env (cdr env))) + (when unset (tramp-send-command - multi-method method user host - (format "echo %s | %s | %s" - (tramp-shell-quote-argument magic-string) rem-enc rem-dec)) - (tramp-wait-for-output) - (unless (looking-at (regexp-quote magic-string)) - (tramp-kill-process multi-method method user host) - (error "Remote host cannot execute de/encoding commands. See buffer `%s' for details" - (buffer-name))) - (erase-buffer) - (tramp-message - 5 "Checking to see if encoding/decoding commands work on remote host...done")))) + 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 @@ -6459,38 +5794,22 @@ locale to C and sets up the remote shell search path." ;; ;; For Irix, no solution is known yet. -(defvar tramp-coding-commands - '(("mimencode -b" "mimencode -u -b" - base64-encode-region base64-decode-region) - ("mmencode -b" "mmencode -u -b" - base64-encode-region base64-decode-region) - ("recode data..base64" "recode base64..data" - base64-encode-region base64-decode-region) - ("uuencode xxx" "uudecode -o /dev/stdout" - tramp-uuencode-region uudecode-decode-region) - ("uuencode xxx" "uudecode -o -" - tramp-uuencode-region uudecode-decode-region) - ("uuencode xxx" "uudecode -p" - tramp-uuencode-region uudecode-decode-region) - ("uuencode xxx" "tramp_uudecode" - tramp-uuencode-region uudecode-decode-region) - ("tramp_encode_with_module" "tramp_decode_with_module" - base64-encode-region base64-decode-region) - ("tramp_encode" "tramp_decode" - base64-encode-region base64-decode-region)) - "List of coding commands for inline transfer. +(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: -\(REMOTE-ENCODING REMOTE-DECODING LOCAL-ENCODING LOCAL-DECODING) +\(FORMAT ENCODING DECODING) -The REMOTE-ENCODING should be a string, giving a command accepting a -plain file on standard input and writing the encoded file to standard -output. The REMOTE-DECODING should also be a string, giving a command -accepting an encoded file on standard input and writing the decoded -file to standard output. +FORMAT is symbol describing the encoding/decoding format. It can be +`b64' for base64 encoding, `uu' for uu encoding, or `pack' for simple packing. -LOCAL-ENCODING and LOCAL-DECODING can be strings, giving commands, or -symbols, giving functions. If they are strings, then they can contain +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 @@ -6500,83 +5819,139 @@ 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.") -(defun tramp-find-inline-encoding (multi-method method user host) +(defconst tramp-remote-coding-commands + '((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 avalible 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-coding-commands'." - (let ((commands tramp-coding-commands) - (magic "xyzzy") - item found) - (while (and commands (null found)) - (setq item (pop commands)) - (catch 'wont-work - (let ((rem-enc (nth 0 item)) - (rem-dec (nth 1 item)) - (loc-enc (nth 2 item)) - (loc-dec (nth 3 item))) - ;; 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 reaons: 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! - (tramp-message-for-buffer - multi-method method user host 9 - "Checking remote encoding command `%s' for sanity" rem-enc) - (unless (zerop (tramp-send-command-and-check - multi-method method user host - (format "%s </dev/null" rem-enc) t)) - (throw 'wont-work nil)) - (tramp-message-for-buffer - multi-method method user host 9 - "Checking remote decoding command `%s' for sanity" rem-dec) - (unless (zerop (tramp-send-command-and-check - multi-method method user host - (format "echo %s | %s | %s" - magic rem-enc rem-dec) t)) - (throw 'wont-work nil)) - (save-excursion - (goto-char (point-min)) - (unless (looking-at (regexp-quote magic)) - (throw 'wont-work nil))) - ;; If the local encoder or decoder is a string, the - ;; corresponding command has to work locally. - (when (stringp loc-enc) - (tramp-message-for-buffer - multi-method method user host 9 - "Checking local encoding command `%s' for sanity" loc-enc) - (unless (zerop (tramp-call-local-coding-command - loc-enc nil nil)) - (throw 'wont-work nil))) - (when (stringp loc-dec) - (tramp-message-for-buffer - multi-method method user host 9 - "Checking local decoding command `%s' for sanity" loc-dec) - (unless (zerop (tramp-call-local-coding-command - loc-dec nil nil)) - (throw 'wont-work nil))) - ;; CCC: At this point, maybe we should check that the output - ;; of the commands is correct. But for the moment we will - ;; assume that commands working on empty input will also - ;; work in practice. - (setq found item)))) - ;; Did we find something? If not, issue error. If so, - ;; set connection properties. - (unless found - (error "Couldn't find an inline transfer encoding")) - (let ((rem-enc (nth 0 found)) - (rem-dec (nth 1 found)) - (loc-enc (nth 2 found)) - (loc-dec (nth 3 found))) - (tramp-message 10 "Using remote encoding %s" rem-enc) - (tramp-set-remote-encoding multi-method method user host rem-enc) - (tramp-message 10 "Using remote decoding %s" rem-dec) - (tramp-set-remote-decoding multi-method method user host rem-dec) - (tramp-message 10 "Using local encoding %s" loc-enc) - (tramp-set-local-encoding multi-method method user host loc-enc) - (tramp-message 10 "Using local decoding %s" loc-dec) - (tramp-set-local-decoding multi-method method user host loc-dec)))) +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 (zerop (tramp-send-command-and-check + vec (format "%s </dev/null" rem-enc) t)) + (throw 'wont-work-remote nil)) + + (when (not (stringp rem-dec)) + (let ((name (symbol-name rem-dec))) + (while (string-match (regexp-quote "-") name) + (setq name (replace-match "_" nil t name))) + (tramp-maybe-send-script vec (symbol-value rem-dec) name) + (setq rem-dec name))) + (tramp-message + vec 5 + "Checking remote decoding command `%s' for sanity" rem-dec) + (unless (zerop (tramp-send-command-and-check + vec + (format "echo %s | %s | %s" + magic rem-enc rem-dec) t)) + (throw 'wont-work-remote nil)) + + (with-current-buffer (tramp-get-buffer vec) + (goto-char (point-min)) + (unless (looking-at (regexp-quote magic)) + (throw 'wont-work-remote nil))) + + ;; `rem-enc' and `rem-dec' could be a string meanwhile. + (setq rem-enc (nth 1 ritem)) + (setq rem-dec (nth 2 ritem)) + (setq found t))))))) + + ;; Did we find something? If not, issue an error. + (unless found + (kill-process (tramp-get-connection-process vec)) + (tramp-error + vec 'file-error "Couldn't find an inline transfer encoding")) + + ;; Set connection properties. + (tramp-message vec 5 "Using local encoding `%s'" loc-enc) + (tramp-set-connection-property vec "local-encoding" loc-enc) + (tramp-message vec 5 "Using local decoding `%s'" loc-dec) + (tramp-set-connection-property vec "local-decoding" loc-dec) + (tramp-message vec 5 "Using remote encoding `%s'" rem-enc) + (tramp-set-connection-property vec "remote-encoding" rem-enc) + (tramp-message vec 5 "Using remote decoding `%s'" rem-dec) + (tramp-set-connection-property vec "remote-decoding" rem-dec)))) (defun tramp-call-local-coding-command (cmd input output) "Call the local encoding or decoding command. @@ -6586,25 +5961,114 @@ INPUT can also be nil which means `/dev/null'. OUTPUT can be a string (which specifies a filename), or t (which means standard output and thus the current buffer), or nil (which means discard it)." - (call-process - tramp-encoding-shell ;program - (when (and input (not (string-match "%s" cmd))) - input) ;input - (if (eq output t) t nil) ;output - nil ;redisplay + (tramp-local-call-process + tramp-encoding-shell + (when (and input (not (string-match "%s" cmd))) input) + (if (eq output t) t nil) + nil tramp-encoding-command-switch - ;; actual shell command (concat (if (string-match "%s" cmd) (format cmd input) cmd) (if (stringp output) (concat "> " output) "")))) -(defun tramp-maybe-open-connection (multi-method method user host) - "Maybe open a connection to HOST, logging in as USER, using METHOD. +(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 (nth 2 item)) + (when (and + ;; host + (string-match (or (nth 0 item) "") + (or (tramp-file-name-host (car target-alist)) "")) + ;; user + (string-match (or (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 + `((?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 (and (boundp 'tramp-gw-tunnel-method) + (string-match (format + "^\\(%s\\|%s\\)$" + (symbol-value 'tramp-gw-tunnel-method) + (symbol-value '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-get-method-parameter + (tramp-file-name-method hop) 'tramp-default-port) + (tramp-error + vec 'file-error + "Method `%s' is not supported for gateway access." + (tramp-file-name-method hop))) + ;; Add default port if needed. + (unless + (string-match + tramp-host-with-port-regexp (tramp-file-name-host hop)) + (aset hop 2 + (concat + (tramp-file-name-host hop) tramp-prefix-port-format + (number-to-string + (tramp-get-method-parameter + (tramp-file-name-method hop) 'tramp-default-port))))) + ;; Open the gateway connection. + (add-to-list + 'target-alist + (vector + (tramp-file-name-method hop) (tramp-file-name-user hop) + (funcall (symbol-function '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))))) + + ;; 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." - (let ((p (get-buffer-process - (tramp-get-buffer multi-method method user host))) - last-cmd-time) + (let ((p (tramp-get-connection-process vec)) + (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 @@ -6612,239 +6076,286 @@ connection if a previous connection has died for some reason." ;; 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. - (save-excursion - (set-buffer (tramp-get-buffer multi-method method user host)) - (when (and tramp-last-cmd-time - (> (tramp-time-diff (current-time) tramp-last-cmd-time) 60) - p (processp p) (memq (process-status p) '(run open))) - (tramp-send-command - multi-method method user host "echo are you awake" nil t) - (unless (and (memq (process-status p) '(run open)) - (tramp-wait-for-output 10)) - (delete-process p) - (setq p nil)) - (erase-buffer))) + (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 nil) + (tramp-flush-connection-property p nil) + (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) + (if (zerop (length (tramp-file-name-user vec))) + (tramp-message + vec 3 "Opening connection for %s using %s..." + (tramp-file-name-host vec) + (tramp-file-name-method vec)) + (tramp-message + vec 3 "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)) - (let ((process-connection-type tramp-process-connection-type)) - (funcall (tramp-get-method-parameter - multi-method - (tramp-find-method multi-method method user host) - user host 'tramp-connection-function) - multi-method method user host))))) - -(defun tramp-send-command - (multi-method method user host command &optional noerase neveropen) - "Send the COMMAND to USER at HOST (logged in using METHOD). -Erases temporary buffer before sending the command (unless NOERASE -is true). -If optional seventh arg NEVEROPEN is non-nil, never try to open the -connection. This is meant to be used from -`tramp-maybe-open-connection' only." - (or neveropen - (tramp-maybe-open-connection multi-method method user host)) - (setq tramp-last-cmd-time (current-time)) - (setq tramp-last-cmd command) - (when tramp-debug-buffer - (save-excursion - (set-buffer (tramp-get-debug-buffer multi-method method user host)) - (goto-char (point-max)) - (tramp-insert-with-face 'bold (format "$ %s\n" command)))) - (let ((proc nil)) - (set-buffer (tramp-get-buffer multi-method method user host)) - (unless noerase (erase-buffer)) - (setq proc (get-buffer-process (current-buffer))) - (process-send-string proc - (concat command tramp-rsh-end-of-line)))) - -(defun tramp-send-command-internal - (multi-method method user host command &optional msg) - "Send command to remote host and wait for success. -Sends COMMAND, then waits 30 seconds for shell prompt." - (tramp-send-command multi-method method user host command t t) - (when msg - (tramp-message 9 "Waiting 30s for %s..." msg)) - (tramp-barf-if-no-shell-prompt - nil 30 - "Couldn't `%s', see buffer `%s'" command (buffer-name))) - -(defun tramp-wait-for-output (&optional timeout) + (delete-process p)) + (setenv "TERM" tramp-terminal-type) + (setenv "LC_ALL" "C") + (setenv "PROMPT_COMMAND") + (setenv "PS1" "$ ") + (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 + (or (tramp-get-connection-property vec "process-name" nil) + (tramp-buffer-name vec)) + (tramp-get-connection-buffer vec) + tramp-encoding-shell))) + (first-hop t)) + + (tramp-message + vec 6 "%s" (mapconcat 'identity (process-command p) " ")) + + ;; Check whether process is alive. + (set-process-sentinel p 'tramp-flush-connection-property) + (tramp-set-process-query-on-exit-flag p nil) + (tramp-message vec 3 "Waiting 60s for local shell to come up...") + (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)) + (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 gateway arguments if necessary. + (when (and gw gw-args) + (setq login-args (append login-args gw-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 gatewy. + (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 `((?h . ,l-host) (?u . ,l-user) (?p . ,l-port) + (?t . ,tmpfile)) + command + (concat + command " " + (mapconcat + '(lambda (x) + (setq x (mapcar '(lambda (y) (format-spec y spec)) x)) + (unless (member "" x) (mapconcat 'identity x " "))) + login-args " ") + ;; String to detect failed connection. Every single word must + ;; be enclosed with '\"'; otherwise it is detected + ;; during connection setup. + ;; Local shell could be a Windows COMSPEC. It doesn't know + ;; the ";" syntax, but we must exit always for `start-process'. + ;; "exec" does not work either. + (if first-hop + " && exit || exit" + "; echo \"Tramp\" \"connection\" \"closed\"; sleep 1")) + ;; We don't reach a Windows shell. Could be initial only. + first-hop nil) + + ;; 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))) + (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 rsh command." - (let ((proc (get-buffer-process (current-buffer))) - (found nil) - (start-time (current-time)) - (start-point (point)) - (end-of-output (concat "^" - (regexp-quote tramp-end-of-output) - "\r?$"))) - ;; Algorithm: get waiting output. See if last line contains - ;; end-of-output sentinel. If not, wait a bit and again get - ;; waiting output. Repeat until timeout expires or end-of-output - ;; sentinel is seen. Will hang if timeout is nil and - ;; end-of-output sentinel never appears. - (save-match-data - (cond (timeout - ;; Work around an XEmacs bug, where the timeout expires - ;; faster than it should. This degenerates into polling - ;; for buggy XEmacsen, but oh, well. - (while (and (not found) - (< (tramp-time-diff (current-time) start-time) - timeout)) - (with-timeout (timeout) - (while (not found) - (tramp-accept-process-output proc 1) - (unless (memq (process-status proc) '(run open)) - (error "Process has died")) - (goto-char (point-max)) - (forward-line -1) - (setq found (looking-at end-of-output)))))) - (t - (while (not found) - (tramp-accept-process-output proc 1) - (unless (memq (process-status proc) '(run open)) - (error "Process has died")) - (goto-char (point-max)) - (forward-line -1) - (setq found (looking-at end-of-output)))))) - ;; At this point, either the timeout has expired or we have found - ;; the end-of-output sentinel. - (when found - (goto-char (point-max)) - (forward-line -2) - (delete-region (point) (point-max))) - ;; If processing echoes, look for it in the first line and delete. - (when tramp-process-echoes - (save-excursion - (goto-char start-point) - (when (looking-at (regexp-quote tramp-last-cmd)) - (delete-region (point) (progn (forward-line 1) (point)))))) - ;; Add output to debug buffer if appropriate. - (when tramp-debug-buffer - (append-to-buffer - (tramp-get-debug-buffer tramp-current-multi-method tramp-current-method - tramp-current-user tramp-current-host) - (point-min) (point-max)) - (when (not found) - (save-excursion - (set-buffer - (tramp-get-debug-buffer tramp-current-multi-method tramp-current-method - tramp-current-user tramp-current-host)) - (goto-char (point-max)) - (insert "[[Remote prompt `" end-of-output "' not found" - (if timeout (format " in %d secs" timeout) "") - "]]")))) - (goto-char (point-min)) - ;; Return value is whether end-of-output sentinel was found. - found)) + (with-current-buffer (process-buffer proc) + ;; Initially, `tramp-end-of-output' is "$ ". There might be + ;; leading escape sequences, which must be ignored. + (let* ((regexp (format "^[^$\n]*%s\r?$" (regexp-quote tramp-end-of-output))) + (found (tramp-wait-for-regexp proc timeout regexp))) + (if found + (let (buffer-read-only) + (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 (multi-method method user host command - &optional subshell) +(defun tramp-send-command-and-check (vec command &optional subshell) "Run COMMAND and check its exit status. -MULTI-METHOD and METHOD specify how to log in (as USER) to the remote HOST. Sends `echo $?' along with the COMMAND for checking the exit status. If COMMAND is nil, just sends `echo $?'. Returns the exit status found. If the optional argument SUBSHELL is non-nil, the command is executed in a subshell, ie surrounded by parentheses." - (tramp-send-command multi-method method user host - (concat (if subshell "( " "") - command - (if command " 2>/dev/null; " "") - "echo tramp_exit_status $?" - (if subshell " )" " "))) - (tramp-wait-for-output) - (goto-char (point-max)) - (unless (search-backward "tramp_exit_status " nil t) - (error "Couldn't find exit status of `%s'" command)) - (skip-chars-forward "^ ") - (read (current-buffer))) - -(defun tramp-barf-unless-okay (multi-method method user host command subshell - signal fmt &rest args) + (tramp-send-command + vec + (concat (if subshell "( " "") + command + (if command " 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 + (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 (zerop (tramp-send-command-and-check - multi-method method user host command subshell)) - ;; CCC: really pop-to-buffer? Maybe it's appropriate to be more - ;; silent. - (pop-to-buffer (current-buffer)) - (funcall 'signal signal (apply 'format fmt args)))) + (unless (zerop (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-" (tramp-compat-line-end-position) t) + (error nil))) + (error (tramp-error + vec 'file-error + "`%s' does not return a valid Lisp expression: `%s'" + command (buffer-string)))))) ;; It seems that Tru64 Unix does not like it if long strings are sent ;; to it in one go. (This happens when sending the Perl ;; `file-attributes' implementation, for instance.) Therefore, we -;; have this function which waits a bit at each line. -(defun tramp-send-string - (multi-method method user host string) - "Send the STRING to USER at HOST using METHOD. +;; have this function which sends the string in chunks. +(defun tramp-send-string (vec string) + "Send the STRING via connection VEC. The STRING is expected to use Unix line-endings, but the lines sent to the remote host use line-endings as defined in the variable -`tramp-rsh-end-of-line'." - (let ((proc (get-buffer-process - (tramp-get-buffer multi-method method user host)))) - (unless proc - (error "Can't send string to remote host -- not logged in")) - ;; debug message - (when tramp-debug-buffer - (save-excursion - (set-buffer (tramp-get-debug-buffer multi-method method user host)) - (goto-char (point-max)) - (tramp-insert-with-face 'bold (format "$ %s\n" string)))) - ;; replace "\n" by `tramp-rsh-end-of-line' - (setq string - (mapconcat 'identity - (split-string string "\n") - tramp-rsh-end-of-line)) - (unless (or (string= string "") - (string-equal (substring string -1) tramp-rsh-end-of-line)) - (setq string (concat string tramp-rsh-end-of-line))) - ;; send the string - (if (and tramp-chunksize (not (zerop tramp-chunksize))) - (let ((pos 0) - (end (length string))) - (while (< pos end) - (tramp-message-for-buffer - multi-method method user host 10 - "Sending chunk from %s to %s" - pos (min (+ pos tramp-chunksize) end)) - (process-send-string - proc (substring string pos (min (+ pos tramp-chunksize) end))) - (setq pos (+ pos tramp-chunksize)) - (sleep-for 0.1))) - (process-send-string proc string)))) - -(defun tramp-send-eof (multi-method method user host) - "Send EOF to the remote end. -METHOD, HOST and USER specify the connection." - (let ((proc (get-buffer-process - (tramp-get-buffer multi-method method user host)))) - (unless proc - (error "Can't send EOF to remote host -- not logged in")) - (process-send-eof proc))) -; (process-send-string proc "\^D"))) - -(defun tramp-kill-process (multi-method method user host) - "Kill the connection process used by Tramp. -MULTI-METHOD, METHOD, USER, and HOST specify the connection." - (let ((proc (get-buffer-process - (tramp-get-buffer multi-method method user host)))) - (kill-process proc))) - -(defun tramp-discard-garbage-erase-buffer (p multi-method method user host) - "Erase buffer, then discard subsequent garbage. -If `tramp-discard-garbage' is nil, just erase buffer." - (if (not tramp-discard-garbage) - (erase-buffer) - (while (prog1 (erase-buffer) (tramp-accept-process-output p 0.25)) - (when tramp-debug-buffer - (save-excursion - (set-buffer (tramp-get-debug-buffer multi-method method user host)) - (goto-char (point-max)) - (tramp-insert-with-face - 'bold (format "Additional characters detected\n"))))))) +`tramp-rsh-end-of-line'. The communication buffer is erased before sending." + (let* ((p (tramp-get-connection-process vec)) + (chunksize (tramp-get-connection-property p "chunksize" nil))) + (unless p + (tramp-error + vec 'file-error "Can't send string to remote host -- not logged in")) + (tramp-set-connection-property p "last-cmd-time" (current-time)) + (tramp-message vec 10 "%s" string) + (with-current-buffer (tramp-get-connection-buffer vec) + ;; Clean up the buffer. We cannot call `erase-buffer' because + ;; narrowing might be in effect. + (let (buffer-read-only) (delete-region (point-min) (point-max))) + ;; Replace "\n" by `tramp-rsh-end-of-line'. + (setq string + (mapconcat 'identity + (split-string string "\n") + tramp-rsh-end-of-line)) + (unless (or (string= string "") + (string-equal (substring string -1) tramp-rsh-end-of-line)) + (setq string (concat string tramp-rsh-end-of-line))) + ;; Send the string. + (if (and chunksize (not (zerop chunksize))) + (let ((pos 0) + (end (length string))) + (while (< pos end) + (tramp-message + vec 10 "Sending chunk from %s to %s" + pos (min (+ pos chunksize) end)) + (process-send-string + p (substring string pos (min (+ pos chunksize) end))) + (setq pos (+ pos chunksize)))) + (process-send-string p string))))) (defun tramp-mode-string-to-int (mode-string) "Converts a ten-letter `drwxrwxrwx'-style mode string into mode bits." @@ -6917,27 +6428,83 @@ If `tramp-discard-garbage' is nil, just erase buffer." (t (error "Tenth char `%c' must be one of `xtT-'" other-execute-or-sticky))))))) -(defun tramp-convert-file-attributes (multi-method method user host attr) - "Convert file-attributes ATTR generated by perl script or ls. +(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." + ;; 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)))) - ;; Set file's gid change bit. Possible only when id-format is 'integer. - (when (numberp (nth 3 attr)) - (setcar (nthcdr 9 attr) - (not (eql (nth 3 attr) - (tramp-get-remote-gid multi-method method user host))))) + ;; Convert directory indication bit. + (if (string-match "^d" (nth 8 attr)) + (setcar attr t) + (if (and (listp (car attr)) (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 + (list (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 multi-method method user host)) + (tramp-get-device vec)) attr) -(defun tramp-get-device (multi-method method user host) +(defun tramp-get-inode (vec) + "Returns the virtual inode number. +If it doesn't exist, generate a new one." + (let ((string (tramp-make-tramp-file-name + (tramp-file-name-method vec) + (tramp-file-name-user vec) + (tramp-file-name-host vec) + ""))) + (unless (assoc string tramp-inodes) + (add-to-list 'tramp-inodes + (list string (length tramp-inodes)))) + (nth 1 (assoc string tramp-inodes)))) + +(defun tramp-get-device (vec) "Returns the virtual device number. If it doesn't exist, generate a new one." - (let ((string (tramp-make-tramp-file-name multi-method method user host ""))) + (let ((string (tramp-make-tramp-file-name + (tramp-file-name-method vec) + (tramp-file-name-user vec) + (tramp-file-name-host vec) + ""))) (unless (assoc string tramp-devices) (add-to-list 'tramp-devices (list string (length tramp-devices)))) @@ -6957,7 +6524,6 @@ If it doesn't exist, generate a new one." (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'." @@ -6970,7 +6536,6 @@ This is used internally by `tramp-file-mode-from-int'." (and suid (upcase suid-text)) ; suid, !execute (and x "x") "-")))) ; !suid - (defun tramp-decimal-to-octal (i) "Return a string consisting of the octal digits of I. Not actually used. Use `(format \"%o\" i)' instead?" @@ -6981,16 +6546,6 @@ Not actually used. Use `(format \"%o\" i)' instead?" (number-to-string (% i 8)))))) -;;(defun tramp-octal-to-decimal (ostr) -;; "Given a string of octal digits, return a decimal number." -;; (cond ((null ostr) 0) -;; ((string= "" ostr) 0) -;; (t (let ((last (aref ostr (1- (length ostr)))) -;; (rest (substring ostr 0 (1- (length ostr))))) -;; (unless (and (>= last ?0) -;; (<= last ?7)) -;; (error "Not an octal digit: %c" last)) -;; (+ (- last ?0) (* 8 (tramp-octal-to-decimal rest))))))) ;; Kudos to Gerd Moellmann for this suggestion. (defun tramp-octal-to-decimal (ostr) "Given a string of octal digits, return a decimal number." @@ -7012,295 +6567,427 @@ Not actually used. Use `(format \"%o\" i)' instead?" ;; ------------------------------------------------------------ -;; -- TRAMP file names -- +;; -- Tramp file names -- ;; ------------------------------------------------------------ ;; Conversion functions between external representation and ;; internal data structure. Convenience functions for internal ;; data structure. -(defun tramp-file-name-p (obj) - "Check whether TRAMP-FILE-NAME is a Tramp object." - (and (vectorp obj) (= 5 (length obj)))) - -(defun tramp-file-name-multi-method (obj) - "Return MULTI-METHOD component of TRAMP-FILE-NAME." - (and (tramp-file-name-p obj) (aref obj 0))) - -(defun tramp-file-name-method (obj) - "Return METHOD component of TRAMP-FILE-NAME." - (and (tramp-file-name-p obj) (aref obj 1))) - -(defun tramp-file-name-user (obj) - "Return USER component of TRAMP-FILE-NAME." - (and (tramp-file-name-p obj) (aref obj 2))) - -(defun tramp-file-name-host (obj) - "Return HOST component of TRAMP-FILE-NAME." - (and (tramp-file-name-p obj) (aref obj 3))) - -(defun tramp-file-name-localname (obj) - "Return LOCALNAME component of TRAMP-FILE-NAME." - (and (tramp-file-name-p obj) (aref obj 4))) +(defun tramp-file-name-p (vec) + "Check whether VEC is a Tramp object." + (and (vectorp vec) (= 4 (length vec)))) + +(defun tramp-file-name-method (vec) + "Return method component of VEC." + (and (tramp-file-name-p vec) (aref vec 0))) + +(defun tramp-file-name-user (vec) + "Return user component of VEC." + (and (tramp-file-name-p vec) (aref vec 1))) + +(defun tramp-file-name-host (vec) + "Return host component of VEC." + (and (tramp-file-name-p vec) (aref vec 2))) + +(defun tramp-file-name-localname (vec) + "Return localname component of VEC." + (and (tramp-file-name-p vec) (aref vec 3))) + +;; The host part of a Tramp file name vector can be of kind +;; "host#port". Sometimes, we must extract these parts. +(defun tramp-file-name-real-host (vec) + "Return the host name of VEC without port." + (let ((host (tramp-file-name-host vec))) + (if (and (stringp host) + (string-match tramp-host-with-port-regexp host)) + (match-string 1 host) + host))) + +(defun tramp-file-name-port (vec) + "Return the port number of VEC." + (let ((host (tramp-file-name-host vec))) + (and (stringp host) + (string-match tramp-host-with-port-regexp host) + (string-to-number (match-string 2 host))))) (defun tramp-tramp-file-p (name) - "Return t if NAME is a tramp file." + "Return t if NAME is a Tramp file." (save-match-data (string-match tramp-file-name-regexp name))) -;; HHH: Changed. Used to assign the return value of (user-login-name) -;; to the `user' part of the structure if a user name was not -;; provided, now it assigns nil. -(defun tramp-dissect-file-name (name) - "Return an `tramp-file-name' structure. -The structure consists of remote method, remote user, remote host and -localname (file name on remote host)." - (save-match-data - (let* ((match (string-match (nth 0 tramp-file-name-structure) name)) - (method - ; single-hop - (if match (match-string (nth 1 tramp-file-name-structure) name) - ; maybe multi-hop - (string-match - (format (nth 0 tramp-multi-file-name-structure) - (nth 0 tramp-multi-file-name-hop-structure)) name) - (match-string (nth 1 tramp-multi-file-name-structure) name)))) - (if (and method (member method tramp-multi-methods)) - ;; If it's a multi method, the file name structure contains - ;; arrays of method, user and host. - (tramp-dissect-multi-file-name name) - ;; Normal method. First, find out default method. - (unless match (error "Not a tramp file name: %s" name)) - (let ((user (match-string (nth 2 tramp-file-name-structure) name)) - (host (match-string (nth 3 tramp-file-name-structure) name)) - (localname (match-string (nth 4 tramp-file-name-structure) name))) - (vector nil method (or user nil) host localname)))))) - -(defun tramp-find-default-method (user host) - "Look up the right method to use in `tramp-default-method-alist'." - (let ((choices tramp-default-method-alist) - (method tramp-default-method) - item) - (while choices - (setq item (pop choices)) - (when (and (string-match (or (nth 0 item) "") (or host "")) - (string-match (or (nth 1 item) "") (or user ""))) - (setq method (nth 2 item)) - (setq choices nil))) - method)) - -(defun tramp-find-method (multi-method method user host) +(defun tramp-find-method (method user host) "Return the right method string to use. -This is MULTI-METHOD, if non-nil. Otherwise, it is METHOD, if non-nil. -If both MULTI-METHOD and METHOD are nil, do a lookup in +This is METHOD, if non-nil. Otherwise, do a lookup in `tramp-default-method-alist'." - (or multi-method method (tramp-find-default-method user host))) - -;; HHH: Not Changed. Multi method. Will probably not handle the case where -;; a user name is not provided in the "file name" very well. -(defun tramp-dissect-multi-file-name (name) - "Not implemented yet." - (let ((regexp (nth 0 tramp-multi-file-name-structure)) - (method-index (nth 1 tramp-multi-file-name-structure)) - (hops-index (nth 2 tramp-multi-file-name-structure)) - (localname-index (nth 3 tramp-multi-file-name-structure)) - (hop-regexp (nth 0 tramp-multi-file-name-hop-structure)) - (hop-method-index (nth 1 tramp-multi-file-name-hop-structure)) - (hop-user-index (nth 2 tramp-multi-file-name-hop-structure)) - (hop-host-index (nth 3 tramp-multi-file-name-hop-structure)) - method hops len hop-methods hop-users hop-hosts localname) - (unless (string-match (format regexp hop-regexp) name) - (error "Not a multi tramp file name: %s" name)) - (setq method (match-string method-index name)) - (setq hops (match-string hops-index name)) - (setq len (/ (length (match-data t)) 2)) - (when (< localname-index 0) (setq localname-index (+ localname-index len))) - (setq localname (match-string localname-index name)) - (let ((index 0)) - (while (string-match hop-regexp hops index) - (setq index (match-end 0)) - (setq hop-methods - (cons (match-string hop-method-index hops) hop-methods)) - (setq hop-users - (cons (match-string hop-user-index hops) hop-users)) - (setq hop-hosts - (cons (match-string hop-host-index hops) hop-hosts)))) - (vector - method - (apply 'vector (reverse hop-methods)) - (apply 'vector (reverse hop-users)) - (apply 'vector (reverse hop-hosts)) - localname))) - -(defun tramp-make-tramp-file-name (multi-method method user host localname) - "Constructs a tramp file name from METHOD, USER, HOST and LOCALNAME." - (if multi-method - (tramp-make-tramp-multi-file-name multi-method method user host localname) - (format-spec - (concat tramp-prefix-format - (when method (concat "%m" tramp-postfix-single-method-format)) - (when user (concat "%u" tramp-postfix-user-format)) - (when host (concat "%h" tramp-postfix-host-format)) - (when localname (concat "%p"))) - `((?m . ,method) (?u . ,user) (?h . ,host) (?p . ,localname))))) - -;; CCC: Henrik Holm: Not Changed. Multi Method. What should be done -;; with this when USER is nil? -(defun tramp-make-tramp-multi-file-name (multi-method method user host localname) - "Constructs a tramp file name for a multi-hop method." - (unless tramp-make-multi-tramp-file-format - (error "`tramp-make-multi-tramp-file-format' is nil")) - (let* ((prefix-format (nth 0 tramp-make-multi-tramp-file-format)) - (hop-format (nth 1 tramp-make-multi-tramp-file-format)) - (localname-format (nth 2 tramp-make-multi-tramp-file-format)) - (prefix (format-spec prefix-format `((?m . ,multi-method)))) - (hops "") - (localname (format-spec localname-format `((?p . ,localname)))) - (i 0) - (len (length method))) - (while (< i len) - (let ((m (aref method i)) (u (aref user i)) (h (aref host i))) - (setq hops (concat hops (format-spec hop-format - `((?m . ,m) (?u . ,u) (?h . ,h))))) - (setq i (1+ i)))) - (concat prefix hops localname))) - -(defun tramp-make-copy-program-file-name (user host localname) - "Create a file name suitable to be passed to `rcp' and workalikes." - (if user - (format "%s@%s:%s" user host localname) - (format "%s:%s" host localname))) + (or method + (let ((choices tramp-default-method-alist) + lmethod item) + (while choices + (setq item (pop choices)) + (when (and (string-match (or (nth 0 item) "") (or host "")) + (string-match (or (nth 1 item) "") (or user ""))) + (setq lmethod (nth 2 item)) + (setq choices nil))) + lmethod) + tramp-default-method)) + +(defun tramp-find-user (method user host) + "Return the right user string to use. +This is USER, if non-nil. Otherwise, do a lookup in +`tramp-default-user-alist'." + (or user + (let ((choices tramp-default-user-alist) + luser item) + (while choices + (setq item (pop choices)) + (when (and (string-match (or (nth 0 item) "") (or method "")) + (string-match (or (nth 1 item) "") (or host ""))) + (setq luser (nth 2 item)) + (setq choices nil))) + luser) + tramp-default-user)) + +(defun tramp-find-host (method user host) + "Return the right host string to use. +This is HOST, if non-nil. Otherwise, it is `tramp-default-host'." + (or (and (> (length host) 0) host) + tramp-default-host)) + +(defun tramp-dissect-file-name (name &optional nodefault) + "Return a `tramp-file-name' structure. +The structure consists of remote method, remote user, remote host +and localname (file name on remote host). If NODEFAULT is +non-nil, the file name parts are not expanded to their default +values." + (save-match-data + (let ((match (string-match (nth 0 tramp-file-name-structure) name))) + (unless match (error "Not a Tramp file name: %s" name)) + (let ((method (match-string (nth 1 tramp-file-name-structure) name)) + (user (match-string (nth 2 tramp-file-name-structure) name)) + (host (match-string (nth 3 tramp-file-name-structure) name)) + (localname (match-string (nth 4 tramp-file-name-structure) name))) + (if nodefault + (vector method user host localname) + (vector + (tramp-find-method method user host) + (tramp-find-user method user host) + (tramp-find-host method user host) + localname)))))) + +(defun tramp-equal-remote (file1 file2) + "Checks, whether the remote parts of FILE1 and FILE2 are identical. +The check depends on method, user and host name of the files. If +one of the components is missing, the default values are used. +The local file name parts of FILE1 and FILE2 are not taken into +account. -(defun tramp-method-out-of-band-p (multi-method method user host) - "Return t if this is an out-of-band method, nil otherwise." - (tramp-get-method-parameter - multi-method - (tramp-find-method multi-method method user host) - user host 'tramp-copy-program)) +Example: -;; Variables local to connection. + (tramp-equal-remote \"/ssh::/etc\" \"/<your host name>:/home\") + +would yield `t'. On the other hand, the following check results in nil: + + (tramp-equal-remote \"/sudo::/etc\" \"/su::/etc\")" + (and (stringp (file-remote-p file1)) + (stringp (file-remote-p file2)) + (string-equal (file-remote-p file1) (file-remote-p file2)))) + +(defun tramp-make-tramp-file-name (method user host localname) + "Constructs a Tramp file name from METHOD, USER, HOST and LOCALNAME." + (concat tramp-prefix-format + (when (not (zerop (length method))) + (concat method tramp-postfix-method-format)) + (when (not (zerop (length user))) + (concat user tramp-postfix-user-format)) + (when host host) tramp-postfix-host-format + (when localname localname))) + +(defun tramp-completion-make-tramp-file-name (method user host localname) + "Constructs a Tramp file name from METHOD, USER, HOST and LOCALNAME. +It must not be a complete Tramp file name, but as long as there are +necessary only. This function will be used in file name completion." + (concat tramp-prefix-format + (when (not (zerop (length method))) + (concat method tramp-postfix-method-format)) + (when (not (zerop (length user))) + (concat user tramp-postfix-user-format)) + (when (not (zerop (length host))) + (concat host tramp-postfix-host-format)) + (when localname localname))) + +(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) + "Return t if this is an out-of-band method, nil otherwise." + (tramp-get-method-parameter (tramp-file-name-method vec) 'tramp-copy-program)) -(defun tramp-get-ls-command (multi-method method user host) - (or - (save-excursion - (tramp-maybe-open-connection multi-method method user host) - (set-buffer (tramp-get-buffer multi-method method user host)) - tramp-ls-command) - (error "Couldn't find remote `ls' command"))) +(defun tramp-local-host-p (vec) + "Return t if this points to the local host, nil otherwise." + (let ((host (tramp-file-name-real-host vec))) + (and + (stringp host) + (string-match + (concat "^" (regexp-opt (list "localhost" (system-name)) t) "$") host)))) -(defun tramp-get-test-groks-nt (multi-method method user host) - (save-excursion - (tramp-maybe-open-connection multi-method method user host) - (set-buffer (tramp-get-buffer multi-method method user host)) - tramp-test-groks-nt)) +;; Variables local to connection. -(defun tramp-get-file-exists-command (multi-method method user host) - (or - (save-excursion - (tramp-maybe-open-connection multi-method method user host) - (set-buffer (tramp-get-buffer multi-method method user host)) - tramp-file-exists-command) - (error "Couldn't find remote `test -e' command"))) +(defun tramp-get-remote-path (vec) + (with-connection-property vec "remote-path" + (let* ((remote-path (tramp-compat-copy-tree tramp-remote-path)) + (elt (memq 'tramp-default-remote-path remote-path)) + (default-remote-path + (when elt + (condition-case nil + (symbol-name + (tramp-send-command-and-read vec "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"))))) + (when elt + ;; Replace place holder `tramp-default-remote-path'. + (setcdr elt + (append + (tramp-split-string default-remote-path ":") + (cdr elt))) + (setq remote-path (delq 'tramp-default-remote-path remote-path))) + + ;; Remove non-existing directories. + (delq + nil + (mapcar + (lambda (x) + (and + (with-connection-property vec 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 (zerop + (tramp-send-command-and-check + vec (format "%s -d %s" (tramp-get-test-command vec) dir))) + (zerop + (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" + (with-current-buffer (tramp-get-buffer vec) + (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 parameter. + (when (zerop (tramp-send-command-and-check + vec (format "%s -lnd /" result))) + (throw 'ls-found result)) + (setq dl (cdr dl)))))) + (tramp-error vec 'file-error "Couldn't find a proper `ls' command"))))) + +(defun tramp-get-test-command (vec) + (with-connection-property vec "test" + (with-current-buffer (tramp-get-buffer vec) + (tramp-message vec 5 "Finding a suitable `test' command") + (if (zerop (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 + (format "\n%s\r?\n" (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" + (with-current-buffer (tramp-get-buffer vec) + (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" + (with-current-buffer (tramp-get-buffer vec) + (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" + (with-current-buffer (tramp-get-buffer vec) + (tramp-message vec 5 "Finding a suitable `perl' command") + (or (tramp-find-executable vec "perl5" (tramp-get-remote-path vec)) + (tramp-find-executable vec "perl" (tramp-get-remote-path vec)))))) + +(defun tramp-get-remote-stat (vec) + (with-connection-property vec "stat" + (with-current-buffer (tramp-get-buffer vec) + (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. + (when result + (setq tmp + ;; We don't want to display an error message. + (with-temp-message (or (current-message) "") + (condition-case nil + (tramp-send-command-and-read + vec (format "%s -c '(\"%%N\")' /" result)) + (error nil)))) + (unless (and (listp tmp) (stringp (car tmp)) + (string-match "^./.$" (car tmp))) + (setq result nil))) + result)))) -(defun tramp-get-remote-perl (multi-method method user host) - (tramp-get-connection-property "perl" nil multi-method method user host)) +(defun tramp-get-remote-id (vec) + (with-connection-property vec "id" + (with-current-buffer (tramp-get-buffer vec) + (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 (zerop (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))) -(defun tramp-get-remote-ln (multi-method method user host) +;; Some predefined connection properties. +(defun tramp-get-remote-coding (vec prop) + ;; Local coding handles properties like remote coding. So we could + ;; call it without pain. + (let ((ret (tramp-get-local-coding vec prop))) + ;; The connection property might have been cached. So we must send + ;; the script - maybe. + (when (not (stringp ret)) + (let ((name (symbol-name ret))) + (while (string-match (regexp-quote "-") name) + (setq name (replace-match "_" nil t name))) + (tramp-maybe-send-script vec (symbol-value ret) name) + (setq ret name))) + ;; Return the value. + ret)) + +(defun tramp-get-local-coding (vec prop) (or - (tramp-get-connection-property "ln" nil multi-method method user host) - (error "Couldn't find remote `ln' command"))) - -(defun tramp-get-remote-uid (multi-method method user host) - (tramp-get-connection-property "uid" nil multi-method method user host)) - -(defun tramp-get-remote-gid (multi-method method user host) - (tramp-get-connection-property "gid" nil multi-method method user host)) - -;; Get a property of a TRAMP connection. -(defun tramp-get-connection-property - (property default multi-method method user host) - "Get the named property for the connection. -If the value is not set for the connection, return `default'" - (tramp-maybe-open-connection multi-method method user host) - (with-current-buffer (tramp-get-buffer multi-method method user host) - (let (error) - (condition-case nil - (symbol-value (intern (concat "tramp-connection-property-" property))) - (error default))))) - -;; Set a property of a TRAMP connection. -(defun tramp-set-connection-property - (property value multi-method method user host) - "Set the named property of a TRAMP connection." - (tramp-maybe-open-connection multi-method method user host) - (with-current-buffer (tramp-get-buffer multi-method method user host) - (set (make-local-variable - (intern (concat "tramp-connection-property-" property))) - value))) + (tramp-get-connection-property vec prop nil) + (progn + (tramp-find-inline-encoding vec) + (tramp-get-connection-property vec prop nil)))) -;; Some predefined connection properties. -(defun tramp-set-remote-encoding (multi-method method user host rem-enc) - (tramp-set-connection-property "remote-encoding" rem-enc - multi-method method user host)) -(defun tramp-get-remote-encoding (multi-method method user host) - (tramp-get-connection-property "remote-encoding" nil - multi-method method user host)) - -(defun tramp-set-remote-decoding (multi-method method user host rem-dec) - (tramp-set-connection-property "remote-decoding" rem-dec - multi-method method user host)) -(defun tramp-get-remote-decoding (multi-method method user host) - (tramp-get-connection-property "remote-decoding" nil - multi-method method user host)) - -(defun tramp-set-local-encoding (multi-method method user host loc-enc) - (tramp-set-connection-property "local-encoding" loc-enc - multi-method method user host)) -(defun tramp-get-local-encoding (multi-method method user host) - (tramp-get-connection-property "local-encoding" nil - multi-method method user host)) - -(defun tramp-set-local-decoding (multi-method method user host loc-dec) - (tramp-set-connection-property "local-decoding" loc-dec - multi-method method user host)) -(defun tramp-get-local-decoding (multi-method method user host) - (tramp-get-connection-property "local-decoding" nil - multi-method method user host)) - -(defun tramp-get-method-parameter (multi-method method user host param) +(defun tramp-get-method-parameter (method param) "Return the method parameter PARAM. -If the `tramp-methods' entry does not exist, use the variable PARAM -as default." - (unless (boundp param) - (error "Non-existing method parameter `%s'" param)) - (let ((entry (assoc param - (assoc (tramp-find-method multi-method method user host) - tramp-methods)))) - (if entry - (cadr entry) - (symbol-value param)))) - +If the `tramp-methods' entry does not exist, return NIL." + (let ((entry (assoc param (assoc method tramp-methods)))) + (when entry (cadr entry)))) ;; Auto saving to a special directory. (defun tramp-exists-file-name-handler (operation &rest args) - (let ((buffer-file-name "/") - (fnha file-name-handler-alist) - (check-file-name-operation operation) - (file-name-handler-alist - (list - (cons "/" - '(lambda (operation &rest args) - "Returns OPERATION if it is the one to be checked" - (if (equal check-file-name-operation operation) - operation - (let ((file-name-handler-alist fnha)) - (apply operation args)))))))) - (eq (apply operation args) operation))) + "Checks whether OPERATION runs a file name handler." + ;; The file name handler is determined on base of either an + ;; argument, `buffer-file-name', or `default-directory'. + (condition-case nil + (let* ((buffer-file-name "/") + (default-directory "/") + (fnha file-name-handler-alist) + (check-file-name-operation operation) + (file-name-handler-alist + (list + (cons "/" + '(lambda (operation &rest args) + "Returns OPERATION if it is the one to be checked." + (if (equal check-file-name-operation operation) + operation + (let ((file-name-handler-alist fnha)) + (apply operation args)))))))) + (equal (apply operation args) operation)) + (error nil))) (unless (tramp-exists-file-name-handler 'make-auto-save-file-name) (defadvice make-auto-save-file-name (around tramp-advice-make-auto-save-file-name () activate) - "Invoke `tramp-handle-make-auto-save-file-name' for tramp files." + "Invoke `tramp-handle-make-auto-save-file-name' for Tramp files." (if (and (buffer-file-name) (tramp-tramp-file-p (buffer-file-name))) (setq ad-return-value (tramp-handle-make-auto-save-file-name)) ad-do-it)) @@ -7315,6 +7002,7 @@ as default." (let ((bfn (buffer-file-name))) (when (and (stringp bfn) (tramp-tramp-file-p bfn) + (buffer-modified-p) (stringp buffer-auto-save-file-name) (not (equal bfn buffer-auto-save-file-name))) (unless (file-exists-p buffer-auto-save-file-name) @@ -7347,57 +7035,67 @@ ALIST is of the form ((FROM . TO) ...)." (setq alist (cdr alist)))) string)) -(defun tramp-insert-with-face (face string) - "Insert text with a specific face." - (let ((start (point))) - (insert string) - (add-text-properties start (point) (list 'face face)))) - ;; ------------------------------------------------------------ ;; -- Compatibility functions section -- ;; ------------------------------------------------------------ -(defun tramp-temporary-file-directory () - "Return name of directory for temporary files (compat function). -For Emacs, this is the variable `temporary-file-directory', for XEmacs -this is the function `temp-directory'." - (cond ((boundp 'temporary-file-directory) - (symbol-value 'temporary-file-directory)) - ((fboundp 'temp-directory) - (funcall (symbol-function 'temp-directory))) ;pacify byte-compiler - ((let ((d (getenv "TEMP"))) (and d (file-directory-p d))) - (file-name-as-directory (getenv "TEMP"))) - ((let ((d (getenv "TMP"))) (and d (file-directory-p d))) - (file-name-as-directory (getenv "TMP"))) - ((let ((d (getenv "TMPDIR"))) (and d (file-directory-p d))) - (file-name-as-directory (getenv "TMPDIR"))) - ((file-exists-p "c:/temp") (file-name-as-directory "c:/temp")) - (t (message (concat "Neither `temporary-file-directory' nor " - "`temp-directory' is defined -- using /tmp.")) - (file-name-as-directory "/tmp")))) - -(defun tramp-read-passwd (user host prompt) +(defun tramp-read-passwd (proc &optional prompt) "Read a password from user (compat function). Invokes `password-read' if available, `read-passwd' else." - (if (functionp 'password-read) - (let* ((key (concat (or user (user-login-name)) "@" host)) - (password (apply #'password-read (list prompt key)))) - (apply #'password-cache-add (list key password)) - password) - (read-passwd prompt))) - -(defun tramp-clear-passwd (&optional user host) - "Clear password cache for connection related to current-buffer." - (interactive) - (let ((filename (or buffer-file-name list-buffers-directory ""))) - (when (and (functionp 'password-cache-remove) - (or (and user host) (tramp-tramp-file-p filename))) - (let* ((v (when (tramp-tramp-file-p filename) - (tramp-dissect-file-name filename))) - (luser (or user (tramp-file-name-user v) (user-login-name))) - (lhost (or host (tramp-file-name-host v) (system-name))) - (key (concat luser "@" lhost))) - (apply #'password-cache-remove (list key)))))) + (let* ((key (tramp-make-tramp-file-name + tramp-current-method tramp-current-user + tramp-current-host "")) + (pw-prompt + (or prompt + (with-current-buffer (process-buffer proc) + (tramp-check-for-regexp proc tramp-password-prompt-regexp) + (format "%s for %s " (capitalize (match-string 1)) key))))) + (if (functionp 'password-read) + (let ((password (funcall (symbol-function 'password-read) + pw-prompt key))) + (funcall (symbol-function 'password-cache-add) key password) + password) + (read-passwd pw-prompt)))) + +(defun tramp-clear-passwd (vec) + "Clear password cache for connection related to VEC." + (when (functionp 'password-cache-remove) + (funcall + (symbol-function 'password-cache-remove) + (tramp-make-tramp-file-name + (tramp-file-name-method vec) + (tramp-file-name-user vec) + (tramp-file-name-host vec) + "")))) + +;; Snarfed code from time-date.el and parse-time.el + +(defconst tramp-half-a-year '(241 17024) +"Evaluated by \"(days-to-time 183)\".") + +(defconst tramp-parse-time-months + '(("jan" . 1) ("feb" . 2) ("mar" . 3) + ("apr" . 4) ("may" . 5) ("jun" . 6) + ("jul" . 7) ("aug" . 8) ("sep" . 9) + ("oct" . 10) ("nov" . 11) ("dec" . 12)) + "Alist mapping month names to integers.") + +(defun tramp-time-less-p (t1 t2) + "Say whether time value T1 is less than time value T2." + (unless t1 (setq t1 '(0 0))) + (unless t2 (setq t2 '(0 0))) + (or (< (car t1) (car t2)) + (and (= (car t1) (car t2)) + (< (nth 1 t1) (nth 1 t2))))) + +(defun tramp-time-subtract (t1 t2) + "Subtract two time values. +Return the difference in the format of a time value." + (unless t1 (setq t1 '(0 0))) + (unless t2 (setq t2 '(0 0))) + (let ((borrow (< (cadr t1) (cadr t2)))) + (list (- (car t1) (car t2) (if borrow 1 0)) + (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2))))) (defun tramp-time-diff (t1 t2) "Return the difference between the two times, in seconds. @@ -7416,11 +7114,7 @@ T1 and T2 are time values (as returned by `current-time' for example)." (if (< (length t1) 3) (append t1 '(0)) t1) (if (< (length t2) 3) (append t2 '(0)) t2))) (t - ;; snarfed from Emacs 21 time-date.el; combining - ;; time-to-seconds and subtract-time - (let ((time (let ((borrow (< (cadr t1) (cadr t2)))) - (list (- (car t1) (car t2) (if borrow 1 0)) - (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2)))))) + (let ((time (tramp-time-subtract t1 t2))) (+ (* (car time) 65536.0) (cadr time) (/ (or (nth 2 time) 0) 1000000.0)))))) @@ -7429,19 +7123,18 @@ T1 and T2 are time values (as returned by `current-time' for example)." "Return a coding system like CODING-SYSTEM but with given EOL-TYPE. EOL-TYPE can be one of `dos', `unix', or `mac'." (cond ((fboundp 'coding-system-change-eol-conversion) - (apply #'coding-system-change-eol-conversion - (list coding-system eol-type))) + (funcall (symbol-function 'coding-system-change-eol-conversion) + coding-system eol-type)) ((fboundp 'subsidiary-coding-system) - (apply - #'subsidiary-coding-system - (list coding-system - (cond ((eq eol-type 'dos) 'crlf) - ((eq eol-type 'unix) 'lf) - ((eq eol-type 'mac) 'cr) - (t - (error "Unknown EOL-TYPE `%s', must be %s" - eol-type - "`dos', `unix', or `mac'")))))) + (funcall (symbol-function 'subsidiary-coding-system) + coding-system + (cond ((eq eol-type 'dos) 'crlf) + ((eq eol-type 'unix) 'lf) + ((eq eol-type 'mac) 'cr) + (t + (error "Unknown EOL-TYPE `%s', must be %s" + eol-type + "`dos', `unix', or `mac'"))))) (t (error "Can't change EOL conversion -- is MULE missing?")))) (defun tramp-split-string (string pattern) @@ -7459,11 +7152,9 @@ it does the right thing." "Specify if query is needed for process when Emacs is exited. If the second argument flag is non-nil, Emacs will query the user before exiting if process is running." - (funcall (if (fboundp 'set-process-query-on-exit-flag) - (symbol-function 'set-process-query-on-exit-flag) - (symbol-function 'process-kill-without-query)) - process flag)) + (funcall (symbol-function 'set-process-query-on-exit-flag) process flag) + (funcall (symbol-function 'process-kill-without-query) process flag))) ;; ------------------------------------------------------------ @@ -7510,29 +7201,6 @@ Only works for Bourne-like shells." t t result))) result)))) -;; ;; EFS hooks itself into the file name handling stuff in more places -;; ;; than just `file-name-handler-alist'. The following tells EFS to stay -;; ;; away from tramp.el file names. -;; ;; -;; ;; This is needed because EFS installs (efs-dired-before-readin) into -;; ;; 'dired-before-readin-hook'. This prevents EFS from opening an FTP -;; ;; connection to help it's dired process. Not that I have any real -;; ;; idea *why* this is helpful to dired. -;; ;; -;; ;; Anyway, this advice fixes the problem (with a sledgehammer :) -;; ;; -;; ;; Daniel Pittman <daniel@danann.net> -;; ;; -;; ;; CCC: when the other defadvice calls have disappeared, make sure -;; ;; not to call defadvice unless it's necessary. How do we find out whether -;; ;; it is necessary? (featurep 'efs) is surely the wrong way -- -;; ;; EFS might nicht be loaded yet. -;; (defadvice efs-ftp-path (around dont-match-tramp-localname activate protect) -;; "Cause efs-ftp-path to fail when the path is a TRAMP localname." -;; (if (tramp-tramp-file-p (ad-get-arg 0)) -;; nil -;; ad-do-it)) - ;; We currently (sometimes) use "[" and "]" in the filename format. ;; This means that Emacs wants to expand wildcards if ;; `find-file-wildcards' is non-nil, and then barfs because no @@ -7543,245 +7211,22 @@ Only works for Bourne-like shells." ;; CCC: This check is now also really awful; we should search all ;; of the filename format, not just the prefix. (when (string-match "\\[" tramp-prefix-format) - (defadvice file-expand-wildcards (around tramp-fix activate) + (defadvice file-expand-wildcards + (around tramp-advice-file-expand-wildcards activate) (let ((name (ad-get-arg 0))) (if (tramp-tramp-file-p name) ;; If it's a Tramp file, dissect it and look if wildcards ;; need to be expanded at all. - (let ((v (tramp-dissect-file-name name))) - (if (string-match "[[*?]" (tramp-file-name-localname v)) - (let ((res ad-do-it)) - (setq ad-return-value (or res (list name)))) - (setq ad-return-value (list name)))) + (if (string-match + "[[*?]" + (tramp-file-name-localname (tramp-dissect-file-name name))) + (setq ad-return-value (or ad-do-it (list name))) + (setq ad-return-value (list name))) ;; If it is not a Tramp file, just run the original function. - (let ((res ad-do-it)) - (setq ad-return-value (or res (list name))))))) + (setq ad-return-value (or ad-do-it (list name)))))) (add-hook 'tramp-unload-hook '(lambda () (ad-unadvise 'file-expand-wildcards)))) -;; Tramp version is useful in a number of situations. - -(defun tramp-version (arg) - "Print version number of tramp.el in minibuffer or current buffer." - (interactive "P") - (if arg (insert tramp-version) (message tramp-version))) - -;; Make the `reporter` functionality available for making bug reports about -;; the package. A most useful piece of code. - -(unless (fboundp 'reporter-submit-bug-report) - (autoload 'reporter-submit-bug-report "reporter")) - -(defun tramp-bug () - "Submit a bug report to the TRAMP developers." - (interactive) - (require 'reporter) - (catch 'dont-send - (let ((reporter-prompt-for-summary-p t)) - (reporter-submit-bug-report - tramp-bug-report-address ; to-address - (format "tramp (%s)" tramp-version) ; package name and version - (delq nil - `(;; Current state - tramp-ls-command - tramp-test-groks-nt - tramp-file-exists-command - tramp-current-multi-method - tramp-current-method - tramp-current-user - tramp-current-host - - ;; System defaults - tramp-auto-save-directory ; vars to dump - tramp-default-method - tramp-rsh-end-of-line - tramp-default-password-end-of-line - tramp-remote-path - tramp-login-prompt-regexp - ;; Mask non-7bit characters - (tramp-password-prompt-regexp . tramp-reporter-dump-variable) - tramp-wrong-passwd-regexp - tramp-yesno-prompt-regexp - tramp-yn-prompt-regexp - tramp-terminal-prompt-regexp - tramp-temp-name-prefix - tramp-file-name-structure - tramp-file-name-regexp - tramp-multi-file-name-structure - tramp-multi-file-name-hop-structure - tramp-multi-methods - tramp-multi-connection-function-alist - tramp-methods - tramp-end-of-output - tramp-coding-commands - tramp-actions-before-shell - tramp-actions-copy-out-of-band - tramp-multi-actions - tramp-terminal-type - ;; Mask non-7bit characters - (tramp-shell-prompt-pattern . tramp-reporter-dump-variable) - tramp-chunksize - ,(when (boundp 'tramp-backup-directory-alist) - 'tramp-backup-directory-alist) - ,(when (boundp 'tramp-bkup-backup-directory-info) - 'tramp-bkup-backup-directory-info) - - ;; Non-tramp variables of interest - ;; Mask non-7bit characters - (shell-prompt-pattern . tramp-reporter-dump-variable) - backup-by-copying - backup-by-copying-when-linked - backup-by-copying-when-mismatch - ,(when (boundp 'backup-by-copying-when-privileged-mismatch) - 'backup-by-copying-when-privileged-mismatch) - ,(when (boundp 'password-cache) - 'password-cache) - ,(when (boundp 'password-cache-expiry) - 'password-cache-expiry) - ,(when (boundp 'backup-directory-alist) - 'backup-directory-alist) - ,(when (boundp 'bkup-backup-directory-info) - 'bkup-backup-directory-info) - file-name-handler-alist)) - - 'tramp-load-report-modules ; pre-hook - 'tramp-append-tramp-buffers ; post-hook - "\ -Enter your bug report in this message, including as much detail as you -possibly can about the problem, what you did to cause it and what the -local and remote machines are. - -If you can give a simple set of instructions to make this bug happen -reliably, please include those. Thank you for helping kill bugs in -TRAMP. - -Another useful thing to do is to put (setq tramp-debug-buffer t) in -the ~/.emacs file and to repeat the bug. Then, include the contents -of the *tramp/foo* buffer and the *debug tramp/foo* buffer in your bug -report. - ---bug report follows this line-- -")))) - -(defun tramp-reporter-dump-variable (varsym mailbuf) - "Pretty-print the value of the variable in symbol VARSYM. -Used for non-7bit chars in strings." - (let* ((reporter-eval-buffer (symbol-value 'reporter-eval-buffer)) - (val (with-current-buffer reporter-eval-buffer - (symbol-value varsym)))) - - ;; There are characters to be masked. - (when (and (boundp 'mm-7bit-chars) - (string-match - (concat "[^" (symbol-value 'mm-7bit-chars) "]") val)) - (with-current-buffer reporter-eval-buffer - (set varsym (concat "(base64-decode-string \"" - (base64-encode-string val) - "\")")))) - - ;; Dump variable. - (funcall (symbol-function 'reporter-dump-variable) varsym mailbuf) - - ;; Remove string quotation. - (forward-line -1) - (when (looking-at - (concat "\\(^.*\\)" "\"" ;; \1 " - "\\((base64-decode-string \\)" "\\\\" ;; \2 \ - "\\(\".*\\)" "\\\\" ;; \3 \ - "\\(\")\\)" "\"$")) ;; \4 " - (replace-match "\\1\\2\\3\\4") - (beginning-of-line) - (insert " ;; variable encoded due to non-printable characters\n")) - (forward-line 1) - - ;; Reset VARSYM to old value. - (with-current-buffer reporter-eval-buffer - (set varsym val)))) - -(defun tramp-load-report-modules () - "Load needed modules for reporting." - - ;; We load message.el and mml.el from Gnus. - (if (featurep 'xemacs) - (progn - (load "message" 'noerror) - (load "mml" 'noerror)) - (require 'message nil 'noerror) - (require 'mml nil 'noerror)) - (when (functionp 'message-mode) - (funcall (symbol-function 'message-mode))) - (when (functionp 'mml-mode) - (funcall (symbol-function 'mml-mode) t))) - -(defun tramp-append-tramp-buffers () - "Append Tramp buffers into the bug report." - - (when (and - (eq major-mode 'message-mode) - (boundp 'mml-mode) - (symbol-value 'mml-mode)) - - (let* ((tramp-buf-regexp "\\*\\(debug \\)?tramp/") - (buffer-list - (delq nil - (mapcar '(lambda (b) - (when (string-match tramp-buf-regexp (buffer-name b)) b)) - (buffer-list)))) - (curbuf (current-buffer))) - - ;; There is at least one Tramp buffer. - (when buffer-list - (switch-to-buffer (list-buffers-noselect nil)) - (delete-other-windows) - (setq buffer-read-only nil) - (goto-char (point-min)) - (while (not (eobp)) - (if (re-search-forward tramp-buf-regexp (tramp-point-at-eol) t) - (forward-line 1) - (forward-line 0) - (let ((start (point))) - (forward-line 1) - (kill-region start (point))))) - (insert " -The buffer(s) above will be appended to this message. If you don't want -to append a buffer because it contains sensitive data, or because the buffer -is too large, you should delete the respective buffer. The buffer(s) will -contain user and host names. Passwords will never be included there.") - - (when (and tramp-debug-buffer (> tramp-verbose 9)) - (insert "\n\n") - (let ((start (point))) - (insert "\ -Please note that you have set `tramp-verbose' to a value greater than 9. -Therefore, the contents of files might be included in the debug buffer(s).") - (add-text-properties start (point) (list 'face 'italic)))) - - (set-buffer-modified-p nil) - (setq buffer-read-only t) - (goto-char (point-min)) - - (if (y-or-n-p "Do you want to append the buffer(s)? ") - ;; OK, let's send. First we delete the buffer list. - (progn - (kill-buffer nil) - (switch-to-buffer curbuf) - (goto-char (point-max)) - (insert "\n\n") - (dolist (buffer buffer-list) - (funcall (symbol-function 'mml-insert-empty-tag) - 'part 'type "text/plain" 'encoding "base64" - 'disposition "attachment" 'buffer (buffer-name buffer) - 'description (buffer-name buffer))) - (set-buffer-modified-p nil)) - - ;; Don't send. Delete the message buffer. - (set-buffer curbuf) - (set-buffer-modified-p nil) - (kill-buffer nil) - (throw 'dont-send nil)))))) - -(defalias 'tramp-submit-bug 'tramp-bug) - ;; Checklist for `tramp-unload-hook' ;; - Unload all `tramp-*' packages ;; - Reset `file-name-handler-alist' @@ -7797,25 +7242,13 @@ Therefore, the contents of files might be included in the debug buffer(s).") ;; ange-ftp settings must be enabled. (when (functionp 'tramp-ftp-enable-ange-ftp) (funcall (symbol-function 'tramp-ftp-enable-ange-ftp))) - ;; `tramp-util' unloads also `tramp'. - (condition-case nil ;; maybe its not loaded yet. - (unload-feature (if (featurep 'tramp-util) 'tramp-util 'tramp) 'force) + ;; Maybe its not loaded yet. + (condition-case nil + (unload-feature 'tramp 'force) (error nil))) (provide 'tramp) -;; Make sure that we get integration with the VC package. -;; When it is loaded, we need to pull in the integration module. -;; This must come after (provide 'tramp) because tramp-vc.el -;; requires tramp. -(eval-after-load "vc" - '(progn - (require 'tramp-vc) - (add-hook 'tramp-unload-hook - '(lambda () - (when (featurep 'tramp-vc) - (unload-feature 'tramp-vc 'force)))))) - ;;; TODO: ;; * Allow putting passwords in the filename. @@ -7826,24 +7259,13 @@ Therefore, the contents of files might be included in the debug buffer(s).") ;; Another approach is to read a netrc file like ~/.authinfo ;; from Gnus. ;; * Handle nonlocal exits such as C-g. +;; * But it would probably be better to use with-local-quit at the +;; place where it's actually needed: around any potentially +;; indefinitely blocking piece of code. In this case it would be +;; within Tramp around one of its calls to accept-process-output (or +;; around one of the loops that calls accept-process-output) +;; (Stefan Monnier). ;; * Autodetect if remote `ls' groks the "--dired" switch. -;; * Add fallback for inline encodings. This should be used -;; if the remote end doesn't support mimencode or a similar program. -;; For reading files from the remote host, we can just parse the output -;; of `od -b'. For writing files to the remote host, we construct -;; a shell program which contains only "safe" ascii characters -;; and which writes the right bytes to the file. We can use printf(1) -;; or "echo -e" or the printf function in awk and use octal escapes -;; for the "dangerous" characters. The null byte might be a problem. -;; On some systems, the octal escape doesn't work. So we try the following -;; two commands to write a null byte: -;; dd if=/dev/zero bs=1 count=1 -;; echo | tr '\n' '\000' -;; * Separate local `tramp-coding-commands' from remote ones. Connect -;; the two via a format which can be `uu' or `b64'. Then we can search -;; for the right local commands and the right remote commands separately. -;; * Cooperate with PCL-CVS. It uses start-process, which doesn't -;; work for remote files. ;; * Rewrite `tramp-shell-quote-argument' to abstain from using ;; `shell-quote-argument'. ;; * Completion gets confused when you leave out the method name. @@ -7861,68 +7283,63 @@ Therefore, the contents of files might be included in the debug buffer(s).") ;; * 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. -;; * Find out about the new auto-save mechanism in Emacs 21 and -;; do the right thing. ;; * `vc-directory' does not work. It never displays any files, even ;; if it does show files when run locally. ;; * Allow correction of passwords, if the remote end allows this. ;; (Mark Hershberger) ;; * How to deal with MULE in `insert-file-contents' and `write-region'? -;; * Do asynchronous `shell-command's. ;; * Grok `append' parameter for `write-region'. ;; * Test remote ksh or bash for tilde expansion in `tramp-find-shell'? ;; * abbreviate-file-name -;; * grok ~ in tramp-remote-path (Henrik Holm <henrikh@tele.ntnu.no>) -;; * Also allow to omit user names when doing multi-hop. Not sure yet -;; what the user names should default to, though. ;; * better error checking. At least whenever we see something ;; strange when doing zerop, we should kill the process and start ;; again. (Greg Stark) -;; * Add caching for filename completion. (Greg Stark) -;; Of course, this has issues with usability (stale cache bites) -;; -- <daniel@danann.net> ;; * Provide a local cache of old versions of remote files for the rsync ;; transfer method to use. (Greg Stark) ;; * Remove unneeded parameters from methods. ;; * Invoke rsync once for copying a whole directory hierarchy. -;; (Francesco Potort,Al(B) -;; * Should we set PATH ourselves or should we rely on the remote end -;; to do it? -;; * Make it work for XEmacs 20, which is missing `with-timeout'. +;; (Francesco Potortì) ;; * Make it work for different encodings, and for different file name ;; encodings, too. (Daniel Pittman) -;; * Change applicable functions to pass a struct tramp-file-name rather -;; than the individual items MULTI-METHOD, METHOD, USER, HOST, LOCALNAME. -;; * Implement asynchronous shell commands. -;; * Clean up unused *tramp/foo* buffers after a while. (Pete Forman) ;; * Progress reports while copying files. (Michael Kifer) -;; * `Smart' connection method that uses inline for small and out of -;; band for large files. (Michael Kifer) ;; * Don't search for perl5 and perl. Instead, only search for perl and ;; then look if it's the right version (with `perl -v'). ;; * When editing a remote CVS controlled file as a different user, VC ;; gets confused about the file locking status. Try to find out why ;; the workaround doesn't work. -;; * Change `copy-file' to grok the case where the filename handler -;; for the source and the target file are different. Right now, -;; it looks at the source file and then calls that handler, if -;; there is one. But since ange-ftp, for instance, does not know -;; about Tramp, it does not do the right thing if the target file -;; name is a Tramp name. ;; * Username and hostname completion. -;; ** If `partial-completion-mode' isn't loaded, "/foo:bla" tries to -;; connect to host "blabla" already if that host is unique. No idea -;; how to suppress. Maybe not an essential problem. ;; ** Try to avoid usage of `last-input-event' in `tramp-completion-mode-p'. -;; ** Extend `tramp-get-completion-su' for NIS and shadow passwords. ;; ** Unify `tramp-parse-{rhosts,shosts,sconfig,hosts,passwd,netrc}'. ;; Code is nearly identical. -;; ** Decide whiche files to take for searching user/host names depending on -;; operating system (windows-nt) in `tramp-completion-function-alist'. -;; ** Enhance variables for debug. -;; ** Implement "/multi:" completion. -;; ** Add a learning mode for completion. Make results persistent. ;; * Allow out-of-band methods as _last_ multi-hop. +;; * 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) +;; * Could Tramp reasonably look for a prompt after ^M rather than +;; only after ^J ? (Stefan Monnier) +;; * Reconnect directly to a compliant shell without first going +;; through the user's default shell. (Pete Forman) +;; * Make `tramp-default-user' obsolete. +;; * Tramp shall reconnect automatically to its ssh connection when it +;; detects that the process "has died". (David Reitter) ;; Functions for file-name-handler-alist: ;; diff-latest-backup-file -- in diff.el diff --git a/lisp/net/trampver.el b/lisp/net/trampver.el index be994b35102..689987faff4 100644 --- a/lisp/net/trampver.el +++ b/lisp/net/trampver.el @@ -1,10 +1,10 @@ -;;; -*- mode: Emacs-Lisp; coding: iso-2022-7bit; -*- ;;; trampver.el --- Transparent Remote Access, Multiple Protocol +;;; -*- mode: Emacs-Lisp; coding: utf-8; -*- ;;; lisp/trampver.el. Generated from trampver.el.in by configure. ;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. -;; Author: Kai Gro,A_(Bjohann <kai.grossjohann@gmx.net> +;; Author: Kai Großjohann <kai.grossjohann@gmx.net> ;; Keywords: comm, processes ;; This file is part of GNU Emacs. @@ -20,22 +20,26 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -;; Boston, MA 02110-1301, USA. +;; along with GNU Emacs; see the file COPYING. If not, see +;; <http://www.gnu.org/licenses/>. ;;; Code: ;; In the Tramp CVS repository, the version numer and the bug report address ;; are auto-frobbed from configure.ac, so you should edit that file and run -;; "autoconf && ./configure" to change them. +;; "autoconf && ./configure" to change them. (X)Emacs version check is defined +;; in macro AC_EMACS_INFO of aclocal.m4; should be changed only there. -(defconst tramp-version "2.0.57-pre" +(defconst tramp-version "2.1.12" "This version of Tramp.") (defconst tramp-bug-report-address "tramp-devel@gnu.org" "Email address to send bug reports to.") +;; Check for (X)Emacs version. +(let ((x (if (or (< emacs-major-version 21) (and (featurep 'xemacs) (< emacs-minor-version 4))) (format "Tramp 2.1.12 is not fit for %s" (when (string-match "^.*$" (emacs-version)) (match-string 0 (emacs-version)))) "ok"))) + (unless (string-match "\\`ok\\'" x) (error "%s" x))) + (provide 'trampver) ;;; arch-tag: 443576ca-f8f1-4bb1-addc-5c70861e93b1 diff --git a/lisp/net/webjump.el b/lisp/net/webjump.el index 3c5e03acadd..f5f0c32520d 100644 --- a/lisp/net/webjump.el +++ b/lisp/net/webjump.el @@ -229,7 +229,6 @@ ("Yahoo" . [simple-query "www.yahoo.com" "search.yahoo.com/search?p=" ""]) ("Yahoo: Reference" . "www.yahoo.com/Reference/") - ("Wikipedia" . [simple-query "wikipedia.org" "wikipedia.org/wiki/" ""]) @@ -452,14 +451,12 @@ Please submit bug reports and other feedback to the author, Neil W. Van Dyke (defun webjump-url-encode (str) (mapconcat '(lambda (c) - (cond ((= c 32) "+") - ((or (and (>= c ?a) (<= c ?z)) - (and (>= c ?A) (<= c ?Z)) - (and (>= c ?0) (<= c ?9))) - (char-to-string c)) - (t (upcase (format "%%%02x" c))))) - str - "")) + (let ((s (char-to-string c))) + (cond ((string= s " ") "+") + ((string-match "[a-zA-Z_.-/]" s) s) + (t (upcase (format "%%%02x" c)))))) + (encode-coding-string str 'utf-8) + "")) (defun webjump-url-fix (url) (if (webjump-null-or-blank-string-p url) |