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;
156 g_free (SUP
.password
);
160 pipeopen(vfs_s_super
*super
, char *path
, char *argv
[])
162 int fileset1
[2], fileset2
[2];
165 if ((pipe(fileset1
)<0) || (pipe(fileset2
)<0))
166 vfs_die("Could not pipe(): %m.");
168 if ((res
= fork())) {
169 if (res
<0) vfs_die("Could not fork(): %m.");
170 /* We are the parent */
172 SUP
.sockw
= fileset1
[1];
174 SUP
.sockr
= fileset2
[0];
178 close(fileset1
[0]); close(fileset1
[1]);
181 /* stderr to /dev/null */
182 open ("/dev/null", O_WRONLY
);
183 close(fileset2
[0]); close(fileset2
[1]);
189 /* The returned directory should always contain a trailing slash */
190 static char *fish_getcwd(vfs
*me
, vfs_s_super
*super
)
192 if (command(me
, super
, WANT_STRING
, "#PWD\npwd; echo '### 200'\n") == COMPLETE
)
193 return g_strconcat (reply_str
, "/", NULL
);
197 open_archive_int (vfs
*me
, vfs_s_super
*super
)
200 char *xsh
= (SUP
.flags
== FISH_FLAG_RSH
? "rsh" : "ssh");
204 #ifdef HAVE_HACKED_SSH
208 argv
[i
++] = SUP
.user
;
209 argv
[i
++] = SUP
.host
;
210 if (SUP
.flags
== FISH_FLAG_COMPRESSED
)
212 argv
[i
++] = "echo FISH:; /bin/sh";
217 if (!MEDATA
->logfile
)
218 MEDATA
->logfile
= fopen( "/home/pavel/talk.fish", "w+" ); /* FIXME */
221 pipeopen(super
, xsh
, argv
);
225 print_vfs_message( _("fish: Waiting for initial line...") );
226 if (!vfs_s_get_line(me
, SUP
.sockr
, answer
, sizeof(answer
), ':'))
227 ERRNOR (E_PROTO
, -1);
228 print_vfs_message( answer
);
229 if (strstr(answer
, "assword")) {
231 /* Currently, this does not work. ssh reads passwords from
232 /dev/tty, not from stdin :-(. */
234 #ifndef HAVE_HACKED_SSH
235 message_1s (1, MSG_ERROR
, _("Sorry, we can not do password authenticated connections for now."));
240 p
= g_strconcat (_(" fish: Password required for "), SUP
.user
,
242 op
= vfs_get_password (p
);
246 SUP
.password
= g_strdup (op
);
249 print_vfs_message( _("fish: Sending password...") );
250 write(SUP
.sockw
, SUP
.password
, strlen(SUP
.password
));
251 write(SUP
.sockw
, "\n", 1);
255 print_vfs_message( _("fish: Sending initial line...") );
257 * Run `start_fish_server'. If it doesn't exist - no problem,
258 * we'll talk directly to the shell.
260 if (command (me
, super
, WAIT_REPLY
,
261 "#FISH\necho; start_fish_server 2>&1;"
262 " echo '### 200'\n") != COMPLETE
)
263 ERRNOR (E_PROTO
, -1);
265 print_vfs_message( _("fish: Handshaking version...") );
266 if (command (me
, super
, WAIT_REPLY
, "#VER 0.0.0\necho '### 000'\n") != COMPLETE
)
267 ERRNOR (E_PROTO
, -1);
269 /* Set up remote locale to C, otherwise dates cannot be recognized */
270 if (command (me
, super
, WAIT_REPLY
, "LANG=C; LC_ALL=C; LC_TIME=C\n"
271 "export LANG; export LC_ALL; export LC_TIME\n"
272 "echo '### 200'\n") != COMPLETE
)
273 ERRNOR (E_PROTO
, -1);
275 print_vfs_message( _("fish: Setting up current directory...") );
276 SUP
.cwdir
= fish_getcwd (me
, super
);
277 print_vfs_message( _("fish: Connected, home %s."), SUP
.cwdir
);
279 super
->name
= g_strconcat ( "/#sh:", SUP
.user
, "@", SUP
.host
, "/", NULL
);
281 super
->name
= g_strdup(PATH_SEP_STR
);
283 super
->root
= vfs_s_new_inode (me
, super
, vfs_s_default_stat(me
, S_IFDIR
| 0755));
288 open_archive (vfs
*me
, vfs_s_super
*super
, char *archive_name
, char *op
)
290 char *host
, *user
, *password
, *p
;
293 p
= vfs_split_url (strchr(op
, ':')+1, &host
, &user
, &flags
, &password
, 0, URL_NOSLASH
);
301 if (!strncmp( op
, "rsh:", 4 ))
302 SUP
.flags
|= FISH_FLAG_RSH
;
305 SUP
.password
= password
;
306 return open_archive_int (me
, super
);
310 archive_same(vfs
*me
, vfs_s_super
*super
, char *archive_name
, char *op
, void *cookie
)
315 op
= vfs_split_url (strchr(op
, ':')+1, &host
, &user
, &flags
, 0, 0, URL_NOSLASH
);
320 flags
= ((strcmp (host
, SUP
.host
) == 0) &&
321 (strcmp (user
, SUP
.user
) == 0) &&
322 (flags
== SUP
.flags
));
330 fish_which (vfs
*me
, char *path
)
332 if (!strncmp (path
, "/#sh:", 5))
334 if (!strncmp (path
, "/#ssh:", 6))
336 if (!strncmp (path
, "/#rsh:", 6))
342 dir_uptodate(vfs
*me
, vfs_s_inode
*ino
)
346 gettimeofday(&tim
, NULL
);
347 if (force_expiration
) {
348 force_expiration
= 0;
351 if (tim
.tv_sec
< ino
->u
.fish
.timestamp
.tv_sec
)
357 dir_load(vfs
*me
, vfs_s_inode
*dir
, char *remote_path
)
359 vfs_s_super
*super
= dir
->super
;
361 vfs_s_entry
*ent
= NULL
;
364 logfile
= MEDATA
->logfile
;
366 print_vfs_message(_("fish: Reading directory %s..."), remote_path
);
368 gettimeofday(&dir
->u
.fish
.timestamp
, NULL
);
369 dir
->u
.fish
.timestamp
.tv_sec
+= 10; /* was 360: 10 is good for
370 stressing direntry layer a bit */
372 command(me
, super
, NONE
,
374 "ls -lLa \"/%s\" 2>/dev/null | grep '^[^cbt]' | (\n"
375 "while read p x u g s m d y n; do\n"
376 "echo \"P$p $u.$g\nS$s\nd$m $d $y\n:$n\n\"\n"
379 "ls -lLa \"/%s\" 2>/dev/null | grep '^[cb]' | (\n"
380 "while read p x u g a i m d y n; do\n"
381 "echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"\n"
385 remote_path
, remote_path
, remote_path
);
387 #define SIMPLE_ENTRY vfs_s_generate_entry(me, NULL, dir, 0)
390 int res
= vfs_s_get_line_interruptible (me
, buffer
, sizeof (buffer
), SUP
.sockr
);
391 if ((!res
) || (res
== EINTR
)) {
392 vfs_s_free_entry(me
, ent
);
393 me
->verrno
= ECONNRESET
;
397 fputs (buffer
, logfile
);
398 fputs ("\n", logfile
);
401 if (!strncmp(buffer
, "### ", 4))
405 vfs_s_insert_entry(me
, dir
, ent
);
411 #define ST ent->ino->st
416 if (!strcmp(buffer
+1, ".") || !strcmp(buffer
+1, ".."))
417 break; /* We'll do . and .. ourself */
418 ent
->name
= g_strdup(buffer
+1);
419 /* if ((c=strchr(ent->name, ' ')))
420 *c = 0; / * this is ugly, but we can not handle " " in name */
423 case 'S': ST
.st_size
= atoi(buffer
+1); break;
426 if ((i
= vfs_parse_filetype(buffer
[1])) ==-1)
429 if ((i
= vfs_parse_filemode(buffer
+2)) ==-1)
432 if (S_ISLNK(ST
.st_mode
))
437 vfs_split_text(buffer
+1);
438 if (!vfs_parse_filedate(0, &ST
.st_ctime
))
440 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
;
445 if (sscanf(buffer
+1, "%d %d %d %d %d %d", &tim
.tm_year
, &tim
.tm_mon
,
446 &tim
.tm_mday
, &tim
.tm_hour
, &tim
.tm_min
, &tim
.tm_sec
) != 6)
448 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
= mktime(&tim
);
453 if (sscanf(buffer
+1, "%d,%d", &maj
, &min
) != 2)
456 ST
.st_rdev
= (maj
<< 8) | min
;
459 case 'L': ent
->ino
->linkname
= g_strdup(buffer
+1);
464 vfs_s_free_entry (me
, ent
);
465 me
->verrno
= E_REMOTE
;
466 if (decode_reply(buffer
+4, 0) == COMPLETE
) {
468 SUP
.cwdir
= g_strdup (remote_path
);
469 print_vfs_message (_("%s: done."), me
->name
);
474 print_vfs_message (_("%s: failure"), me
->name
);
479 file_store(vfs
*me
, vfs_s_fh
*fh
, char *name
, char *localname
)
481 vfs_s_super
*super
= FH_SUPER
;
488 h
= open(localname
, O_RDONLY
);
493 /* Use this as stor: ( dd block ; dd smallblock ) | ( cat > file; cat > /dev/null ) */
495 print_vfs_message(_("fish: store %s: sending command..."), name
);
497 * FIXME: Limit size to unsigned long for now.
498 * Files longer than 256 * ULONG_MAX are not supported.
500 if (!fh
->u
.fish
.append
)
501 n
= command (me
, super
, WAIT_REPLY
,
506 "dd ibs=256 obs=4096 count=%lu\n"
507 "dd bs=%lu count=1\n"
508 ") 2>/dev/null | (\n"
511 "); echo '### 200'\n",
512 (unsigned long) s
.st_size
, name
, name
,
513 (unsigned long) (s
.st_size
>> 8),
514 ((unsigned long) s
.st_size
) & (256 - 1), name
);
516 n
= command (me
, super
, WAIT_REPLY
,
520 "dd ibs=256 obs=4096 count=%lu\n"
521 "dd bs=%lu count=1\n"
522 ") 2>/dev/null | (\n"
525 "); echo '### 200'\n",
526 (unsigned long) s
.st_size
, name
,
527 (unsigned long) (s
.st_size
>> 8),
528 ((unsigned long) s
.st_size
) & (256 - 1), name
);
531 ERRNOR(E_REMOTE
, -1);
536 while ((n
= read(h
, buffer
, sizeof(buffer
))) < 0) {
537 if ((errno
== EINTR
) && got_interrupt
)
539 print_vfs_message(_("fish: Local read failed, sending zeros") );
541 h
= open( "/dev/zero", O_RDONLY
);
545 while (write(SUP
.sockw
, buffer
, n
) < 0) {
549 disable_interrupt_key();
551 print_vfs_message(_("fish: storing %s %d (%lu)"),
552 was_error
? _("zeros") : _("file"), total
,
553 (unsigned long) s
.st_size
);
555 if ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
) || was_error
)
556 ERRNOR (E_REMOTE
, -1);
561 get_reply(me
, SUP
.sockr
, NULL
, 0);
565 static int linear_start(vfs
*me
, vfs_s_fh
*fh
, int offset
)
569 ERRNOR (E_NOTSUPP
, 0);
570 /* fe->local_stat.st_mtime = 0; FIXME: what is this good for? */
571 name
= vfs_s_fullpath (me
, fh
->ino
);
574 fh
->u
.fish
.append
= 0;
575 offset
= command(me
, FH_SUPER
, WANT_STRING
,
577 "ls -l \"/%s\" 2>/dev/null | (\n"
578 "read var1 var2 var3 var4 var5 var6\n"
586 if (offset
!= PRELIM
) ERRNOR (E_REMOTE
, 0);
587 fh
->linear
= LS_LINEAR_OPEN
;
589 if (sscanf( reply_str
, "%d", &fh
->u
.fish
.total
)!=1)
590 ERRNOR (E_REMOTE
, 0);
595 linear_abort (vfs
*me
, vfs_s_fh
*fh
)
597 vfs_s_super
*super
= FH_SUPER
;
601 print_vfs_message( _("Aborting transfer...") );
603 n
= MIN(8192, fh
->u
.fish
.total
- fh
->u
.fish
.got
);
605 if ((n
= read(SUP
.sockr
, buffer
, n
)) < 0)
609 if (get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)
610 print_vfs_message( _("Error reported after abort.") );
612 print_vfs_message( _("Aborted transfer would be successful.") );
616 linear_read (vfs
*me
, vfs_s_fh
*fh
, void *buf
, int len
)
618 vfs_s_super
*super
= FH_SUPER
;
620 len
= MIN( fh
->u
.fish
.total
- fh
->u
.fish
.got
, len
);
621 disable_interrupt_key();
622 while (len
&& ((n
= read (SUP
.sockr
, buf
, len
))<0)) {
623 if ((errno
== EINTR
) && !got_interrupt())
627 enable_interrupt_key();
629 if (n
>0) fh
->u
.fish
.got
+= n
;
630 if (n
<0) linear_abort(me
, fh
);
631 if ((!n
) && ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)))
632 ERRNOR (E_REMOTE
, -1);
637 linear_close (vfs
*me
, vfs_s_fh
*fh
)
639 if (fh
->u
.fish
.total
!= fh
->u
.fish
.got
)
640 linear_abort(me
, fh
);
641 else if (stat (fh
->ino
->localname
, &fh
->ino
->u
.fish
.local_stat
) < 0)
642 fh
->ino
->u
.fish
.local_stat
.st_mtime
= 0;
646 fish_ctl (void *fh
, int ctlop
, int arg
)
650 case MCCTL_IS_NOTREADY
:
655 vfs_die ("You may not do this");
656 if (FH
->linear
== LS_LINEAR_CLOSED
)
659 v
= vfs_s_select_on_two (FH_SUPER
->u
.fish
.sockr
, 0);
660 if (((v
< 0) && (errno
== EINTR
)) || v
== 0)
670 send_fish_command(vfs
*me
, vfs_s_super
*super
, char *cmd
, int flags
)
674 r
= command (me
, super
, WAIT_REPLY
, cmd
);
675 vfs_add_noncurrent_stamps (&vfs_fish_ops
, (vfsid
) super
, NULL
);
676 if (r
!= COMPLETE
) ERRNOR (E_REMOTE
, -1);
677 if (flags
& OPT_FLUSH
)
678 vfs_s_invalidate(me
, super
);
683 char buf[BUF_LARGE]; \
685 vfs_s_super *super; \
686 if (!(rpath = vfs_s_get_path_mangle(me, path, &super, 0))) \
689 #define POSTFIX(flags) \
690 return send_fish_command(me, super, buf, flags);
693 fish_chmod (vfs
*me
, char *path
, int mode
)
696 g_snprintf(buf
, sizeof(buf
), "#CHMOD %4.4o /%s\n"
697 "chmod %4.4o \"/%s\" 2>/dev/null\n"
700 mode
& 07777, rpath
);
704 #define FISH_OP(name, chk, string) \
705 static int fish_##name (vfs *me, char *path1, char *path2) \
707 char buf[BUF_LARGE]; \
708 char *rpath1 = NULL, *rpath2 = NULL; \
709 vfs_s_super *super1, *super2; \
710 if (!(rpath1 = vfs_s_get_path_mangle(me, path1, &super1, 0))) \
712 if (!(rpath2 = vfs_s_get_path_mangle(me, path2, &super2, 0))) \
714 g_snprintf(buf, 1023, string "\n", rpath1, rpath2, rpath1, rpath2 ); \
715 return send_fish_command(me, super2, buf, OPT_FLUSH); \
718 #define XTEST if (bucket1 != bucket2) { ERRNOR (EXDEV, -1); }
719 FISH_OP(rename
, XTEST
, "#RENAME /%s /%s\n"
720 "mv \"/%s\" \"/%s\" 2>/dev/null\n"
722 FISH_OP(link
, XTEST
, "#LINK /%s /%s\n"
723 "ln \"/%s\" \"/%s\" 2>/dev/null\n"
726 static int fish_symlink (vfs
*me
, char *setto
, char *path
)
729 g_snprintf(buf
, sizeof(buf
),
731 "ln -s \"%s\" \"/%s\" 2>/dev/null\n"
733 setto
, rpath
, setto
, rpath
);
738 fish_chown (vfs
*me
, char *path
, int owner
, int group
)
740 char *sowner
, *sgroup
;
745 if ((pw
= getpwuid (owner
)) == NULL
)
748 if ((gr
= getgrgid (group
)) == NULL
)
751 sowner
= pw
->pw_name
;
752 sgroup
= gr
->gr_name
;
753 g_snprintf(buf
, sizeof(buf
),
755 "chown %s \"/%s\" 2>/dev/null\n"
759 send_fish_command(me
, super
, buf
, OPT_FLUSH
);
760 /* FIXME: what should we report if chgrp succeeds but chown fails? */
761 g_snprintf(buf
, sizeof(buf
),
763 "chgrp %s \"/%s\" 2>/dev/null\n"
767 /* send_fish_command(me, super, buf, OPT_FLUSH); */
771 static int fish_unlink (vfs
*me
, char *path
)
774 g_snprintf(buf
, sizeof(buf
),
776 "rm -f \"/%s\" 2>/dev/null\n"
782 static int fish_mkdir (vfs
*me
, char *path
, mode_t mode
)
785 g_snprintf(buf
, sizeof(buf
),
787 "mkdir \"/%s\" 2>/dev/null\n"
793 static int fish_rmdir (vfs
*me
, char *path
)
796 g_snprintf(buf
, sizeof(buf
),
798 "rmdir \"/%s\" 2>/dev/null\n"
804 static int fish_fh_open (vfs
*me
, vfs_s_fh
*fh
, int flags
, int mode
)
806 fh
->u
.fish
.append
= 0;
807 /* File will be written only, so no need to retrieve it */
808 if (((flags
& O_WRONLY
) == O_WRONLY
) && !(flags
& (O_RDONLY
|O_RDWR
))){
809 fh
->u
.fish
.append
= flags
& O_APPEND
;
810 if (!fh
->ino
->localname
){
811 int tmp_handle
= mc_mkstemps (&fh
->ino
->localname
, me
->name
, NULL
);
812 if (tmp_handle
== -1)
818 if (!fh
->ino
->localname
)
819 if (vfs_s_retrieve_file (me
, fh
->ino
)==-1)
821 if (!fh
->ino
->localname
)
822 vfs_die( "retrieve_file failed to fill in localname" );
826 static struct vfs_s_data fish_data
= {
832 NULL
, /* init_inode */
833 NULL
, /* free_inode */
834 NULL
, /* init_entry */
836 NULL
, /* archive_check */
841 fish_fh_open
, /* fh_open */
844 vfs_s_find_entry_linear
,
855 fish_fill_names (vfs
*me
, void (*func
)(char *))
857 struct vfs_s_super
* super
= fish_data
.supers
;
862 switch (SUP
.flags
& (FISH_FLAG_RSH
| FISH_FLAG_COMPRESSED
)) {
866 case FISH_FLAG_COMPRESSED
:
869 case FISH_FLAG_RSH
| FISH_FLAG_COMPRESSED
:
877 name
= g_strconcat ("/#sh:", SUP
.user
, "@", SUP
.host
, flags
,
878 "/", SUP
.cwdir
, NULL
);
886 NULL
, /* This is place of next pointer */
890 &fish_data
, /* data */
917 fish_symlink
, /* symlink */
918 fish_link
, /* link */
921 fish_rename
, /* rename */
931 NULL
, /* vfs_s_getlocalcopy, */
932 NULL
, /* vfs_s_ungetlocalcopy, */