1 /* Virtual File System: FISH implementation for transfering files over
4 Copyright (C) 1998 The Free Software Foundation
6 Written by: 1998 Pavel Machek
7 Spaces fix: 2000 Michal Svec
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU Library General Public License
15 as published by the Free Software Foundation; either version 2 of
16 the License, or (at your option) any later version.
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU Library General Public License for more details.
23 You should have received a copy of the GNU Library General Public
24 License along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
28 * Read README.fish for protocol specification.
30 * Syntax of path is: /#sh:user@host[:Cr]/path
31 * where C means you want compressed connection,
32 * and r means you want to use rsh
34 * Namespace: fish_vfs_ops exported.
37 /* Define this if your ssh can take -I option */
41 #undef HAVE_HACKED_SSH
44 #include "../src/dialog.h" /* For MSG_ERROR */
46 #include "xdirentry.h"
49 #include "container.h"
55 #define PRELIM 1 /* positive preliminary */
56 #define COMPLETE 2 /* positive completion */
57 #define CONTINUE 3 /* positive intermediate */
58 #define TRANSIENT 4 /* transient negative completion */
59 #define ERROR 5 /* permanent negative completion */
61 /* If true, the directory cache is forced to reload */
62 static int force_expiration
= 0;
64 /* FIXME: prev two variables should be killed */
66 /* command wait_flag: */
68 #define WAIT_REPLY 0x01
69 #define WANT_STRING 0x02
70 static char reply_str
[80];
73 command (vfs
*me
, vfs_s_super
*super
, int wait_reply
, const char *fmt
, ...)
74 __attribute__ ((format (printf
, 4, 5)));
76 static int decode_reply (char *s
, int was_garbage
)
79 if (!sscanf(s
, "%d", &code
)) {
83 if (code
<100) return was_garbage
? ERROR
: (!code
? COMPLETE
: PRELIM
);
87 /* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
88 static int get_reply (vfs
*me
, int sock
, char *string_buf
, int string_len
)
94 if (!vfs_s_get_line(me
, sock
, answer
, sizeof(answer
), '\n')) {
99 if (strncmp(answer
, "### ", 4)) {
102 strncpy(string_buf
, answer
, string_len
- 1);
103 *(string_buf
+ string_len
- 1) = 0;
105 } else return decode_reply(answer
+4, was_garbage
);
109 #define SUP super->u.fish
112 command (vfs
*me
, vfs_s_super
*super
, int wait_reply
, const char *fmt
, ...)
117 FILE *logfile
= MEDATA
->logfile
;
121 str
= g_strdup_vprintf (fmt
, ap
);
125 fwrite (str
, strlen (str
), 1, logfile
);
129 enable_interrupt_key();
131 status
= write(SUP
.sockw
, str
, strlen(str
));
134 disable_interrupt_key();
139 return get_reply (me
, SUP
.sockr
, (wait_reply
& WANT_STRING
) ? reply_str
: NULL
, sizeof (reply_str
)-1);
144 free_archive (vfs
*me
, vfs_s_super
*super
)
146 if ((SUP
.sockw
!= -1) || (SUP
.sockr
!= -1)){
147 print_vfs_message (_("fish: Disconnecting from %s"), super
->name
?super
->name
:"???");
148 command(me
, super
, NONE
, "#BYE\nexit\n");
151 SUP
.sockw
= SUP
.sockr
= -1;
157 ifree (SUP
.password
);
161 pipeopen(vfs_s_super
*super
, char *path
, char *argv
[])
163 int fileset1
[2], fileset2
[2];
166 if ((pipe(fileset1
)<0) || (pipe(fileset2
)<0))
167 vfs_die("Could not pipe(): %m.");
169 if ((res
= fork())) {
170 if (res
<0) vfs_die("Could not fork(): %m.");
171 /* We are the parent */
173 SUP
.sockw
= fileset1
[1];
175 SUP
.sockr
= fileset2
[0];
179 close(fileset1
[0]); close(fileset1
[1]);
182 /* stderr to /dev/null */
183 open ("/dev/null", O_WRONLY
);
184 close(fileset2
[0]); close(fileset2
[1]);
186 vfs_die("Exec failed.");
190 /* The returned directory should always contain a trailing slash */
191 static char *fish_getcwd(vfs
*me
, vfs_s_super
*super
)
193 if (command(me
, super
, WANT_STRING
, "#PWD\npwd; echo '### 200'\n") == COMPLETE
)
194 return g_strconcat (reply_str
, "/", NULL
);
198 open_archive_int (vfs
*me
, vfs_s_super
*super
)
201 char *xsh
= (SUP
.flags
== FISH_FLAG_RSH
? "rsh" : "ssh");
205 #ifdef HAVE_HACKED_SSH
209 argv
[i
++] = SUP
.user
;
210 argv
[i
++] = SUP
.host
;
211 if (SUP
.flags
== FISH_FLAG_COMPRESSED
)
213 argv
[i
++] = "echo FISH:; /bin/sh";
218 if (!MEDATA
->logfile
)
219 MEDATA
->logfile
= fopen( "/home/pavel/talk.fish", "w+" ); /* FIXME */
222 pipeopen(super
, xsh
, argv
);
226 print_vfs_message( _("fish: Waiting for initial line...") );
227 if (!vfs_s_get_line(me
, SUP
.sockr
, answer
, sizeof(answer
), ':'))
228 ERRNOR (E_PROTO
, -1);
229 print_vfs_message( answer
);
230 if (strstr(answer
, "assword")) {
232 /* Currently, this does not work. ssh reads passwords from
233 /dev/tty, not from stdin :-(. */
235 #ifndef HAVE_HACKED_SSH
236 message_1s (1, MSG_ERROR
, _("Sorry, we can not do password authenticated connections for now."));
241 p
= g_strconcat (_(" fish: Password required for "), SUP
.user
,
243 op
= vfs_get_password (p
);
247 SUP
.password
= g_strdup (op
);
250 print_vfs_message( _("fish: Sending password...") );
251 write(SUP
.sockw
, SUP
.password
, strlen(SUP
.password
));
252 write(SUP
.sockw
, "\n", 1);
256 print_vfs_message( _("fish: Sending initial line...") );
258 * Run `start_fish_server'. If it doesn't exist - no problem,
259 * we'll talk directly to the shell.
261 if (command (me
, super
, WAIT_REPLY
,
262 "#FISH\necho; start_fish_server 2>&1;"
263 " echo '### 200'\n") != COMPLETE
)
264 ERRNOR (E_PROTO
, -1);
266 print_vfs_message( _("fish: Handshaking version...") );
267 if (command (me
, super
, WAIT_REPLY
, "#VER 0.0.0\necho '### 000'\n") != COMPLETE
)
268 ERRNOR (E_PROTO
, -1);
270 /* Set up remote locale to C, otherwise dates cannot be recognized */
271 if (command (me
, super
, WAIT_REPLY
, "LANG=C; LC_ALL=C; LC_TIME=C\n"
272 "export LANG; export LC_ALL; export LC_TIME\n"
273 "echo '### 200'\n") != COMPLETE
)
274 ERRNOR (E_PROTO
, -1);
276 print_vfs_message( _("fish: Setting up current directory...") );
277 SUP
.home
= fish_getcwd (me
, super
);
278 print_vfs_message( _("fish: Connected, home %s."), SUP
.home
);
280 super
->name
= g_strconcat ( "/#sh:", SUP
.user
, "@", SUP
.host
, "/", NULL
);
282 super
->name
= g_strdup(PATH_SEP_STR
);
284 super
->root
= vfs_s_new_inode (me
, super
, vfs_s_default_stat(me
, S_IFDIR
| 0755));
289 open_archive (vfs
*me
, vfs_s_super
*super
, char *archive_name
, char *op
)
291 char *host
, *user
, *password
, *p
;
294 p
= vfs_split_url (strchr(op
, ':')+1, &host
, &user
, &flags
, &password
, 0, URL_NOSLASH
);
302 if (!strncmp( op
, "rsh:", 4 ))
303 SUP
.flags
|= FISH_FLAG_RSH
;
306 SUP
.password
= password
;
307 return open_archive_int (me
, super
);
311 archive_same(vfs
*me
, vfs_s_super
*super
, char *archive_name
, char *op
, void *cookie
)
316 op
= vfs_split_url (strchr(op
, ':')+1, &host
, &user
, &flags
, 0, 0, URL_NOSLASH
);
321 flags
= ((strcmp (host
, SUP
.host
) == 0) &&
322 (strcmp (user
, SUP
.user
) == 0) &&
323 (flags
== SUP
.flags
));
331 fish_which (vfs
*me
, char *path
)
333 if (!strncmp (path
, "/#sh:", 5))
335 if (!strncmp (path
, "/#ssh:", 6))
337 if (!strncmp (path
, "/#rsh:", 6))
343 dir_uptodate(vfs
*me
, vfs_s_inode
*ino
)
347 gettimeofday(&tim
, NULL
);
348 if (force_expiration
) {
349 force_expiration
= 0;
352 if (tim
.tv_sec
< ino
->u
.fish
.timestamp
.tv_sec
)
358 dir_load(vfs
*me
, vfs_s_inode
*dir
, char *remote_path
)
360 vfs_s_super
*super
= dir
->super
;
362 vfs_s_entry
*ent
= NULL
;
365 logfile
= MEDATA
->logfile
;
367 print_vfs_message(_("fish: Reading directory %s..."), remote_path
);
369 gettimeofday(&dir
->u
.fish
.timestamp
, NULL
);
370 dir
->u
.fish
.timestamp
.tv_sec
+= 10; /* was 360: 10 is good for
371 stressing direntry layer a bit */
373 command(me
, super
, NONE
,
375 "ls -lLa \"/%s\" 2>/dev/null | grep '^[^cbt]' | (\n"
376 "while read p x u g s m d y n; do\n"
377 "echo \"P$p $u.$g\nS$s\nd$m $d $y\n:$n\n\"\n"
380 "ls -lLa \"/%s\" 2>/dev/null | grep '^[cb]' | (\n"
381 "while read p x u g a i m d y n; do\n"
382 "echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"\n"
386 remote_path
, remote_path
, remote_path
);
388 #define SIMPLE_ENTRY vfs_s_generate_entry(me, NULL, dir, 0)
391 int res
= vfs_s_get_line_interruptible (me
, buffer
, sizeof (buffer
), SUP
.sockr
);
392 if ((!res
) || (res
== EINTR
)) {
393 vfs_s_free_entry(me
, ent
);
394 me
->verrno
= ECONNRESET
;
398 fputs (buffer
, logfile
);
399 fputs ("\n", logfile
);
402 if (!strncmp(buffer
, "### ", 4))
406 vfs_s_insert_entry(me
, dir
, ent
);
412 #define ST ent->ino->st
417 if (!strcmp(buffer
+1, ".") || !strcmp(buffer
+1, ".."))
418 break; /* We'll do . and .. ourself */
419 ent
->name
= g_strdup(buffer
+1);
420 /* if ((c=strchr(ent->name, ' ')))
421 *c = 0; / * this is ugly, but we can not handle " " in name */
424 case 'S': ST
.st_size
= atoi(buffer
+1); break;
427 if ((i
= vfs_parse_filetype(buffer
[1])) ==-1)
430 if ((i
= vfs_parse_filemode(buffer
+2)) ==-1)
433 if (S_ISLNK(ST
.st_mode
))
438 vfs_split_text(buffer
+1);
439 if (!vfs_parse_filedate(0, &ST
.st_ctime
))
441 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
;
446 if (sscanf(buffer
+1, "%d %d %d %d %d %d", &tim
.tm_year
, &tim
.tm_mon
,
447 &tim
.tm_mday
, &tim
.tm_hour
, &tim
.tm_min
, &tim
.tm_sec
) != 6)
449 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
= mktime(&tim
);
454 if (sscanf(buffer
+1, "%d,%d", &maj
, &min
) != 2)
457 ST
.st_rdev
= (maj
<< 8) | min
;
460 case 'L': ent
->ino
->linkname
= g_strdup(buffer
+1);
465 vfs_s_free_entry (me
, ent
);
466 me
->verrno
= E_REMOTE
;
467 if (decode_reply(buffer
+4, 0) == COMPLETE
)
471 print_vfs_message(_("fish: failed"));
476 file_store(vfs
*me
, vfs_s_fh
*fh
, char *name
, char *localname
)
478 vfs_s_super
*super
= FH_SUPER
;
485 h
= open(localname
, O_RDONLY
);
490 /* Use this as stor: ( dd block ; dd smallblock ) | ( cat > file; cat > /dev/null ) */
492 print_vfs_message(_("fish: store %s: sending command..."), name
);
494 * FIXME: Limit size to unsigned long for now.
495 * Files longer than 256 * ULONG_MAX are not supported.
497 if (command (me
, super
, WAIT_REPLY
,
502 "dd ibs=256 obs=4096 count=%lu\n"
503 "dd bs=%lu count=1\n"
504 ") 2>/dev/null | (\n"
507 "); echo '### 200'\n",
508 (unsigned long) s
.st_size
, name
, name
,
509 (unsigned long) (s
.st_size
>> 8),
510 ((unsigned long) s
.st_size
) & (256 - 1), name
)
512 ERRNOR(E_REMOTE
, -1);
517 while ((n
= read(h
, buffer
, sizeof(buffer
))) < 0) {
518 if ((errno
== EINTR
) && got_interrupt
)
520 print_vfs_message(_("fish: Local read failed, sending zeros") );
522 h
= open( "/dev/zero", O_RDONLY
);
526 while (write(SUP
.sockw
, buffer
, n
) < 0) {
530 disable_interrupt_key();
532 print_vfs_message(_("fish: storing %s %d (%lu)"),
533 was_error
? _("zeros") : _("file"), total
,
534 (unsigned long) s
.st_size
);
536 if ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
) || was_error
)
537 ERRNOR (E_REMOTE
, -1);
542 get_reply(me
, SUP
.sockr
, NULL
, 0);
546 static int linear_start(vfs
*me
, vfs_s_fh
*fh
, int offset
)
550 ERRNOR (E_NOTSUPP
, 0);
551 /* fe->local_stat.st_mtime = 0; FIXME: what is this good for? */
552 name
= vfs_s_fullpath (me
, fh
->ino
);
555 offset
= command(me
, FH_SUPER
, WANT_STRING
,
557 "ls -l \"/%s\" 2>/dev/null | (\n"
558 "read var1 var2 var3 var4 var5 var6\n"
566 if (offset
!= PRELIM
) ERRNOR (E_REMOTE
, 0);
567 fh
->linear
= LS_LINEAR_OPEN
;
569 if (sscanf( reply_str
, "%d", &fh
->u
.fish
.total
)!=1)
570 ERRNOR (E_REMOTE
, 0);
575 linear_abort (vfs
*me
, vfs_s_fh
*fh
)
577 vfs_s_super
*super
= FH_SUPER
;
581 print_vfs_message( _("Aborting transfer...") );
583 n
= MIN(8192, fh
->u
.fish
.total
- fh
->u
.fish
.got
);
585 if ((n
= read(SUP
.sockr
, buffer
, n
)) < 0)
589 if (get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)
590 print_vfs_message( _("Error reported after abort.") );
592 print_vfs_message( _("Aborted transfer would be successful.") );
596 linear_read (vfs
*me
, vfs_s_fh
*fh
, void *buf
, int len
)
598 vfs_s_super
*super
= FH_SUPER
;
600 len
= MIN( fh
->u
.fish
.total
- fh
->u
.fish
.got
, len
);
601 disable_interrupt_key();
602 while (len
&& ((n
= read (SUP
.sockr
, buf
, len
))<0)) {
603 if ((errno
== EINTR
) && !got_interrupt())
607 enable_interrupt_key();
609 if (n
>0) fh
->u
.fish
.got
+= n
;
610 if (n
<0) linear_abort(me
, fh
);
611 if ((!n
) && ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)))
612 ERRNOR (E_REMOTE
, -1);
617 linear_close (vfs
*me
, vfs_s_fh
*fh
)
619 if (fh
->u
.fish
.total
!= fh
->u
.fish
.got
)
620 linear_abort(me
, fh
);
621 else if (stat (fh
->ino
->localname
, &fh
->ino
->u
.fish
.local_stat
) < 0)
622 fh
->ino
->u
.fish
.local_stat
.st_mtime
= 0;
626 fish_ctl (void *fh
, int ctlop
, int arg
)
630 case MCCTL_IS_NOTREADY
:
635 vfs_die ("You may not do this");
636 if (FH
->linear
== LS_LINEAR_CLOSED
)
639 v
= vfs_s_select_on_two (FH_SUPER
->u
.fish
.sockr
, 0);
640 if (((v
< 0) && (errno
== EINTR
)) || v
== 0)
650 send_fish_command(vfs
*me
, vfs_s_super
*super
, char *cmd
, int flags
)
654 r
= command (me
, super
, WAIT_REPLY
, cmd
);
655 vfs_add_noncurrent_stamps (&vfs_fish_ops
, (vfsid
) super
, NULL
);
656 if (r
!= COMPLETE
) ERRNOR (E_REMOTE
, -1);
657 if (flags
& OPT_FLUSH
)
658 vfs_s_invalidate(me
, super
);
663 char buf[BUF_LARGE]; \
665 vfs_s_super *super; \
666 if (!(rpath = vfs_s_get_path_mangle(me, path, &super, 0))) \
669 #define POSTFIX(flags) \
670 return send_fish_command(me, super, buf, flags);
673 fish_chmod (vfs
*me
, char *path
, int mode
)
676 g_snprintf(buf
, sizeof(buf
), "#CHMOD %4.4o /%s\n"
677 "chmod %4.4o \"/%s\" 2>/dev/null\n"
680 mode
& 07777, rpath
);
684 #define FISH_OP(name, chk, string) \
685 static int fish_##name (vfs *me, char *path1, char *path2) \
687 char buf[BUF_LARGE]; \
688 char *rpath1 = NULL, *rpath2 = NULL; \
689 vfs_s_super *super1, *super2; \
690 if (!(rpath1 = vfs_s_get_path_mangle(me, path1, &super1, 0))) \
692 if (!(rpath2 = vfs_s_get_path_mangle(me, path2, &super2, 0))) \
694 g_snprintf(buf, 1023, string "\n", rpath1, rpath2, rpath1, rpath2 ); \
695 return send_fish_command(me, super2, buf, OPT_FLUSH); \
698 #define XTEST if (bucket1 != bucket2) { ERRNOR (EXDEV, -1); }
699 FISH_OP(rename
, XTEST
, "#RENAME /%s /%s\n"
700 "mv \"/%s\" \"/%s\" 2>/dev/null\n"
702 FISH_OP(link
, XTEST
, "#LINK /%s /%s\n"
703 "ln \"/%s\" \"/%s\" 2>/dev/null\n"
706 static int fish_symlink (vfs
*me
, char *setto
, char *path
)
709 g_snprintf(buf
, sizeof(buf
),
711 "ln -s \"%s\" \"/%s\" 2>/dev/null\n"
713 setto
, rpath
, setto
, rpath
);
718 fish_chown (vfs
*me
, char *path
, int owner
, int group
)
720 char *sowner
, *sgroup
;
725 if ((pw
= getpwuid (owner
)) == NULL
)
728 if ((gr
= getgrgid (group
)) == NULL
)
731 sowner
= pw
->pw_name
;
732 sgroup
= gr
->gr_name
;
733 g_snprintf(buf
, sizeof(buf
),
735 "chown %s \"/%s\" 2>/dev/null\n"
739 send_fish_command(me
, super
, buf
, OPT_FLUSH
);
740 /* FIXME: what should we report if chgrp succeeds but chown fails? */
741 g_snprintf(buf
, sizeof(buf
),
743 "chgrp %s \"/%s\" 2>/dev/null\n"
747 /* send_fish_command(me, super, buf, OPT_FLUSH); */
751 static int fish_unlink (vfs
*me
, char *path
)
754 g_snprintf(buf
, sizeof(buf
),
756 "rm -f \"/%s\" 2>/dev/null\n"
762 static int fish_mkdir (vfs
*me
, char *path
, mode_t mode
)
765 g_snprintf(buf
, sizeof(buf
),
767 "mkdir \"/%s\" 2>/dev/null\n"
773 static int fish_rmdir (vfs
*me
, char *path
)
776 g_snprintf(buf
, sizeof(buf
),
778 "rmdir \"/%s\" 2>/dev/null\n"
784 static int fish_fh_open (vfs
*me
, vfs_s_fh
*fh
, int flags
, int mode
)
786 /* File will be written only, so no need to retrieve it */
787 if (((flags
& O_WRONLY
) == O_WRONLY
) && !(flags
& (O_RDONLY
|O_RDWR
))){
788 if (!fh
->ino
->localname
){
789 int tmp_handle
= mc_mkstemps (&fh
->ino
->localname
, me
->name
, NULL
);
790 if (tmp_handle
== -1)
796 if (!fh
->ino
->localname
)
797 if (vfs_s_retrieve_file (me
, fh
->ino
)==-1)
799 if (!fh
->ino
->localname
)
800 vfs_die( "retrieve_file failed to fill in localname" );
804 static struct vfs_s_data fish_data
= {
810 NULL
, /* init_inode */
811 NULL
, /* free_inode */
812 NULL
, /* init_entry */
814 NULL
, /* archive_check */
819 fish_fh_open
, /* fh_open */
822 vfs_s_find_entry_linear
,
833 NULL
, /* This is place of next pointer */
837 &fish_data
, /* data */
864 fish_symlink
, /* symlink */
865 fish_link
, /* link */
868 fish_rename
, /* rename */
878 NULL
, /* vfs_s_getlocalcopy, */
879 NULL
, /* vfs_s_ungetlocalcopy, */