1 /* Virtual File System: FISH implementation for transfering files over
4 Copyright (C) 1998 The Free Software Foundation
6 Written by: 1998 Pavel Machek
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public License
12 as published by the Free Software Foundation; either version 2 of
13 the License, or (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU Library General Public License for more details.
20 You should have received a copy of the GNU Library General Public
21 License along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
25 * Read README.fish for protocol specification.
27 * Syntax of path is: /#sh:user@host[:Cr]/path
28 * where C means you want compressed connection,
29 * and r means you want to use rsh
31 * Namespace: fish_vfs_ops exported.
34 /* Define this if your ssh can take -I option */
36 #undef HAVE_HACKED_SSH
40 #include "xdirentry.h"
43 #include "container.h"
49 #define PRELIM 1 /* positive preliminary */
50 #define COMPLETE 2 /* positive completion */
51 #define CONTINUE 3 /* positive intermediate */
52 #define TRANSIENT 4 /* transient negative completion */
53 #define ERROR 5 /* permanent negative completion */
55 /* If true, the directory cache is forced to reload */
56 static int force_expiration
= 0;
58 /* FIXME: prev two variables should be killed */
60 /* command wait_flag: */
62 #define WAIT_REPLY 0x01
63 #define WANT_STRING 0x02
64 static char reply_str
[80];
66 static int decode_reply (char *s
, int was_garbage
)
69 if (!sscanf(s
, "%d", &code
)) {
73 if (code
<100) return was_garbage
? ERROR
: (!code
? COMPLETE
: PRELIM
);
77 /* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
78 static int get_reply (vfs
*me
, int sock
, char *string_buf
, int string_len
)
84 if (!vfs_s_get_line(me
, sock
, answer
, sizeof(answer
), '\n')) {
89 if (strncmp(answer
, "### ", 4)) {
92 strncpy(string_buf
, answer
, string_len
- 1);
93 *(string_buf
+ string_len
- 1) = 0;
95 } else return decode_reply(answer
+4, was_garbage
);
99 #define SUP super->u.fish
101 static int command (vfs
*me
, vfs_s_super
*super
, int wait_reply
, char *fmt
, ...)
106 FILE *logfile
= MEDATA
->logfile
;
110 str
= g_strdup_vprintf (fmt
, ap
);
114 fwrite (str
, strlen (str
), 1, logfile
);
118 enable_interrupt_key();
120 status
= write(SUP
.sockw
, str
, strlen(str
));
123 disable_interrupt_key();
128 return get_reply (me
, SUP
.sockr
, (wait_reply
& WANT_STRING
) ? reply_str
: NULL
, sizeof (reply_str
)-1);
133 free_archive (vfs
*me
, vfs_s_super
*super
)
135 if ((SUP
.sockw
!= -1) || (SUP
.sockr
!= -1)){
136 print_vfs_message (_("fish: Disconnecting from %s"), super
->name
?super
->name
:"???");
137 command(me
, super
, NONE
, "#BYE\nlogout\n");
140 SUP
.sockw
= SUP
.sockr
= -1;
146 ifree (SUP
.password
);
150 pipeopen(vfs_s_super
*super
, char *path
, char *argv
[])
152 int fileset1
[2], fileset2
[2];
155 if ((pipe(fileset1
)<0) || (pipe(fileset2
)<0))
156 vfs_die("Could not pipe(): %m.");
158 if ((res
= fork())) {
159 if (res
<0) vfs_die("Could not fork(): %m.");
160 /* We are the parent */
162 SUP
.sockw
= fileset1
[1];
164 SUP
.sockr
= fileset2
[0];
168 close(fileset1
[0]); close(fileset1
[1]);
172 close(fileset2
[0]); close(fileset2
[1]);
174 vfs_die("Exec failed.");
178 /* The returned directory should always contain a trailing slash */
179 static char *fish_getcwd(vfs
*me
, vfs_s_super
*super
)
181 if (command(me
, super
, WANT_STRING
, "#PWD\npwd; echo '### 200'\n") == COMPLETE
)
182 return g_strconcat (reply_str
, "/", NULL
);
186 open_archive_int (vfs
*me
, vfs_s_super
*super
)
189 char *xsh
= (SUP
.flags
== FISH_FLAG_RSH
? "rsh" : "ssh");
193 #ifdef HAVE_HACKED_SSH
197 argv
[i
++] = SUP
.user
;
198 argv
[i
++] = SUP
.host
;
199 if (SUP
.flags
== FISH_FLAG_COMPRESSED
)
201 argv
[i
++] = "echo FISH:; /bin/sh";
206 if (!MEDATA
->logfile
)
207 MEDATA
->logfile
= fopen( "/home/pavel/talk.fish", "w+" ); /* FIXME */
210 pipeopen(super
, xsh
, argv
);
214 print_vfs_message( _("fish: Waiting for initial line...") );
215 if (!vfs_s_get_line(me
, SUP
.sockr
, answer
, sizeof(answer
), ':'))
216 ERRNOR (E_PROTO
, -1);
217 print_vfs_message( answer
);
218 if (strstr(answer
, "assword")) {
220 /* Currently, this does not work. ssh reads passwords from
221 /dev/tty, not from stdin :-(. */
223 #ifndef HAVE_HACKED_SSH
224 message_1s (1, _(" Error "), _("Sorry, we can not do password authenticated connections for now."));
229 p
= g_strconcat (" fish: Password required for ", SUP
.user
,
231 op
= vfs_get_password (p
);
235 SUP
.password
= g_strdup (op
);
238 print_vfs_message( _("fish: Sending password...") );
239 write(SUP
.sockw
, SUP
.password
, strlen(SUP
.password
));
240 write(SUP
.sockw
, "\n", 1);
244 print_vfs_message( _("fish: Sending initial line...") );
245 if (command (me
, super
, WAIT_REPLY
, "#FISH\necho; start_fish_server; echo '### 200'\n") != COMPLETE
)
246 ERRNOR (E_PROTO
, -1);
248 print_vfs_message( _("fish: Handshaking version...") );
249 if (command (me
, super
, WAIT_REPLY
, "#VER 0.0.0\necho '### 000'\n") != COMPLETE
)
250 ERRNOR (E_PROTO
, -1);
252 print_vfs_message( _("fish: Setting up current directory...") );
253 SUP
.home
= fish_getcwd (me
, super
);
254 print_vfs_message( _("fish: Connected, home %s."), SUP
.home
);
256 super
->name
= g_strconcat ( "/#sh:", SUP
.user
, "@", SUP
.host
, "/", NULL
);
258 super
->name
= g_strdup(PATH_SEP_STR
);
260 super
->root
= vfs_s_new_inode (me
, super
, vfs_s_default_stat(me
, S_IFDIR
| 0755));
265 open_archive (vfs
*me
, vfs_s_super
*super
, char *archive_name
, char *op
)
267 char *host
, *user
, *password
;
270 vfs_split_url (strchr(op
, ':')+1, &host
, &user
, &flags
, &password
, 0, URL_NOSLASH
);
271 SUP
.host
= g_strdup (host
);
272 SUP
.user
= g_strdup (user
);
274 if (!strncmp( op
, "rsh:", 4 ))
275 SUP
.flags
|= FISH_FLAG_RSH
;
278 SUP
.password
= g_strdup (password
);
279 return open_archive_int (me
, super
);
283 archive_same(vfs
*me
, vfs_s_super
*super
, char *archive_name
, char *op
, void *cookie
)
285 char *host
, *user
, *dummy2
;
287 vfs_split_url (strchr(op
, ':')+1, &host
, &user
, &flags
, &dummy2
, 0, URL_NOSLASH
);
288 return ((strcmp (host
, SUP
.host
) == 0) &&
289 (strcmp (user
, SUP
.user
) == 0) &&
290 (flags
== SUP
.flags
));
294 fish_which (vfs
*me
, char *path
)
296 if (!strncmp (path
, "/#sh:", 5))
298 if (!strncmp (path
, "/#ssh:", 6))
300 if (!strncmp (path
, "/#rsh:", 6))
306 dir_uptodate(vfs
*me
, vfs_s_inode
*ino
)
310 return 1; /* Timeouting of directories does not work too well :-(. */
311 gettimeofday(&tim
, NULL
);
312 if (force_expiration
) {
313 force_expiration
= 0;
316 if (tim
.tv_sec
< ino
->u
.fish
.timestamp
.tv_sec
)
322 dir_load(vfs
*me
, vfs_s_inode
*dir
, char *remote_path
)
324 vfs_s_super
*super
= dir
->super
;
326 vfs_s_entry
*ent
= NULL
;
329 logfile
= MEDATA
->logfile
;
331 print_vfs_message(_("fish: Reading directory %s..."), remote_path
);
333 gettimeofday(&dir
->u
.fish
.timestamp
, NULL
);
334 dir
->u
.fish
.timestamp
.tv_sec
+= 10; /* was 360: 10 is good for
335 stressing direntry layer a bit */
337 command(me
, super
, NONE
,
338 "#LIST /%s\nls -lLa /%s | grep '^[^cbt]' | ( while read p x u g s m d y n; do echo \"P$p $u.$g\n"
339 "S$s\nd$m $d $y\n:$n\n\"; done )\n"
340 "ls -lLa /%s | grep '^[cb]' | ( while read p x u g a i m d y n; do echo \"P$p $u.$g\n"
341 "E$a$i\nd$m $d $y\n:$n\n\"; done ); echo '### 200'\n",
342 remote_path
, remote_path
, remote_path
);
344 #define SIMPLE_ENTRY vfs_s_generate_entry(me, NULL, dir, 0)
347 int res
= vfs_s_get_line_interruptible (me
, buffer
, sizeof (buffer
), SUP
.sockr
);
348 if ((!res
) || (res
== EINTR
)) {
349 vfs_s_free_entry(me
, ent
);
350 me
->verrno
= ECONNRESET
;
354 fputs (buffer
, logfile
);
355 fputs ("\n", logfile
);
358 if (!strncmp(buffer
, "### ", 4))
362 vfs_s_insert_entry(me
, dir
, ent
);
368 #define ST ent->ino->st
373 if (!strcmp(buffer
+1, ".") || !strcmp(buffer
+1, ".."))
374 break; /* We'll do . and .. ourself */
375 ent
->name
= g_strdup(buffer
+1);
376 if ((c
=strchr(ent
->name
, ' ')))
377 *c
= 0; /* this is ugly, but we can not handle " " in name */
380 case 'S': ST
.st_size
= atoi(buffer
+1); break;
383 if ((i
= vfs_parse_filetype(buffer
[1])) ==-1)
386 if ((i
= vfs_parse_filemode(buffer
+2)) ==-1)
389 if (S_ISLNK(ST
.st_mode
))
394 vfs_split_text(buffer
+1);
395 if (!vfs_parse_filedate(0, &ST
.st_ctime
))
397 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
;
402 if (sscanf(buffer
+1, "%d %d %d %d %d %d", &tim
.tm_year
, &tim
.tm_mon
,
403 &tim
.tm_mday
, &tim
.tm_hour
, &tim
.tm_min
, &tim
.tm_sec
) != 6)
405 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
= mktime(&tim
);
410 if (sscanf(buffer
+1, "%d,%d", &maj
, &min
) != 2)
413 ST
.st_rdev
= (maj
<< 8) | min
;
416 case 'L': ent
->ino
->linkname
= g_strdup(buffer
+1);
421 vfs_s_free_entry (me
, ent
);
422 me
->verrno
= E_REMOTE
;
423 if (decode_reply(buffer
+4, 0) != COMPLETE
)
426 print_vfs_message(_("fish: got listing"));
430 print_vfs_message(_("fish: failed"));
435 file_store(vfs
*me
, vfs_s_super
*super
, char *name
, char *localname
)
443 h
= open(localname
, O_RDONLY
);
448 /* Use this as stor: ( dd block ; dd smallblock ) | ( cat > file; cat > /dev/null ) */
450 print_vfs_message(_("fish: store %s: sending command..."), name
);
451 if (command (me
, super
, WAIT_REPLY
,
452 "#STOR %d /%s\n> /%s; echo '### 001'; ( dd bs=4096 count=%d; dd bs=%d count=1 ) 2>/dev/null | ( cat > /%s; cat > /dev/null ); echo '### 200'\n",
453 s
.st_size
, name
, name
,
454 s
.st_size
/ 4096, s
.st_size
% 4096, name
)
456 ERRNOR(E_REMOTE
, -1);
461 while ((n
= read(h
, buffer
, sizeof(buffer
))) < 0) {
462 if ((errno
== EINTR
) && got_interrupt
)
464 print_vfs_message(_("fish: Local read failed, sending zeros") );
466 h
= open( "/dev/zero", O_RDONLY
);
470 while (write(SUP
.sockw
, buffer
, n
) < 0) {
474 disable_interrupt_key();
476 print_vfs_message(_("fish: storing %s %d (%d)"),
477 was_error
? "zeros" : "file", total
, s
.st_size
);
479 if ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
) || was_error
)
480 ERRNOR (E_REMOTE
, 0);
485 get_reply(me
, SUP
.sockr
, NULL
, 0);
489 static int linear_start(vfs
*me
, vfs_s_fh
*fh
, int offset
)
493 ERRNOR (E_NOTSUPP
, 0);
494 /* fe->local_stat.st_mtime = 0; FIXME: what is this good for? */
495 name
= vfs_s_fullpath (me
, fh
->ino
);
498 if (command(me
, FH_SUPER
, WANT_STRING
,
499 "#RETR /%s\nls -l /%s | ( read var1 var2 var3 var4 var5 var6; echo $var5 ); echo '### 100'; cat /%s; echo '### 200'\n",
501 != PRELIM
) ERRNOR (E_REMOTE
, 0);
502 fh
->linear
= LS_LINEAR_OPEN
;
504 if (sscanf( reply_str
, "%d", &fh
->u
.fish
.total
)!=1)
505 ERRNOR (E_REMOTE
, 0);
510 linear_abort (vfs
*me
, vfs_s_fh
*fh
)
512 vfs_s_super
*super
= FH_SUPER
;
516 print_vfs_message( _("Aborting transfer...") );
518 n
= MIN(8192, fh
->u
.fish
.total
- fh
->u
.fish
.got
);
520 if ((n
= read(SUP
.sockr
, buffer
, n
)) < 0)
524 if (get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)
525 print_vfs_message( _("Error reported after abort.") );
527 print_vfs_message( _("Aborted transfer would be successful.") );
531 linear_read (vfs
*me
, vfs_s_fh
*fh
, void *buf
, int len
)
533 vfs_s_super
*super
= FH_SUPER
;
535 len
= MIN( fh
->u
.fish
.total
- fh
->u
.fish
.got
, len
);
536 disable_interrupt_key();
537 while (len
&& ((n
= read (SUP
.sockr
, buf
, len
))<0)) {
538 if ((errno
== EINTR
) && !got_interrupt())
542 enable_interrupt_key();
544 if (n
>0) fh
->u
.fish
.got
+= n
;
545 if (n
<0) linear_abort(me
, fh
);
546 if ((!n
) && ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)))
547 ERRNOR (E_REMOTE
, -1);
552 linear_close (vfs
*me
, vfs_s_fh
*fh
)
554 if (fh
->u
.fish
.total
!= fh
->u
.fish
.got
)
555 linear_abort(me
, fh
);
559 fish_ctl (void *fh
, int ctlop
, int arg
)
563 case MCCTL_IS_NOTREADY
:
568 vfs_die ("You may not do this");
569 if (FH
->linear
== LS_LINEAR_CLOSED
)
572 v
= vfs_s_select_on_two (FH_SUPER
->u
.fish
.sockr
, 0);
573 if (((v
< 0) && (errno
== EINTR
)) || v
== 0)
583 send_fish_command(vfs
*me
, vfs_s_super
*super
, char *cmd
, int flags
)
587 r
= command (me
, super
, WAIT_REPLY
, cmd
);
588 vfs_add_noncurrent_stamps (&vfs_fish_ops
, (vfsid
) super
, NULL
);
589 if (r
!= COMPLETE
) ERRNOR (E_REMOTE
, -1);
590 if (flags
& OPT_FLUSH
)
591 vfs_s_invalidate(me
, super
);
596 char buf[BUF_LARGE]; \
598 vfs_s_super *super; \
599 if (!(rpath = vfs_s_get_path_mangle(me, path, &super, 0))) \
602 #define POSTFIX(flags) \
603 return send_fish_command(me, super, buf, flags);
606 fish_chmod (vfs
*me
, char *path
, int mode
)
609 g_snprintf(buf
, sizeof(buf
), "#CHMOD %4.4o /%s\nchmod %4.4o /%s; echo '### 000'\n",
611 mode
& 07777, rpath
);
615 #define FISH_OP(name, chk, string) \
616 static int fish_##name (vfs *me, char *path1, char *path2) \
618 char buf[BUF_LARGE]; \
619 char *rpath1 = NULL, *rpath2 = NULL; \
620 vfs_s_super *super1, *super2; \
621 if (!(rpath1 = vfs_s_get_path_mangle(me, path1, &super1, 0))) \
623 if (!(rpath2 = vfs_s_get_path_mangle(me, path2, &super2, 0))) \
625 g_snprintf(buf, 1023, string "\n", rpath1, rpath2, rpath1, rpath2 ); \
626 return send_fish_command(me, super2, buf, OPT_FLUSH); \
629 #define XTEST if (bucket1 != bucket2) { ERRNOR (EXDEV, -1); }
630 FISH_OP(rename
, XTEST
, "#RENAME /%s /%s\nmv /%s /%s; echo '### 000'" );
631 FISH_OP(link
, XTEST
, "#LINK /%s /%s\nln /%s /%s; echo '### 000'" );
633 static int fish_symlink (vfs
*me
, char *setto
, char *path
)
636 g_snprintf(buf
, sizeof(buf
), "#SYMLINK %s /%s\nln -s %s /%s; echo '### 000'\n", setto
, rpath
, setto
, rpath
);
641 fish_chown (vfs
*me
, char *path
, int owner
, int group
)
643 char *sowner
, *sgroup
;
645 sowner
= getpwuid( owner
)->pw_name
;
646 sgroup
= getgrgid( group
)->gr_name
;
647 g_snprintf(buf
, sizeof(buf
), "#CHOWN /%s /%s\nchown /%s /%s; echo '### 000'\n",
650 send_fish_command(me
, super
, buf
, OPT_FLUSH
);
651 /* FIXME: what should we report if chgrp succeeds but chown fails? */
652 g_snprintf(buf
, sizeof(buf
), "#CHGRP /%s /%s\nchgrp /%s /%s; echo '### 000'\n",
658 static int fish_unlink (vfs
*me
, char *path
)
661 g_snprintf(buf
, sizeof(buf
), "#DELE /%s\nrm -f /%s; echo '### 000'\n", rpath
, rpath
);
665 static int fish_mkdir (vfs
*me
, char *path
, mode_t mode
)
668 g_snprintf(buf
, sizeof(buf
), "#MKD /%s\nmkdir /%s; echo '### 000'\n", rpath
, rpath
);
672 static int fish_rmdir (vfs
*me
, char *path
)
675 g_snprintf(buf
, sizeof(buf
), "#RMD /%s\nrmdir /%s; echo '### 000'\n", rpath
, rpath
);
679 static int retrieve_file(vfs
*me
, struct vfs_s_inode
*ino
)
681 /* If you want reget, you'll have to open file with O_LINEAR */
685 int stat_size
= ino
->st
.st_size
;
688 memset(&fh
, 0, sizeof(fh
));
691 if (!(ino
->localname
= tempnam (NULL
, me
->name
))) ERRNOR (ENOMEM
, 0);
693 handle
= open(ino
->localname
, O_RDWR
| O_CREAT
| O_TRUNC
| O_EXCL
, 0600);
699 if (!MEDATA
->linear_start (me
, &fh
, 0))
702 /* Clear the interrupt status */
705 n
= linear_read(me
, &fh
, buffer
, sizeof(buffer
));
712 vfs_print_stats (me
->name
, "Getting file", ino
->ent
->name
, total
, stat_size
);
714 if (write(handle
, buffer
, n
) < 0) {
719 linear_close(me
, &fh
);
722 if (stat (ino
->localname
, &ino
->u
.fish
.local_stat
) < 0)
723 ino
->u
.fish
.local_stat
.st_mtime
= 0;
727 linear_close(me
, &fh
);
729 disable_interrupt_key();
731 unlink(ino
->localname
);
733 g_free(ino
->localname
);
734 ino
->localname
= NULL
;
738 static int fish_fh_open (vfs
*me
, vfs_s_fh
*fh
, int flags
, int mode
)
740 if (IS_LINEAR(mode
)) {
741 message_1s(1, "Linear mode requested", "?!" );
742 fh
->linear
= LS_LINEAR_CLOSED
;
745 if (!fh
->ino
->localname
)
746 if (retrieve_file (me
, fh
->ino
)==-1)
748 if (!fh
->ino
->localname
)
749 vfs_die( "retrieve_file failed to fill in localname" );
753 static struct vfs_s_data fish_data
= {
759 NULL
, /* init_inode */
760 NULL
, /* free_inode */
761 NULL
, /* init_entry */
763 NULL
, /* archive_check */
768 fish_fh_open
, /* fh_open */
771 vfs_s_find_entry_linear
,
782 NULL
, /* This is place of next pointer */
783 "FIles tranferred over SHell",
786 &fish_data
, /* data */
813 fish_symlink
, /* symlink */
814 fish_link
, /* link */
817 fish_rename
, /* rename */
827 NULL
, /* vfs_s_getlocalcopy, */
828 NULL
, /* vfs_s_ungetlocalcopy, */