1 ;;; -*- coding: iso-8859-1; -*-
2 ;;; tramp-fish.el --- Tramp access functions for FISH protocol
4 ;; Copyright (C) 2006, 2007 Free Software Foundation, Inc.
6 ;; Author: Michael Albinus <michael.albinus@gmx.de>
7 ;; Keywords: comm, processes
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3 of the License, or
14 ;; (at your option) any later version.
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, see
23 ;; <http://www.gnu.org/licenses/>.
27 ;; Access functions for FIles transferred over SHell protocol from Tramp.
29 ;; FISH is a protocol developped for the GNU Midnight Commander
30 ;; <https://savannah.gnu.org/projects/mc>. A client connects to a
31 ;; remote host via ssh (or rsh, shall be configurable), and starts
32 ;; there a fish server via the command "start_fish_server". All
33 ;; commands from the client have the form "#FISH_COMMAND\n" (always
34 ;; one line), followed by equivalent shell commands in case there is
35 ;; no fish server running.
37 ;; The fish server (or the equivalent shell commands) must return the
38 ;; response, which is finished by a line "### xxx <optional text>\n".
39 ;; "xxx" stands for 3 digits, representing a return code. Return
40 ;; codes "# 000" and "# 001" are reserved for fallback implementation
41 ;; with native shell commands; they are not used inside the server. See
42 ;; <http://cvs.savannah.gnu.org/viewcvs/mc/vfs/README.fish?root=mc&view=markup>
43 ;; for details of original specification.
45 ;; The GNU Midnight Commander implements the original fish protocol
46 ;; version 0.0.2. The KDE Konqueror has its own implementation, which
48 ;; <http://websvn.kde.org/branches/KDE/3.5/kdebase/kioslave/fish>. It
49 ;; implements an extended protocol version 0.0.3. Additionally, it
50 ;; provides a fish server implementation in Perl (which is the only
51 ;; implementation I've heard of). The following command reference is
52 ;; based on that implementation.
54 ;; All commands return either "### 2xx\n" (OK) or "### 5xx <optional text>\n"
55 ;; (NOK). Return codes are mentioned only if they are different from this.
56 ;; Spaces in any parameter must be escaped by "\ ".
58 ;; Command/Return Code Comment
60 ;; #FISH initial connection, not used
62 ;; ### 100 transfer fish server missing server, or wrong checksum
65 ;; #VER a.b.c <commands requested>
66 ;; VER x.y.z <commands offered> .fishsrv.pl response is not uptodate
73 ;; #COPY /path/a /path/b version 0.0.3 only
75 ;; #RENAME /path/a /path/b
77 ;; #SYMLINK /path/a /path/b
79 ;; #LINK /path/a /path/b
87 ;; #CHOWN user /file/name
89 ;; #CHGRP group /file/name
93 ;; #READ <offset> <size> /path/and/filename
94 ;; ### 291 successful exit when reading
96 ;; ### 292 successful exit when reading
99 ;; #WRITE <offset> <size> /path/and/filename
101 ;; #APPEND <size> /path/and/filename version 0.0.3 only
104 ;; <number of entries> version 0.0.3 only
105 ;; ### 100 version 0.0.3 only
106 ;; P<unix permissions> <owner>.<group>
108 ;; d<3-letters month name> <day> <year or HH:MM>
109 ;; D<year> <month> <day> <hour> <minute> <second>[.1234]
110 ;; E<major-of-device>,<minor>
112 ;; L<filename symlink points to>
113 ;; M<mimetype> version 0.0.3 only
114 ;; <blank line to separate items>
116 ;; #STAT /file version 0.0.3 only
117 ;; like #LIST except for directories
118 ;; <number of entries>
120 ;; P<unix permissions> <owner>.<group>
122 ;; d<3-letters month name> <day> <year or HH:MM>
123 ;; D<year> <month> <day> <hour> <minute> <second>[.1234]
124 ;; E<major-of-device>,<minor>
126 ;; L<filename symlink points to>
127 ;; <blank line to separate items>
132 ;; <binary data> exactly filesize bytes
133 ;; ### 200 with no preceding newline
135 ;; #STOR <size> /file/name
137 ;; <data> exactly size bytes
138 ;; ### 001 partial success
140 ;; #EXEC <command> <tmpfile> version 0.0.3 only
141 ;; <tmpfile> must not exists. It contains the output of <command>.
142 ;; It can be retrieved afterwards. Last line is
143 ;; ###RESULT: <returncode>
145 ;; This implementation is meant as proof of the concept, whether there
146 ;; is a better performance compared with the native ssh method. It
147 ;; looks like the file information retrieval is slower, especially the
148 ;; #LIST command. On the other hand, the file contents transmission
149 ;; seems to perform better than other inline methods, because there is
150 ;; no need for data encoding/decoding, and it supports the APPEND
151 ;; parameter of `write-region'. Transfer of binary data fails due to
152 ;; Emacs' process input/output handling.
158 (require 'tramp-cache
)
160 ;; Pacify byte-compiler
165 ;; Avoid byte-compiler warnings if the byte-compiler supports this.
166 ;; Currently, XEmacs supports this.
168 (when (featurep 'xemacs
)
169 (byte-compiler-options (warnings (- unused-vars
)))))
171 ;; `directory-sep-char' is an obsolete variable in Emacs. But it is
172 ;; used in XEmacs, so we set it here and there. The following is needed
173 ;; to pacify Emacs byte-compiler.
175 (unless (boundp 'byte-compile-not-obsolete-var
)
176 (defvar byte-compile-not-obsolete-var nil
))
177 (setq byte-compile-not-obsolete-var
'directory-sep-char
))
179 ;; Define FISH method ...
180 (defcustom tramp-fish-method
"fish"
181 "*Method to connect via FISH protocol."
185 ;; ... and add it to the method list.
186 (add-to-list 'tramp-methods
(cons tramp-fish-method nil
))
188 ;; Add a default for `tramp-default-user-alist'. Default is the local user.
189 (add-to-list 'tramp-default-user-alist
190 `(,tramp-fish-method nil
,(user-login-name)))
192 ;; Add completion function for FISH method.
193 (tramp-set-completion-function
194 tramp-fish-method tramp-completion-function-alist-ssh
)
196 (defconst tramp-fish-continue-prompt-regexp
"^### 100.*\n"
197 "FISH return code OK.")
199 ;; It cannot be a defconst, occasionally we bind it locally.
200 (defvar tramp-fish-ok-prompt-regexp
"^### 200\n"
201 "FISH return code OK.")
203 (defconst tramp-fish-error-prompt-regexp
"^### \\(4\\|5\\)[0-9]+.*\n"
204 "Regexp for possible error strings of FISH servers.
205 Used instead of analyzing error codes of commands.")
207 (defcustom tramp-fish-start-fish-server-command
208 (concat "stty intr \"\" quit \"\" erase \"\" kill \"\" eof \"\" eol \"\" eol2 \"\" swtch \"\" start \"\" stop \"\" susp \"\" rprnt \"\" werase \"\" lnext \"\" flush \"\"; "
210 "`grep 'ARGV\\[0\\]' .fishsrv.pl | "
211 "sed -e 's/^[^\"]*\"//' -e 's/\"[^\"]*$//'`; "
213 "*Command to connect via FISH protocol."
217 ;; New handlers should be added here.
218 (defconst tramp-fish-file-name-handler-alist
220 ;; `access-file' performed by default handler
221 (add-name-to-file . tramp-fish-handle-add-name-to-file
)
222 ;; `byte-compiler-base-file-name' performed by default handler
223 (copy-file . tramp-fish-handle-copy-file
)
224 (delete-directory . tramp-fish-handle-delete-directory
)
225 (delete-file . tramp-fish-handle-delete-file
)
226 ;; `diff-latest-backup-file' performed by default handler
227 (directory-file-name . tramp-handle-directory-file-name
)
228 (directory-files . tramp-handle-directory-files
)
229 (directory-files-and-attributes . tramp-fish-handle-directory-files-and-attributes
)
230 ;; `dired-call-process' performed by default handler
231 ;; `dired-compress-file' performed by default handler
232 ;; `dired-uncache' performed by default handler
233 (expand-file-name . tramp-fish-handle-expand-file-name
)
234 ;; `file-accessible-directory-p' performed by default handler
235 (file-attributes . tramp-fish-handle-file-attributes
)
236 (file-directory-p . tramp-fish-handle-file-directory-p
)
237 (file-executable-p . tramp-fish-handle-file-executable-p
)
238 (file-exists-p . tramp-fish-handle-file-exists-p
)
239 (file-local-copy . tramp-fish-handle-file-local-copy
)
240 (file-remote-p . tramp-handle-file-remote-p
)
241 (file-modes . tramp-handle-file-modes
)
242 (file-name-all-completions . tramp-fish-handle-file-name-all-completions
)
243 ;; `file-name-as-directory' performed by default handler
244 (file-name-completion . tramp-handle-file-name-completion
)
245 (file-name-directory . tramp-handle-file-name-directory
)
246 (file-name-nondirectory . tramp-handle-file-name-nondirectory
)
247 ;; `file-name-sans-versions' performed by default handler
248 (file-newer-than-file-p . tramp-fish-handle-file-newer-than-file-p
)
249 (file-ownership-preserved-p . ignore
)
250 (file-readable-p . tramp-fish-handle-file-readable-p
)
251 (file-regular-p . tramp-handle-file-regular-p
)
252 (file-symlink-p . tramp-handle-file-symlink-p
)
253 ;; `file-truename' performed by default handler
254 (file-writable-p . tramp-fish-handle-file-writable-p
)
255 (find-backup-file-name . tramp-handle-find-backup-file-name
)
256 ;; `find-file-noselect' performed by default handler
257 ;; `get-file-buffer' performed by default handler
258 (insert-directory . tramp-fish-handle-insert-directory
)
259 (insert-file-contents . tramp-fish-handle-insert-file-contents
)
260 (load . tramp-handle-load
)
261 (make-directory . tramp-fish-handle-make-directory
)
262 (make-directory-internal . tramp-fish-handle-make-directory-internal
)
263 (make-symbolic-link . tramp-fish-handle-make-symbolic-link
)
264 (rename-file . tramp-fish-handle-rename-file
)
265 (set-file-modes . tramp-fish-handle-set-file-modes
)
266 (set-file-times . tramp-fish-handle-set-file-times
)
267 (set-visited-file-modtime . ignore
)
268 (shell-command . tramp-handle-shell-command
)
269 (substitute-in-file-name . tramp-handle-substitute-in-file-name
)
270 (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory
)
271 (vc-registered . ignore
)
272 (verify-visited-file-modtime . ignore
)
273 (write-region . tramp-fish-handle-write-region
)
274 (executable-find . tramp-fish-handle-executable-find
)
275 (start-file-process . ignore
)
276 (process-file . tramp-fish-handle-process-file
)
278 "Alist of handler functions for Tramp FISH method.
279 Operations not mentioned here will be handled by the default Emacs primitives.")
281 (defun tramp-fish-file-name-p (filename)
282 "Check if it's a filename for FISH protocol."
283 (let ((v (tramp-dissect-file-name filename
)))
284 (string= (tramp-file-name-method v
) tramp-fish-method
)))
286 (defun tramp-fish-file-name-handler (operation &rest args
)
287 "Invoke the FISH related OPERATION.
288 First arg specifies the OPERATION, second arg is a list of arguments to
289 pass to the OPERATION."
290 (let ((fn (assoc operation tramp-fish-file-name-handler-alist
)))
292 (save-match-data (apply (cdr fn
) args
))
293 (tramp-run-real-handler operation args
))))
295 (add-to-list 'tramp-foreign-file-name-handler-alist
296 (cons 'tramp-fish-file-name-p
'tramp-fish-file-name-handler
))
299 ;; File name primitives
301 (defun tramp-fish-handle-add-name-to-file
302 (filename newname
&optional ok-if-already-exists
)
303 "Like `add-name-to-file' for Tramp files."
304 (unless (tramp-equal-remote filename newname
)
305 (with-parsed-tramp-file-name
306 (if (tramp-tramp-file-p filename
) filename newname
) nil
309 "add-name-to-file: %s"
310 "only implemented for same method, same user, same host")))
311 (with-parsed-tramp-file-name filename v1
312 (with-parsed-tramp-file-name newname v2
313 (when (and (not ok-if-already-exists
)
314 (file-exists-p newname
)
315 (not (numberp ok-if-already-exists
))
318 "File %s already exists; make it a new name anyway? "
322 "add-name-to-file: file %s already exists" newname
))
323 (tramp-flush-file-property v2 v2-localname
)
324 (unless (tramp-fish-send-command-and-check
325 v1
(format "#LINK %s %s" v1-localname v2-localname
))
327 v1
'file-error
"Error with add-name-to-file %s" newname
)))))
329 (defun tramp-fish-handle-copy-file
330 (filename newname
&optional ok-if-already-exists keep-date
)
331 "Like `copy-file' for Tramp files."
332 (tramp-fish-do-copy-or-rename-file
333 'copy filename newname ok-if-already-exists keep-date
))
335 (defun tramp-fish-handle-delete-directory (directory)
336 "Like `delete-directory' for Tramp files."
337 (when (file-exists-p directory
)
338 (with-parsed-tramp-file-name
339 (directory-file-name (expand-file-name directory
)) nil
340 (tramp-flush-directory-property v localname
)
341 (tramp-fish-send-command-and-check v
(format "#RMD %s" localname
)))))
343 (defun tramp-fish-handle-delete-file (filename)
344 "Like `delete-file' for Tramp files."
345 (when (file-exists-p filename
)
346 (with-parsed-tramp-file-name (expand-file-name filename
) nil
347 (tramp-flush-file-property v localname
)
348 (tramp-fish-send-command-and-check v
(format "#DELE %s" localname
)))))
350 (defun tramp-fish-handle-directory-files-and-attributes
351 (directory &optional full match nosort id-format
)
352 "Like `directory-files-and-attributes' for Tramp files."
355 ;; We cannot call `file-attributes' for backward compatibility reasons.
356 ;; Its optional parameter ID-FORMAT is introduced with Emacs 22.
357 (cons x
(tramp-fish-handle-file-attributes
358 (if full x
(expand-file-name x directory
)) id-format
)))
359 (directory-files directory full match nosort
)))
361 (defun tramp-fish-handle-expand-file-name (name &optional dir
)
362 "Like `expand-file-name' for Tramp files."
363 ;; If DIR is not given, use DEFAULT-DIRECTORY or "/".
364 (setq dir
(or dir default-directory
"/"))
365 ;; Unless NAME is absolute, concat DIR and NAME.
366 (unless (file-name-absolute-p name
)
367 (setq name
(concat (file-name-as-directory dir
) name
)))
368 ;; If NAME is not a tramp file, run the real handler
369 (if (or (tramp-completion-mode) (not (tramp-tramp-file-p name
)))
370 (tramp-drop-volume-letter
371 (tramp-run-real-handler 'expand-file-name
(list name nil
)))
373 (with-parsed-tramp-file-name name nil
374 (unless (file-name-absolute-p localname
)
375 (setq localname
(concat "~/" localname
)))
376 ;; Tilde expansion if necessary.
377 (when (string-match "\\`\\(~[^/]*\\)\\(.*\\)\\'" localname
)
378 (let ((uname (match-string 1 localname
))
379 (fname (match-string 2 localname
)))
380 ;; We cannot apply "~user/", because this is not supported
381 ;; by the FISH protocol.
382 (unless (string-equal uname
"~")
384 v
'file-error
"Tilde expansion not supported for %s" name
))
386 (with-connection-property v uname
387 (tramp-fish-send-command-and-check v
"#PWD")
388 (with-current-buffer (tramp-get-buffer v
)
389 (goto-char (point-min))
390 (buffer-substring (point) (tramp-line-end-position)))))
391 (setq localname
(concat uname fname
))))
392 ;; There might be a double slash, for example when "~/"
393 ;; expands to "/". Remove this.
394 (while (string-match "//" localname
)
395 (setq localname
(replace-match "/" t t localname
)))
396 ;; No tilde characters in file name, do normal
397 ;; expand-file-name (this does "/./" and "/../"). We bind
398 ;; `directory-sep-char' here for XEmacs on Windows, which
399 ;; would otherwise use backslash. `default-directory' is
400 ;; bound, because on Windows there would be problems with UNC
401 ;; shares or Cygwin mounts.
402 (tramp-let-maybe directory-sep-char ?
/
403 (let ((default-directory (tramp-temporary-file-directory)))
404 (tramp-make-tramp-file-name
406 (tramp-drop-volume-letter
407 (tramp-run-real-handler 'expand-file-name
408 (list localname
)))))))))
410 (defun tramp-fish-handle-file-attributes (filename &optional id-format
)
411 "Like `file-attributes' for Tramp files."
412 (with-parsed-tramp-file-name (expand-file-name filename
) nil
413 (with-file-property v localname
(format "file-attributes-%s" id-format
)
414 (cdr (car (tramp-fish-get-file-entries v localname nil
))))))
416 (defun tramp-fish-handle-file-directory-p (filename)
417 "Like `file-directory-p' for Tramp files."
418 (let ((attributes (file-attributes filename
)))
420 (or (string-match "d" (nth 8 attributes
))
421 (and (file-symlink-p filename
)
422 (with-parsed-tramp-file-name filename nil
424 (tramp-make-tramp-file-name
425 method user host
(nth 0 attributes
))))))
428 (defun tramp-fish-handle-file-exists-p (filename)
429 "Like `file-exists-p' for Tramp files."
430 (and (file-attributes filename
) t
))
432 (defun tramp-fish-handle-file-executable-p (filename)
433 "Like `file-executable-p' for Tramp files."
434 (with-parsed-tramp-file-name (expand-file-name filename
) nil
435 (with-file-property v localname
"file-executable-p"
436 (when (file-exists-p filename
)
437 (let ((mode-chars (string-to-vector (nth 8 (file-attributes filename
))))
439 (tramp-make-tramp-file-name
441 (tramp-get-connection-property v
"home-directory" nil
))))
442 (or (and (char-equal (aref mode-chars
3) ?x
)
443 (equal (nth 2 (file-attributes filename
))
444 (nth 2 (file-attributes home-directory
))))
445 (and (char-equal (aref mode-chars
6) ?x
)
446 (equal (nth 3 (file-attributes filename
))
447 (nth 3 (file-attributes home-directory
))))
448 (char-equal (aref mode-chars
9) ?x
)))))))
450 (defun tramp-fish-handle-file-readable-p (filename)
451 "Like `file-readable-p' for Tramp files."
452 (with-parsed-tramp-file-name (expand-file-name filename
) nil
453 (with-file-property v localname
"file-readable-p"
454 (when (file-exists-p filename
)
455 (let ((mode-chars (string-to-vector (nth 8 (file-attributes filename
))))
457 (tramp-make-tramp-file-name
459 (tramp-get-connection-property v
"home-directory" nil
))))
460 (or (and (char-equal (aref mode-chars
1) ?r
)
461 (equal (nth 2 (file-attributes filename
))
462 (nth 2 (file-attributes home-directory
))))
463 (and (char-equal (aref mode-chars
4) ?r
)
464 (equal (nth 3 (file-attributes filename
))
465 (nth 3 (file-attributes home-directory
))))
466 (char-equal (aref mode-chars
7) ?r
)))))))
468 (defun tramp-fish-handle-file-writable-p (filename)
469 "Like `file-writable-p' for Tramp files."
470 (with-parsed-tramp-file-name (expand-file-name filename
) nil
471 (with-file-property v localname
"file-writable-p"
472 (if (not (file-exists-p filename
))
473 ;; If file doesn't exist, check if directory is writable.
474 (and (file-directory-p (file-name-directory filename
))
475 (file-writable-p (file-name-directory filename
)))
476 ;; Existing files must be writable.
477 (let ((mode-chars (string-to-vector (nth 8 (file-attributes filename
))))
479 (tramp-make-tramp-file-name
481 (tramp-get-connection-property v
"home-directory" nil
))))
482 (or (and (char-equal (aref mode-chars
2) ?w
)
483 (equal (nth 2 (file-attributes filename
))
484 (nth 2 (file-attributes home-directory
))))
485 (and (char-equal (aref mode-chars
5) ?w
)
486 (equal (nth 3 (file-attributes filename
))
487 (nth 3 (file-attributes home-directory
))))
488 (char-equal (aref mode-chars
8) ?w
)))))))
490 (defun tramp-fish-handle-file-local-copy (filename)
491 "Like `file-local-copy' for Tramp files."
492 (with-parsed-tramp-file-name (expand-file-name filename
) nil
493 (unless (file-exists-p filename
)
496 "Cannot make local copy of non-existing file `%s'" filename
))
497 (let ((tmpfil (tramp-make-temp-file filename
)))
498 (tramp-message v
4 "Fetching %s to tmp file %s..." filename tmpfil
)
499 (when (tramp-fish-retrieve-data v
)
501 (with-current-buffer (tramp-get-buffer v
)
502 (write-region (point-min) (point-max) tmpfil
))
503 (tramp-message v
4 "Fetching %s to tmp file %s...done" filename tmpfil
)
506 ;; This function should return "foo/" for directories and "bar" for
508 (defun tramp-fish-handle-file-name-all-completions (filename directory
)
509 "Like `file-name-all-completions' for Tramp files."
512 (with-parsed-tramp-file-name (expand-file-name directory
) nil
513 (with-file-property v localname
"file-name-all-completions"
516 (with-file-property v localname
"file-entries"
517 (tramp-fish-get-file-entries v localname t
))))
521 (if (string-match "d" (nth 9 x
))
522 (file-name-as-directory (nth 0 x
))
526 (defun tramp-fish-handle-file-newer-than-file-p (file1 file2
)
527 "Like `file-newer-than-file-p' for Tramp files."
529 ((not (file-exists-p file1
)) nil
)
530 ((not (file-exists-p file2
)) t
)
531 (t (tramp-time-less-p (nth 5 (file-attributes file2
))
532 (nth 5 (file-attributes file1
))))))
534 (defun tramp-fish-handle-insert-directory
535 (filename switches
&optional wildcard full-directory-p
)
536 "Like `insert-directory' for Tramp files.
537 WILDCARD and FULL-DIRECTORY-P are not handled."
538 (setq filename
(expand-file-name filename
))
539 (when (file-directory-p filename
)
540 ;; This check is a little bit strange, but in `dired-add-entry'
541 ;; this function is called with a non-directory ...
542 (setq filename
(file-name-as-directory filename
)))
544 (with-parsed-tramp-file-name filename nil
545 (tramp-flush-file-property v localname
)
548 (with-file-property v localname
"file-entries"
549 (tramp-fish-get-file-entries v localname t
))))
556 (if (string-match "t" switches
)
558 (tramp-time-less-p (nth 6 y
) (nth 6 x
))
560 (string-lessp (nth 0 x
) (nth 0 y
))))))
567 "%10s %3d %-8s %-8s %8s %s %s%s\n"
574 (if (tramp-time-less-p
575 (tramp-time-subtract (current-time) (nth 6 x
))
580 (nth 0 x
) ; file name
581 (if (stringp (nth 1 x
)) (format " -> %s" (nth 1 x
)) "")))
586 (defun tramp-fish-handle-insert-file-contents
587 (filename &optional visit beg end replace
)
588 "Like `insert-file-contents' for Tramp files."
589 (barf-if-buffer-read-only)
591 (setq buffer-file-name
(expand-file-name filename
))
592 (set-visited-file-modtime)
593 (set-buffer-modified-p nil
))
595 (with-parsed-tramp-file-name filename nil
596 (if (not (file-exists-p filename
))
598 v
'file-error
"File %s not found on remote host" filename
)
600 (let ((point (point))
602 (tramp-message v
4 "Fetching file %s..." filename
)
603 (when (tramp-fish-retrieve-data v
)
606 (with-current-buffer (tramp-get-buffer v
)
607 (let ((beg (or beg
(point-min)))
608 (end (min (or end
(point-max)) (point-max))))
609 (setq size
(- end beg
))
610 (buffer-substring beg end
))))
612 (tramp-message v
4 "Fetching file %s...done" filename
)
614 (list (expand-file-name filename
) size
)))))
616 (defun tramp-fish-handle-make-directory (dir &optional parents
)
617 "Like `make-directory' for Tramp files."
618 (setq dir
(directory-file-name (expand-file-name dir
)))
619 (unless (file-name-absolute-p dir
)
620 (setq dir
(expand-file-name dir default-directory
)))
621 (with-parsed-tramp-file-name dir nil
623 (let ((ldir (file-name-directory dir
)))
624 ;; Make missing directory parts
625 (when (and parents
(not (file-directory-p ldir
)))
626 (make-directory ldir parents
))
628 (when (file-directory-p ldir
)
629 (make-directory-internal dir
))
630 (unless (file-directory-p dir
)
631 (tramp-error v
'file-error
"Couldn't make directory %s" dir
))))))
633 (defun tramp-fish-handle-make-directory-internal (directory)
634 "Like `make-directory-internal' for Tramp files."
635 (setq directory
(directory-file-name (expand-file-name directory
)))
636 (unless (file-name-absolute-p directory
)
637 (setq directory
(expand-file-name directory default-directory
)))
638 (when (file-directory-p (file-name-directory directory
))
639 (with-parsed-tramp-file-name directory nil
642 (tramp-fish-send-command-and-check v
(format "#MKD %s" localname
))
644 v
'file-error
"Couldn't make directory %s" directory
))))))
646 (defun tramp-fish-handle-make-symbolic-link
647 (filename linkname
&optional ok-if-already-exists
)
648 "Like `make-symbolic-link' for Tramp files.
649 If LINKNAME is a non-Tramp file, it is used verbatim as the target of
650 the symlink. If LINKNAME is a Tramp file, only the localname component is
651 used as the target of the symlink.
653 If LINKNAME is a Tramp file and the localname component is relative, then
654 it is expanded first, before the localname component is taken. Note that
655 this can give surprising results if the user/host for the source and
656 target of the symlink differ."
657 (with-parsed-tramp-file-name linkname nil
658 ;; Do the 'confirm if exists' thing.
659 (when (file-exists-p linkname
)
661 (if (or (null ok-if-already-exists
) ; not allowed to exist
662 (and (numberp ok-if-already-exists
)
665 "File %s already exists; make it a link anyway? "
668 v
'file-already-exists
"File %s already exists" localname
)
669 (delete-file linkname
)))
671 ;; If FILENAME is a Tramp name, use just the localname component.
672 (when (tramp-tramp-file-p filename
)
673 (setq filename
(tramp-file-name-localname
674 (tramp-dissect-file-name (expand-file-name filename
)))))
676 ;; Right, they are on the same host, regardless of user, method, etc.
677 ;; We now make the link on the remote machine. This will occur as the user
678 ;; that FILENAME belongs to.
680 (tramp-fish-send-command-and-check
681 v
(format "#SYMLINK %s %s" filename localname
))
682 (tramp-error v
'file-error
"Error creating symbolic link %s" linkname
))))
684 (defun tramp-fish-handle-rename-file
685 (filename newname
&optional ok-if-already-exists
)
686 "Like `rename-file' for Tramp files."
687 (tramp-fish-do-copy-or-rename-file
688 'rename filename newname ok-if-already-exists t
))
690 (defun tramp-fish-handle-set-file-modes (filename mode
)
691 "Like `set-file-modes' for Tramp files."
692 (with-parsed-tramp-file-name filename nil
693 (tramp-flush-file-property v localname
)
694 (unless (tramp-fish-send-command-and-check
695 v
(format "#CHMOD %s %s"
696 (tramp-decimal-to-octal mode
)
697 (tramp-shell-quote-argument localname
)))
699 v
'file-error
"Error while changing file's mode %s" filename
))))
701 (defun tramp-fish-handle-set-file-times (filename &optional time
)
702 "Like `set-file-times' for Tramp files."
703 (with-parsed-tramp-file-name filename nil
704 (let ((time (if (or (null time
) (equal time
'(0 0))) (current-time) time
)))
706 "touch" nil nil nil
"-t"
707 (format-time-string "%Y%m%d%H%M.%S" time
)
708 (tramp-shell-quote-argument localname
))))))
710 (defun tramp-fish-handle-write-region
711 (start end filename
&optional append visit lockname confirm
)
712 "Like `write-region' for Tramp files."
713 (setq filename
(expand-file-name filename
))
714 (with-parsed-tramp-file-name filename nil
715 ;; XEmacs takes a coding system as the seventh argument, not `confirm'
716 (when (and (not (featurep 'xemacs
))
717 confirm
(file-exists-p filename
))
718 (unless (y-or-n-p (format "File %s exists; overwrite anyway? "
720 (tramp-error v
'file-error
"File not overwritten")))
722 (tramp-flush-file-property v localname
)
725 (let ((tramp-fish-ok-prompt-regexp
727 tramp-fish-ok-prompt-regexp
"\\|"
728 tramp-fish-continue-prompt-regexp
)))
729 (tramp-fish-send-command
730 v
(format "%s %d %s\n### 100"
731 (if append
"#APPEND" "#STOR") (- end start
) localname
)))
733 ;; Send data, if there are any.
735 (tramp-fish-send-command v
(buffer-substring-no-properties start end
)))
738 (set-visited-file-modtime))))
740 (defun tramp-fish-handle-executable-find (command)
741 "Like `executable-find' for Tramp files."
743 (if (zerop (process-file "which" nil t nil command
))
745 (goto-char (point-min))
746 (buffer-substring (point-min) (tramp-line-end-position))))))
748 (defun tramp-fish-handle-process-file
749 (program &optional infile destination display
&rest args
)
750 "Like `process-file' for Tramp files."
751 ;; The implementation is not complete yet.
752 (when (and (numberp destination
) (zerop destination
))
753 (error "Implementation does not handle immediate return"))
755 (with-parsed-tramp-file-name default-directory nil
756 (let ((temp-name-prefix (tramp-make-tramp-temp-file v
))
757 command input output stderr outbuf tmpfil ret
)
759 (setq command
(mapconcat 'tramp-shell-quote-argument
760 (cons program args
) " "))
763 (setq input
"/dev/null")
764 (setq infile
(expand-file-name infile
))
765 (if (tramp-equal-remote default-directory infile
)
766 ;; INFILE is on the same remote host.
767 (setq input
(with-parsed-tramp-file-name infile nil localname
))
768 ;; INFILE must be copied to remote host.
769 (setq input
(concat temp-name-prefix
".in"))
772 (tramp-make-tramp-file-name method user host input
)
774 (when input
(setq command
(format "%s <%s" command input
)))
777 (setq output
(concat temp-name-prefix
".out"))
780 ((bufferp destination
)
781 (setq outbuf destination
))
783 ((stringp destination
)
784 (setq outbuf
(get-buffer-create destination
)))
785 ;; (REAL-DESTINATION ERROR-DESTINATION)
789 ((bufferp (car destination
))
790 (setq outbuf
(car destination
)))
791 ((stringp (car destination
))
792 (setq outbuf
(get-buffer-create (car destination
)))))
795 ((stringp (cadr destination
))
796 (setcar (cdr destination
) (expand-file-name (cadr destination
)))
797 (if (tramp-equal-remote default-directory
(cadr destination
))
798 ;; stderr is on the same remote host.
799 (setq stderr
(with-parsed-tramp-file-name
800 (cadr destination
) nil localname
))
801 ;; stderr must be copied to remote host. The temporary
802 ;; file must be deleted after execution.
803 (setq stderr
(concat temp-name-prefix
".err"))))
804 ;; stderr to be discarded
805 ((null (cadr destination
))
806 (setq stderr
"/dev/null"))))
809 (setq outbuf
(current-buffer))))
810 (when stderr
(setq command
(format "%s 2>%s" command stderr
)))
812 ;; If we have a temporary file, it must be removed after operation.
813 (when (and input
(string-match temp-name-prefix input
))
814 (setq command
(format "%s; rm %s" command input
)))
815 ;; Goto working directory.
817 (tramp-fish-send-command-and-check
818 v
(format "#CWD %s" (tramp-shell-quote-argument localname
)))
819 (tramp-error v
'file-error
"No such directory: %s" default-directory
))
820 ;; Send the command. It might not return in time, so we protect it.
823 (unless (tramp-fish-send-command-and-check
826 (tramp-shell-quote-argument command
) output
))
828 ;; Check return code.
829 (setq tmpfil
(file-local-copy
830 (tramp-make-tramp-file-name method user host output
)))
832 (insert-file-contents tmpfil
)
833 (goto-char (point-max))
835 (looking-at "^###RESULT: \\([0-9]+\\)")
836 (setq ret
(string-to-number (match-string 1)))
837 (delete-region (point) (point-max))
838 (write-region (point-min) (point-max) tmpfil
))
839 ;; We should show the output anyway.
841 (with-current-buffer outbuf
(insert-file-contents tmpfil
))
842 (when display
(display-buffer outbuf
)))
843 ;; Remove output file.
844 (delete-file (tramp-make-tramp-file-name method user host output
)))
845 ;; When the user did interrupt, we should do it also.
846 (error (setq ret
1)))
848 ;; Provide error file.
849 (when (and stderr
(string-match temp-name-prefix stderr
))
850 (rename-file (tramp-make-tramp-file-name method user host stderr
)
851 (cadr destination
) t
)))
852 ;; Return exit status.
856 ;; Internal file name functions
858 (defun tramp-fish-do-copy-or-rename-file
859 (op filename newname
&optional ok-if-already-exists keep-date
)
860 "Copy or rename a remote file.
861 OP must be `copy' or `rename' and indicates the operation to
862 perform. FILENAME specifies the file to copy or rename, NEWNAME
863 is the name of the new file (for copy) or the new name of the
864 file (for rename). OK-IF-ALREADY-EXISTS means don't barf if
865 NEWNAME exists already. KEEP-DATE means to make sure that
866 NEWNAME has the same timestamp as FILENAME.
868 This function is invoked by `tramp-fish-handle-copy-file' and
869 `tramp-fish-handle-rename-file'. It is an error if OP is neither
870 of `copy' and `rename'. FILENAME and NEWNAME must be absolute
872 (unless (memq op
'(copy rename
))
873 (error "Unknown operation `%s', must be `copy' or `rename'" op
))
874 (let ((t1 (tramp-tramp-file-p filename
))
875 (t2 (tramp-tramp-file-p newname
)))
877 (unless ok-if-already-exists
878 (when (and t2
(file-exists-p newname
))
879 (with-parsed-tramp-file-name newname nil
881 v
'file-already-exists
"File %s already exists" newname
))))
885 ;; Both are Tramp files.
888 ;; Shortcut: if method, host, user are the same for both
889 ;; files, we invoke `cp' or `mv' on the remote host
891 ((tramp-equal-remote filename newname
)
892 (tramp-fish-do-copy-or-rename-file-directly
893 op filename newname keep-date
))
894 ;; No shortcut was possible. So we copy the
895 ;; file first. If the operation was `rename', we go
896 ;; back and delete the original file (if the copy was
897 ;; successful). The approach is simple-minded: we
898 ;; create a new buffer, insert the contents of the
899 ;; source file into it, then write out the buffer to
900 ;; the target file. The advantage is that it doesn't
901 ;; matter which filename handlers are used for the
902 ;; source and target file.
904 (tramp-do-copy-or-rename-file-via-buffer
905 op filename newname keep-date
))))
907 ;; One file is a Tramp file, the other one is local.
909 ;; Use the generic method via a Tramp buffer.
910 (tramp-do-copy-or-rename-file-via-buffer
911 op filename newname keep-date
))
914 ;; One of them must be a Tramp file.
915 (error "Tramp implementation says this cannot happen")))
916 ;; When newname did exist, we have wrong cached values.
918 (with-parsed-tramp-file-name newname nil
919 (tramp-flush-file-property v localname
)
920 (tramp-flush-file-property v
(file-name-directory localname
)))))))
922 (defun tramp-fish-do-copy-or-rename-file-directly
923 (op filename newname keep-date
)
924 "Invokes `COPY' or `RENAME' on the remote system.
925 OP must be one of `copy' or `rename', indicating `cp' or `mv',
926 respectively. VEC specifies the connection. LOCALNAME1 and
927 LOCALNAME2 specify the two arguments of `cp' or `mv'. If
928 KEEP-DATE is non-nil, preserve the time stamp when copying."
929 (with-parsed-tramp-file-name filename v1
930 (with-parsed-tramp-file-name newname v2
931 (tramp-fish-send-command
934 (if (eq op
'copy
) "#COPY" "#RENAME")
935 (tramp-shell-quote-argument v1-localname
)
936 (tramp-shell-quote-argument v2-localname
)))))
937 ;; KEEP-DATE handling.
938 (when (and keep-date
(functionp 'set-file-times
))
939 (apply 'set-file-times
(list newname
(nth 5 (file-attributes filename
)))))
941 (set-file-modes newname
(file-modes filename
)))
943 (defun tramp-fish-get-file-entries (vec localname list
)
944 "Read entries returned by FISH server.
945 When LIST is true, a #LIST command will be sent, including all entries
946 of a directory. Otherwise, #STAT is sent for just one entry.
947 Result is a list of (LOCALNAME LINK COUNT UID GID ATIME MTIME CTIME
948 SIZE MODE WEIRD INODE DEVICE)."
950 (with-current-buffer (tramp-get-buffer vec
)
951 ;; #LIST does not work properly with trailing "/", at least in
953 (when (string-match "/$" localname
)
954 (setq localname
(concat localname
".")))
956 (let ((command (format "%s %s" (if list
"#LIST" "#STAT") localname
))
957 buffer-read-only num res
)
960 (tramp-fish-send-command vec command
)
962 ;; Read number of entries
963 (goto-char (point-min))
965 (unless (integerp (setq num
(read (current-buffer)))) (error))
966 (error (return nil
)))
968 (delete-region (point-min) (point))
971 (goto-char (point-min))
973 (unless (looking-at tramp-fish-continue-prompt-regexp
) (error))
974 (error (return nil
)))
976 (delete-region (point-min) (point))
980 (let ((item (tramp-fish-read-file-entry)))
981 ;; Add inode and device.
984 (list (tramp-get-inode vec
)
985 (tramp-get-device vec
))))))
988 (goto-char (point-min))
990 (unless (looking-at tramp-fish-ok-prompt-regexp
) (error))
993 "`%s' does not return a valid Lisp expression: `%s'"
994 command
(buffer-string))))
996 (delete-region (point-min) (point))
1000 (defun tramp-fish-read-file-entry ()
1001 "Parse entry in output buffer.
1002 Result is the list (LOCALNAME LINK COUNT UID GID ATIME MTIME CTIME
1004 ;; We are called from `tramp-fish-get-file-entries', which sets the
1006 (let (buffer-read-only localname link uid gid mtime size mode
)
1010 ;; P<unix permissions> <owner>.<group>
1011 ((looking-at "^P\\(.+\\)\\s-\\(.+\\)\\.\\(.+\\)$")
1012 (setq mode
(match-string 1))
1013 (setq uid
(match-string 2))
1014 (setq gid
(match-string 3))
1015 (when (string-match "^d" mode
) (setq link t
)))
1017 ((looking-at "^S\\([0-9]+\\)$")
1018 (setq size
(string-to-number (match-string 1))))
1019 ;; D<year> <month> <day> <hour> <minute> <second>[.1234]
1021 "^D\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\(\\S-+\\)$")
1024 (string-to-number (match-string 6))
1025 (string-to-number (match-string 5))
1026 (string-to-number (match-string 4))
1027 (string-to-number (match-string 3))
1028 (string-to-number (match-string 2))
1029 (string-to-number (match-string 1)))))
1030 ;; d<3-letters month name> <day> <year or HH:MM>
1031 ((looking-at "^d") nil
)
1032 ;; E<major-of-device>,<minor>
1033 ((looking-at "^E") nil
)
1035 ((looking-at "^:\\(.+\\)$")
1036 (setq localname
(match-string 1)))
1037 ;; L<filename symlink points to>
1038 ((looking-at "^L\\(.+\\)$")
1039 (setq link
(match-string 1)))
1041 ((looking-at "^M\\(.+\\)$") nil
)
1047 (delete-region (point-min) (point))))
1049 ;; delete trailing empty line
1051 (delete-region (point-min) (point))
1053 ;; Return entry in file-attributes format
1054 (list localname link -
1 uid gid
'(0 0) mtime
'(0 0) size mode nil
)))
1056 (defun tramp-fish-retrieve-data (vec)
1057 "Reads remote data for FISH protocol.
1058 The data are left in the connection buffer of VEC for further processing.
1059 Returns the size of the data."
1061 (with-current-buffer (tramp-get-buffer vec
)
1062 ;; The retrieved data might be in binary format, without
1063 ;; trailing newline. Therefore, the OK prompt might not start
1064 ;; at the beginning of a line.
1065 (let ((tramp-fish-ok-prompt-regexp "### 200\n")
1069 (tramp-fish-send-command
1070 vec
(format "#RETR %s" (tramp-file-name-localname vec
)))
1073 (goto-char (point-min))
1075 (unless (integerp (setq size
(read (current-buffer)))) (error))
1076 (error (return nil
)))
1078 (delete-region (point-min) (point))
1081 (goto-char (point-min))
1083 (unless (looking-at tramp-fish-continue-prompt-regexp
) (error))
1084 (error (return nil
)))
1086 (delete-region (point-min) (point))
1088 ;; The received data might contain the OK prompt already, so
1089 ;; there might be outstanding data.
1090 (while (/= (+ size
(length tramp-fish-ok-prompt-regexp
))
1091 (- (point-max) (point-min)))
1092 (tramp-wait-for-regexp
1093 (tramp-get-connection-process vec
) nil
1094 (concat tramp-fish-ok-prompt-regexp
"$")))
1097 (goto-char (+ (point-min) size
))
1099 (unless (looking-at tramp-fish-ok-prompt-regexp
) (error))
1100 (error (return nil
)))
1101 (delete-region (+ (point-min) size
) (point-max))
1105 ;; Connection functions
1107 (defun tramp-fish-maybe-open-connection (vec)
1108 "Maybe open a connection VEC.
1109 Does not do anything if a connection is already open, but re-opens the
1110 connection if a previous connection has died for some reason."
1111 (let ((process-connection-type tramp-process-connection-type
)
1112 (p (get-buffer-process (tramp-get-buffer vec
))))
1114 ;; New connection must be opened.
1115 (unless (and p
(processp p
) (memq (process-status p
) '(run open
)))
1117 ;; Set variables for computing the prompt for reading password.
1118 (setq tramp-current-method
(tramp-file-name-method vec
)
1119 tramp-current-user
(tramp-file-name-user vec
)
1120 tramp-current-host
(tramp-file-name-host vec
))
1122 ;; Start new process.
1123 (when (and p
(processp p
))
1125 (setenv "TERM" tramp-terminal-type
)
1128 vec
3 "Opening connection for %s@%s using %s..."
1129 tramp-current-user tramp-current-host tramp-current-method
)
1131 (let* ((process-connection-type tramp-process-connection-type
)
1132 (inhibit-eol-conversion nil
)
1133 (coding-system-for-read 'binary
)
1134 (coding-system-for-write 'binary
)
1135 ;; This must be done in order to avoid our file name handler.
1136 (p (let ((default-directory (tramp-temporary-file-directory)))
1138 (or (tramp-get-connection-property vec
"process-name" nil
)
1139 (tramp-buffer-name vec
))
1140 (tramp-get-connection-buffer vec
)
1142 (tramp-file-name-user vec
)
1143 (tramp-file-name-host vec
)))))
1144 (tramp-message vec
6 "%s" (mapconcat 'identity
(process-command p
) " "))
1146 ;; Check whether process is alive.
1147 (set-process-sentinel p
'tramp-flush-connection-property
)
1148 (tramp-set-process-query-on-exit-flag p nil
)
1150 (tramp-process-actions p vec tramp-actions-before-shell
60)
1151 (tramp-fish-send-command vec tramp-fish-start-fish-server-command
)
1154 "Found remote shell prompt on `%s'" (tramp-file-name-host vec
))))))
1156 (defun tramp-fish-send-command (vec command
)
1157 "Send the COMMAND to connection VEC."
1158 (tramp-fish-maybe-open-connection vec
)
1159 (tramp-message vec
6 "%s" command
)
1160 (tramp-send-string vec command
)
1161 (tramp-wait-for-regexp
1162 (tramp-get-connection-process vec
) nil
1163 (concat tramp-fish-ok-prompt-regexp
"\\|" tramp-fish-error-prompt-regexp
)))
1165 (defun tramp-fish-send-command-and-check (vec command
)
1166 "Send the COMMAND to connection VEC.
1167 Returns nil if there has been an error message."
1170 (tramp-fish-send-command vec command
)
1172 ;; Read return code.
1173 (with-current-buffer (tramp-get-buffer vec
)
1174 (goto-char (point-min))
1175 (looking-at tramp-fish-ok-prompt-regexp
)))
1177 (provide 'tramp-fish
)
1181 ;; * Evaluate the MIME information with #LIST or #STAT.
1184 ;; arch-tag: a66df7df-5f29-42a7-a921-643ceb29db49
1185 ;;;; tramp-fish.el ends here