Merge from emacs--rel--22
[bpt/emacs.git] / lisp / net / tramp-fish.el
1 ;;; -*- coding: iso-8859-1; -*-
2 ;;; tramp-fish.el --- Tramp access functions for FISH protocol
3
4 ;; Copyright (C) 2006, 2007 Free Software Foundation, Inc.
5
6 ;; Author: Michael Albinus <michael.albinus@gmx.de>
7 ;; Keywords: comm, processes
8
9 ;; This file is part of GNU Emacs.
10
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.
15
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.
20
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/>.
24
25 ;;; Commentary:
26
27 ;; Access functions for FIles transferred over SHell protocol from Tramp.
28
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.
36
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.
44
45 ;; The GNU Midnight Commander implements the original fish protocol
46 ;; version 0.0.2. The KDE Konqueror has its own implementation, which
47 ;; can be found at
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.
53
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 "\ ".
57
58 ;; Command/Return Code Comment
59 ;;
60 ;; #FISH initial connection, not used
61 ;; in .fishsrv.pl
62 ;; ### 100 transfer fish server missing server, or wrong checksum
63 ;; version 0.0.3 only
64
65 ;; #VER a.b.c <commands requested>
66 ;; VER x.y.z <commands offered> .fishsrv.pl response is not uptodate
67
68 ;; #PWD
69 ;; /path/to/file
70
71 ;; #CWD /some/path
72
73 ;; #COPY /path/a /path/b version 0.0.3 only
74
75 ;; #RENAME /path/a /path/b
76
77 ;; #SYMLINK /path/a /path/b
78
79 ;; #LINK /path/a /path/b
80
81 ;; #DELE /some/path
82
83 ;; #MKD /some/path
84
85 ;; #RMD /some/path
86
87 ;; #CHOWN user /file/name
88
89 ;; #CHGRP group /file/name
90
91 ;; #CHMOD 1234 file
92
93 ;; #READ <offset> <size> /path/and/filename
94 ;; ### 291 successful exit when reading
95 ;; ended at eof
96 ;; ### 292 successful exit when reading
97 ;; did not end at eof
98
99 ;; #WRITE <offset> <size> /path/and/filename
100
101 ;; #APPEND <size> /path/and/filename version 0.0.3 only
102
103 ;; #LIST /directory
104 ;; <number of entries> version 0.0.3 only
105 ;; ### 100 version 0.0.3 only
106 ;; P<unix permissions> <owner>.<group>
107 ;; S<size>
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>
111 ;; :<filename>
112 ;; L<filename symlink points to>
113 ;; M<mimetype> version 0.0.3 only
114 ;; <blank line to separate items>
115
116 ;; #STAT /file version 0.0.3 only
117 ;; like #LIST except for directories
118 ;; <number of entries>
119 ;; ### 100
120 ;; P<unix permissions> <owner>.<group>
121 ;; S<size>
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>
125 ;; :<filename>
126 ;; L<filename symlink points to>
127 ;; <blank line to separate items>
128
129 ;; #RETR /some/name
130 ;; <filesize>
131 ;; ### 100
132 ;; <binary data> exactly filesize bytes
133 ;; ### 200 with no preceding newline
134
135 ;; #STOR <size> /file/name
136 ;; ### 100
137 ;; <data> exactly size bytes
138 ;; ### 001 partial success
139
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>
144
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.
153
154
155 ;;; Code:
156
157 (require 'tramp)
158 (require 'tramp-cache)
159
160 ;; Pacify byte-compiler
161 (eval-when-compile
162 (require 'cl)
163 (require 'custom))
164
165 ;; Avoid byte-compiler warnings if the byte-compiler supports this.
166 ;; Currently, XEmacs supports this.
167 (eval-when-compile
168 (when (featurep 'xemacs)
169 (byte-compiler-options (warnings (- unused-vars)))))
170
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.
174 (eval-when-compile
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))
178
179 ;; Define FISH method ...
180 (defcustom tramp-fish-method "fish"
181 "*Method to connect via FISH protocol."
182 :group 'tramp
183 :type 'string)
184
185 ;; ... and add it to the method list.
186 (add-to-list 'tramp-methods (cons tramp-fish-method nil))
187
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)))
191
192 ;; Add completion function for FISH method.
193 (tramp-set-completion-function
194 tramp-fish-method tramp-completion-function-alist-ssh)
195
196 (defconst tramp-fish-continue-prompt-regexp "^### 100.*\n"
197 "FISH return code OK.")
198
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.")
202
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.")
206
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 \"\"; "
209 "perl .fishsrv.pl "
210 "`grep 'ARGV\\[0\\]' .fishsrv.pl | "
211 "sed -e 's/^[^\"]*\"//' -e 's/\"[^\"]*$//'`; "
212 "exit")
213 "*Command to connect via FISH protocol."
214 :group 'tramp
215 :type 'string)
216
217 ;; New handlers should be added here.
218 (defconst tramp-fish-file-name-handler-alist
219 '(
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)
277 )
278 "Alist of handler functions for Tramp FISH method.
279 Operations not mentioned here will be handled by the default Emacs primitives.")
280
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)))
285
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)))
291 (if fn
292 (save-match-data (apply (cdr fn) args))
293 (tramp-run-real-handler operation args))))
294
295 (add-to-list 'tramp-foreign-file-name-handler-alist
296 (cons 'tramp-fish-file-name-p 'tramp-fish-file-name-handler))
297
298
299 ;; File name primitives
300
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
307 (tramp-error
308 v 'file-error
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))
316 (y-or-n-p
317 (format
318 "File %s already exists; make it a new name anyway? "
319 newname)))
320 (tramp-error
321 v2 'file-error
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))
326 (tramp-error
327 v1 'file-error "Error with add-name-to-file %s" newname)))))
328
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))
334
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)))))
342
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)))))
349
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."
353 (mapcar
354 (lambda (x)
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)))
360
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)))
372 ;; Dissect NAME.
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 "~")
383 (tramp-error
384 v 'file-error "Tilde expansion not supported for %s" name))
385 (setq uname
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
405 method user host
406 (tramp-drop-volume-letter
407 (tramp-run-real-handler 'expand-file-name
408 (list localname)))))))))
409
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))))))
415
416 (defun tramp-fish-handle-file-directory-p (filename)
417 "Like `file-directory-p' for Tramp files."
418 (let ((attributes (file-attributes filename)))
419 (and attributes
420 (or (string-match "d" (nth 8 attributes))
421 (and (file-symlink-p filename)
422 (with-parsed-tramp-file-name filename nil
423 (file-directory-p
424 (tramp-make-tramp-file-name
425 method user host (nth 0 attributes))))))
426 t)))
427
428 (defun tramp-fish-handle-file-exists-p (filename)
429 "Like `file-exists-p' for Tramp files."
430 (and (file-attributes filename) t))
431
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))))
438 (home-directory
439 (tramp-make-tramp-file-name
440 method user host
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)))))))
449
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))))
456 (home-directory
457 (tramp-make-tramp-file-name
458 method user host
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)))))))
467
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))))
478 (home-directory
479 (tramp-make-tramp-file-name
480 method user host
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)))))))
489
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)
494 (tramp-error
495 v 'file-error
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)
500 ;; Save file
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)
504 tmpfil))))
505
506 ;; This function should return "foo/" for directories and "bar" for
507 ;; files.
508 (defun tramp-fish-handle-file-name-all-completions (filename directory)
509 "Like `file-name-all-completions' for Tramp files."
510 (all-completions
511 filename
512 (with-parsed-tramp-file-name (expand-file-name directory) nil
513 (with-file-property v localname "file-name-all-completions"
514 (save-match-data
515 (let ((entries
516 (with-file-property v localname "file-entries"
517 (tramp-fish-get-file-entries v localname t))))
518 (mapcar
519 (lambda (x)
520 (list
521 (if (string-match "d" (nth 9 x))
522 (file-name-as-directory (nth 0 x))
523 (nth 0 x))))
524 entries)))))))
525
526 (defun tramp-fish-handle-file-newer-than-file-p (file1 file2)
527 "Like `file-newer-than-file-p' for Tramp files."
528 (cond
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))))))
533
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)))
543
544 (with-parsed-tramp-file-name filename nil
545 (tramp-flush-file-property v localname)
546 (save-match-data
547 (let ((entries
548 (with-file-property v localname "file-entries"
549 (tramp-fish-get-file-entries v localname t))))
550
551 ;; Sort entries
552 (setq entries
553 (sort
554 entries
555 (lambda (x y)
556 (if (string-match "t" switches)
557 ;; Sort by date.
558 (tramp-time-less-p (nth 6 y) (nth 6 x))
559 ;; Sort by name.
560 (string-lessp (nth 0 x) (nth 0 y))))))
561
562 ;; Print entries.
563 (mapcar
564 (lambda (x)
565 (insert
566 (format
567 "%10s %3d %-8s %-8s %8s %s %s%s\n"
568 (nth 9 x) ; mode
569 1 ; hardlinks
570 (nth 3 x) ; uid
571 (nth 4 x) ; gid
572 (nth 8 x) ; size
573 (format-time-string
574 (if (tramp-time-less-p
575 (tramp-time-subtract (current-time) (nth 6 x))
576 tramp-half-a-year)
577 "%b %e %R"
578 "%b %e %Y")
579 (nth 6 x)) ; date
580 (nth 0 x) ; file name
581 (if (stringp (nth 1 x)) (format " -> %s" (nth 1 x)) "")))
582 (forward-line)
583 (beginning-of-line))
584 entries)))))
585
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)
590 (when visit
591 (setq buffer-file-name (expand-file-name filename))
592 (set-visited-file-modtime)
593 (set-buffer-modified-p nil))
594
595 (with-parsed-tramp-file-name filename nil
596 (if (not (file-exists-p filename))
597 (tramp-error
598 v 'file-error "File %s not found on remote host" filename)
599
600 (let ((point (point))
601 size)
602 (tramp-message v 4 "Fetching file %s..." filename)
603 (when (tramp-fish-retrieve-data v)
604 ;; Insert file
605 (insert
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))))
611 (goto-char point))
612 (tramp-message v 4 "Fetching file %s...done" filename)
613
614 (list (expand-file-name filename) size)))))
615
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
622 (save-match-data
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))
627 ;; Just do it
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))))))
632
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
640 (save-match-data
641 (unless
642 (tramp-fish-send-command-and-check v (format "#MKD %s" localname))
643 (tramp-error
644 v 'file-error "Couldn't make directory %s" directory))))))
645
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.
652
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)
660 ;; What to do?
661 (if (or (null ok-if-already-exists) ; not allowed to exist
662 (and (numberp ok-if-already-exists)
663 (not (yes-or-no-p
664 (format
665 "File %s already exists; make it a link anyway? "
666 localname)))))
667 (tramp-error
668 v 'file-already-exists "File %s already exists" localname)
669 (delete-file linkname)))
670
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)))))
675
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.
679 (unless
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))))
683
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))
689
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)))
698 (tramp-error
699 v 'file-error "Error while changing file's mode %s" filename))))
700
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)))
705 (zerop (process-file
706 "touch" nil nil nil "-t"
707 (format-time-string "%Y%m%d%H%M.%S" time)
708 (tramp-shell-quote-argument localname))))))
709
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? "
719 filename))
720 (tramp-error v 'file-error "File not overwritten")))
721
722 (tramp-flush-file-property v localname)
723
724 ;; Send command
725 (let ((tramp-fish-ok-prompt-regexp
726 (concat
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)))
732
733 ;; Send data, if there are any.
734 (when (> end start)
735 (tramp-fish-send-command v (buffer-substring-no-properties start end)))
736
737 (when (eq visit t)
738 (set-visited-file-modtime))))
739
740 (defun tramp-fish-handle-executable-find (command)
741 "Like `executable-find' for Tramp files."
742 (with-temp-buffer
743 (if (zerop (process-file "which" nil t nil command))
744 (progn
745 (goto-char (point-min))
746 (buffer-substring (point-min) (tramp-line-end-position))))))
747
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"))
754
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)
758 ;; Compute command.
759 (setq command (mapconcat 'tramp-shell-quote-argument
760 (cons program args) " "))
761 ;; Determine input.
762 (if (null infile)
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"))
770 (copy-file
771 infile
772 (tramp-make-tramp-file-name method user host input)
773 t)))
774 (when input (setq command (format "%s <%s" command input)))
775
776 ;; Determine output.
777 (setq output (concat temp-name-prefix ".out"))
778 (cond
779 ;; Just a buffer
780 ((bufferp destination)
781 (setq outbuf destination))
782 ;; A buffer name
783 ((stringp destination)
784 (setq outbuf (get-buffer-create destination)))
785 ;; (REAL-DESTINATION ERROR-DESTINATION)
786 ((consp destination)
787 ;; output
788 (cond
789 ((bufferp (car destination))
790 (setq outbuf (car destination)))
791 ((stringp (car destination))
792 (setq outbuf (get-buffer-create (car destination)))))
793 ;; stderr
794 (cond
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"))))
807 ;; 't
808 (destination
809 (setq outbuf (current-buffer))))
810 (when stderr (setq command (format "%s 2>%s" command stderr)))
811
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.
816 (unless
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.
821 (condition-case nil
822 (unwind-protect
823 (unless (tramp-fish-send-command-and-check
824 v (format
825 "#EXEC %s %s"
826 (tramp-shell-quote-argument command) output))
827 (error))
828 ;; Check return code.
829 (setq tmpfil (file-local-copy
830 (tramp-make-tramp-file-name method user host output)))
831 (with-temp-buffer
832 (insert-file-contents tmpfil)
833 (goto-char (point-max))
834 (forward-line -1)
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.
840 (when outbuf
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)))
847 (unless ret
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.
853 ret)))
854
855
856 ;; Internal file name functions
857
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.
867
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
871 file names."
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)))
876
877 (unless ok-if-already-exists
878 (when (and t2 (file-exists-p newname))
879 (with-parsed-tramp-file-name newname nil
880 (tramp-error
881 v 'file-already-exists "File %s already exists" newname))))
882
883 (prog1
884 (cond
885 ;; Both are Tramp files.
886 ((and t1 t2)
887 (cond
888 ;; Shortcut: if method, host, user are the same for both
889 ;; files, we invoke `cp' or `mv' on the remote host
890 ;; directly.
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.
903 (t
904 (tramp-do-copy-or-rename-file-via-buffer
905 op filename newname keep-date))))
906
907 ;; One file is a Tramp file, the other one is local.
908 ((or t1 t2)
909 ;; Use the generic method via a Tramp buffer.
910 (tramp-do-copy-or-rename-file-via-buffer
911 op filename newname keep-date))
912
913 (t
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.
917 (when t2
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)))))))
921
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
932 v1
933 (format "%s %s %s"
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)))))
940 ;; Set the mode.
941 (set-file-modes newname (file-modes filename)))
942
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)."
949 (block nil
950 (with-current-buffer (tramp-get-buffer vec)
951 ;; #LIST does not work properly with trailing "/", at least in
952 ;; .fishsrv.pl.
953 (when (string-match "/$" localname)
954 (setq localname (concat localname ".")))
955
956 (let ((command (format "%s %s" (if list "#LIST" "#STAT") localname))
957 buffer-read-only num res)
958
959 ;; Send command
960 (tramp-fish-send-command vec command)
961
962 ;; Read number of entries
963 (goto-char (point-min))
964 (condition-case nil
965 (unless (integerp (setq num (read (current-buffer)))) (error))
966 (error (return nil)))
967 (forward-line)
968 (delete-region (point-min) (point))
969
970 ;; Read return code
971 (goto-char (point-min))
972 (condition-case nil
973 (unless (looking-at tramp-fish-continue-prompt-regexp) (error))
974 (error (return nil)))
975 (forward-line)
976 (delete-region (point-min) (point))
977
978 ;; Loop the listing
979 (dotimes (i num)
980 (let ((item (tramp-fish-read-file-entry)))
981 ;; Add inode and device.
982 (add-to-list
983 'res (append item
984 (list (tramp-get-inode vec)
985 (tramp-get-device vec))))))
986
987 ;; Read return code
988 (goto-char (point-min))
989 (condition-case nil
990 (unless (looking-at tramp-fish-ok-prompt-regexp) (error))
991 (error (tramp-error
992 vec 'file-error
993 "`%s' does not return a valid Lisp expression: `%s'"
994 command (buffer-string))))
995 (forward-line)
996 (delete-region (point-min) (point))
997
998 res))))
999
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
1003 SIZE MODE WEIRD)."
1004 ;; We are called from `tramp-fish-get-file-entries', which sets the
1005 ;; current buffer.
1006 (let (buffer-read-only localname link uid gid mtime size mode)
1007 (block nil
1008 (while t
1009 (cond
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)))
1016 ;; S<size>
1017 ((looking-at "^S\\([0-9]+\\)$")
1018 (setq size (string-to-number (match-string 1))))
1019 ;; D<year> <month> <day> <hour> <minute> <second>[.1234]
1020 ((looking-at
1021 "^D\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\([0-9]+\\)\\s-\\(\\S-+\\)$")
1022 (setq mtime
1023 (encode-time
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)
1034 ;; :<filename>
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)))
1040 ;; M<mimetype>
1041 ((looking-at "^M\\(.+\\)$") nil)
1042 ;; last line
1043 ((looking-at "^$")
1044 (return)))
1045 ;; delete line
1046 (forward-line)
1047 (delete-region (point-min) (point))))
1048
1049 ;; delete trailing empty line
1050 (forward-line)
1051 (delete-region (point-min) (point))
1052
1053 ;; Return entry in file-attributes format
1054 (list localname link -1 uid gid '(0 0) mtime '(0 0) size mode nil)))
1055
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."
1060 (block nil
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")
1066 size)
1067
1068 ;; Send command
1069 (tramp-fish-send-command
1070 vec (format "#RETR %s" (tramp-file-name-localname vec)))
1071
1072 ;; Read filesize
1073 (goto-char (point-min))
1074 (condition-case nil
1075 (unless (integerp (setq size (read (current-buffer)))) (error))
1076 (error (return nil)))
1077 (forward-line)
1078 (delete-region (point-min) (point))
1079
1080 ;; Read return code
1081 (goto-char (point-min))
1082 (condition-case nil
1083 (unless (looking-at tramp-fish-continue-prompt-regexp) (error))
1084 (error (return nil)))
1085 (forward-line)
1086 (delete-region (point-min) (point))
1087
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 "$")))
1095
1096 ;; Read return code
1097 (goto-char (+ (point-min) size))
1098 (condition-case nil
1099 (unless (looking-at tramp-fish-ok-prompt-regexp) (error))
1100 (error (return nil)))
1101 (delete-region (+ (point-min) size) (point-max))
1102 size))))
1103
1104
1105 ;; Connection functions
1106
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))))
1113
1114 ;; New connection must be opened.
1115 (unless (and p (processp p) (memq (process-status p) '(run open)))
1116
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))
1121
1122 ;; Start new process.
1123 (when (and p (processp p))
1124 (delete-process p))
1125 (setenv "TERM" tramp-terminal-type)
1126 (setenv "PS1" "$ ")
1127 (tramp-message
1128 vec 3 "Opening connection for %s@%s using %s..."
1129 tramp-current-user tramp-current-host tramp-current-method)
1130
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)))
1137 (start-process
1138 (or (tramp-get-connection-property vec "process-name" nil)
1139 (tramp-buffer-name vec))
1140 (tramp-get-connection-buffer vec)
1141 "ssh" "-l"
1142 (tramp-file-name-user vec)
1143 (tramp-file-name-host vec)))))
1144 (tramp-message vec 6 "%s" (mapconcat 'identity (process-command p) " "))
1145
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)
1149
1150 (tramp-process-actions p vec tramp-actions-before-shell 60)
1151 (tramp-fish-send-command vec tramp-fish-start-fish-server-command)
1152 (tramp-message
1153 vec 3
1154 "Found remote shell prompt on `%s'" (tramp-file-name-host vec))))))
1155
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)))
1164
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."
1168
1169 ;; Send command.
1170 (tramp-fish-send-command vec command)
1171
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)))
1176
1177 (provide 'tramp-fish)
1178 ;
1179 ;;;; TODO:
1180 ;
1181 ;; * Evaluate the MIME information with #LIST or #STAT.
1182 ;
1183
1184 ;; arch-tag: a66df7df-5f29-42a7-a921-643ceb29db49
1185 ;;;; tramp-fish.el ends here