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
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Library General Public License
13 as published by the Free Software Foundation; either version 2 of
14 the License, or (at your option) any later version.
16 This program 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 Library General Public License for more details.
21 You should have received a copy of the GNU Library General Public
22 License along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
26 * Read README.fish for protocol specification.
28 * Syntax of path is: /#sh:user@host[:Cr]/path
29 * where C means you want compressed connection,
30 * and r means you want to use rsh
32 * Namespace: fish_vfs_ops exported.
35 /* Define this if your ssh can take -I option */
39 #undef HAVE_HACKED_SSH
42 #include "../src/dialog.h" /* For MSG_ERROR */
44 #include "xdirentry.h"
52 #define PRELIM 1 /* positive preliminary */
53 #define COMPLETE 2 /* positive completion */
54 #define CONTINUE 3 /* positive intermediate */
55 #define TRANSIENT 4 /* transient negative completion */
56 #define ERROR 5 /* permanent negative completion */
58 /* If true, the directory cache is forced to reload */
59 static int force_expiration
= 0;
61 /* FIXME: prev two variables should be killed */
63 /* command wait_flag: */
65 #define WAIT_REPLY 0x01
66 #define WANT_STRING 0x02
67 static char reply_str
[80];
70 command (vfs
*me
, vfs_s_super
*super
, int wait_reply
, const char *fmt
, ...)
71 __attribute__ ((format (printf
, 4, 5)));
73 static int decode_reply (char *s
, int was_garbage
)
76 if (!sscanf(s
, "%d", &code
)) {
80 if (code
<100) return was_garbage
? ERROR
: (!code
? COMPLETE
: PRELIM
);
84 /* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
85 static int get_reply (vfs
*me
, int sock
, char *string_buf
, int string_len
)
91 if (!vfs_s_get_line(me
, sock
, answer
, sizeof(answer
), '\n')) {
96 if (strncmp(answer
, "### ", 4)) {
99 strncpy(string_buf
, answer
, string_len
- 1);
100 *(string_buf
+ string_len
- 1) = 0;
102 } else return decode_reply(answer
+4, was_garbage
);
106 #define SUP super->u.fish
109 command (vfs
*me
, vfs_s_super
*super
, int wait_reply
, const char *fmt
, ...)
114 FILE *logfile
= MEDATA
->logfile
;
118 str
= g_strdup_vprintf (fmt
, ap
);
122 fwrite (str
, strlen (str
), 1, logfile
);
126 enable_interrupt_key();
128 status
= write(SUP
.sockw
, str
, strlen(str
));
131 disable_interrupt_key();
136 return get_reply (me
, SUP
.sockr
, (wait_reply
& WANT_STRING
) ? reply_str
: NULL
, sizeof (reply_str
)-1);
141 free_archive (vfs
*me
, vfs_s_super
*super
)
143 if ((SUP
.sockw
!= -1) || (SUP
.sockr
!= -1)){
144 print_vfs_message (_("fish: Disconnecting from %s"), super
->name
?super
->name
:"???");
145 command(me
, super
, NONE
, "#BYE\nexit\n");
148 SUP
.sockw
= SUP
.sockr
= -1;
153 g_free (SUP
.password
);
157 pipeopen(vfs_s_super
*super
, char *path
, char *argv
[])
159 int fileset1
[2], fileset2
[2];
162 if ((pipe(fileset1
)<0) || (pipe(fileset2
)<0))
163 vfs_die("Cannot pipe(): %m.");
165 if ((res
= fork())) {
166 if (res
<0) vfs_die("Cannot fork(): %m.");
167 /* We are the parent */
169 SUP
.sockw
= fileset1
[1];
171 SUP
.sockr
= fileset2
[0];
175 close(fileset1
[0]); close(fileset1
[1]);
178 /* stderr to /dev/null */
179 open ("/dev/null", O_WRONLY
);
180 close(fileset2
[0]); close(fileset2
[1]);
186 /* The returned directory should always contain a trailing slash */
187 static char *fish_getcwd(vfs
*me
, vfs_s_super
*super
)
189 if (command(me
, super
, WANT_STRING
, "#PWD\npwd; echo '### 200'\n") == COMPLETE
)
190 return g_strconcat (reply_str
, "/", NULL
);
194 open_archive_int (vfs
*me
, vfs_s_super
*super
)
197 char *xsh
= (SUP
.flags
== FISH_FLAG_RSH
? "rsh" : "ssh");
201 #ifdef HAVE_HACKED_SSH
205 argv
[i
++] = SUP
.user
;
206 argv
[i
++] = SUP
.host
;
207 if (SUP
.flags
== FISH_FLAG_COMPRESSED
)
209 argv
[i
++] = "echo FISH:; /bin/sh";
214 if (!MEDATA
->logfile
)
215 MEDATA
->logfile
= fopen( "/home/pavel/talk.fish", "w+" ); /* FIXME */
218 pipeopen(super
, xsh
, argv
);
222 print_vfs_message( _("fish: Waiting for initial line...") );
223 if (!vfs_s_get_line(me
, SUP
.sockr
, answer
, sizeof(answer
), ':'))
224 ERRNOR (E_PROTO
, -1);
225 print_vfs_message( answer
);
226 if (strstr(answer
, "assword")) {
228 /* Currently, this does not work. ssh reads passwords from
229 /dev/tty, not from stdin :-(. */
231 #ifndef HAVE_HACKED_SSH
232 message_1s (1, MSG_ERROR
, _("Sorry, we cannot do password authenticated connections for now."));
237 p
= g_strconcat (_(" fish: Password required for "), SUP
.user
,
239 op
= vfs_get_password (p
);
243 SUP
.password
= g_strdup (op
);
246 print_vfs_message( _("fish: Sending password...") );
247 write(SUP
.sockw
, SUP
.password
, strlen(SUP
.password
));
248 write(SUP
.sockw
, "\n", 1);
252 print_vfs_message( _("fish: Sending initial line...") );
254 * Run `start_fish_server'. If it doesn't exist - no problem,
255 * we'll talk directly to the shell.
257 if (command (me
, super
, WAIT_REPLY
,
258 "#FISH\necho; start_fish_server 2>&1;"
259 " echo '### 200'\n") != COMPLETE
)
260 ERRNOR (E_PROTO
, -1);
262 print_vfs_message( _("fish: Handshaking version...") );
263 if (command (me
, super
, WAIT_REPLY
, "#VER 0.0.0\necho '### 000'\n") != COMPLETE
)
264 ERRNOR (E_PROTO
, -1);
266 /* Set up remote locale to C, otherwise dates cannot be recognized */
267 if (command (me
, super
, WAIT_REPLY
, "LANG=C; LC_ALL=C; LC_TIME=C\n"
268 "export LANG; export LC_ALL; export LC_TIME\n"
269 "echo '### 200'\n") != COMPLETE
)
270 ERRNOR (E_PROTO
, -1);
272 print_vfs_message( _("fish: Setting up current directory...") );
273 SUP
.cwdir
= fish_getcwd (me
, super
);
274 print_vfs_message( _("fish: Connected, home %s."), SUP
.cwdir
);
276 super
->name
= g_strconcat ( "/#sh:", SUP
.user
, "@", SUP
.host
, "/", NULL
);
278 super
->name
= g_strdup(PATH_SEP_STR
);
280 super
->root
= vfs_s_new_inode (me
, super
, vfs_s_default_stat(me
, S_IFDIR
| 0755));
285 open_archive (vfs
*me
, vfs_s_super
*super
, char *archive_name
, char *op
)
287 char *host
, *user
, *password
, *p
;
290 p
= vfs_split_url (strchr(op
, ':')+1, &host
, &user
, &flags
, &password
, 0, URL_NOSLASH
);
298 if (!strncmp( op
, "rsh:", 4 ))
299 SUP
.flags
|= FISH_FLAG_RSH
;
302 SUP
.password
= password
;
303 return open_archive_int (me
, super
);
307 archive_same(vfs
*me
, vfs_s_super
*super
, char *archive_name
, char *op
, void *cookie
)
312 op
= vfs_split_url (strchr(op
, ':')+1, &host
, &user
, &flags
, 0, 0, URL_NOSLASH
);
317 flags
= ((strcmp (host
, SUP
.host
) == 0) &&
318 (strcmp (user
, SUP
.user
) == 0) &&
319 (flags
== SUP
.flags
));
327 dir_uptodate(vfs
*me
, vfs_s_inode
*ino
)
331 gettimeofday(&tim
, NULL
);
332 if (force_expiration
) {
333 force_expiration
= 0;
336 if (tim
.tv_sec
< ino
->u
.fish
.timestamp
.tv_sec
)
342 dir_load(vfs
*me
, vfs_s_inode
*dir
, char *remote_path
)
344 vfs_s_super
*super
= dir
->super
;
346 vfs_s_entry
*ent
= NULL
;
350 logfile
= MEDATA
->logfile
;
352 print_vfs_message(_("fish: Reading directory %s..."), remote_path
);
354 gettimeofday(&dir
->u
.fish
.timestamp
, NULL
);
355 dir
->u
.fish
.timestamp
.tv_sec
+= 10; /* was 360: 10 is good for
356 stressing direntry layer a bit */
357 quoted_path
= name_quote (remote_path
, 0);
358 command(me
, super
, NONE
,
360 "ls -lLa /%s 2>/dev/null | grep '^[^cbt]' | (\n"
361 "while read p x u g s m d y n; do\n"
362 "echo \"P$p $u.$g\nS$s\nd$m $d $y\n:$n\n\"\n"
365 "ls -lLa /%s 2>/dev/null | grep '^[cb]' | (\n"
366 "while read p x u g a i m d y n; do\n"
367 "echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"\n"
371 remote_path
, quoted_path
, quoted_path
);
372 g_free (quoted_path
);
373 #define SIMPLE_ENTRY vfs_s_generate_entry(me, NULL, dir, 0)
376 int res
= vfs_s_get_line_interruptible (me
, buffer
, sizeof (buffer
), SUP
.sockr
);
377 if ((!res
) || (res
== EINTR
)) {
378 vfs_s_free_entry(me
, ent
);
379 me
->verrno
= ECONNRESET
;
383 fputs (buffer
, logfile
);
384 fputs ("\n", logfile
);
387 if (!strncmp(buffer
, "### ", 4))
391 vfs_s_insert_entry(me
, dir
, ent
);
397 #define ST ent->ino->st
402 if (!strcmp(buffer
+1, ".") || !strcmp(buffer
+1, ".."))
403 break; /* We'll do . and .. ourself */
404 ent
->name
= g_strdup(buffer
+1);
405 /* if ((c=strchr(ent->name, ' ')))
406 *c = 0; / * this is ugly, but we cannot handle " " in name */
409 case 'S': ST
.st_size
= atoi(buffer
+1); break;
412 if ((i
= vfs_parse_filetype(buffer
[1])) ==-1)
415 if ((i
= vfs_parse_filemode(buffer
+2)) ==-1)
418 if (S_ISLNK(ST
.st_mode
))
423 vfs_split_text(buffer
+1);
424 if (!vfs_parse_filedate(0, &ST
.st_ctime
))
426 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
;
431 if (sscanf(buffer
+1, "%d %d %d %d %d %d", &tim
.tm_year
, &tim
.tm_mon
,
432 &tim
.tm_mday
, &tim
.tm_hour
, &tim
.tm_min
, &tim
.tm_sec
) != 6)
434 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
= mktime(&tim
);
439 if (sscanf(buffer
+1, "%d,%d", &maj
, &min
) != 2)
442 ST
.st_rdev
= (maj
<< 8) | min
;
445 case 'L': ent
->ino
->linkname
= g_strdup(buffer
+1);
450 vfs_s_free_entry (me
, ent
);
451 me
->verrno
= E_REMOTE
;
452 if (decode_reply(buffer
+4, 0) == COMPLETE
) {
454 SUP
.cwdir
= g_strdup (remote_path
);
455 print_vfs_message (_("%s: done."), me
->name
);
460 print_vfs_message (_("%s: failure"), me
->name
);
465 file_store(vfs
*me
, vfs_s_fh
*fh
, char *name
, char *localname
)
467 vfs_s_super
*super
= FH_SUPER
;
475 h
= open (localname
, O_RDONLY
);
479 if (fstat(h
, &s
)<0) {
483 /* Use this as stor: ( dd block ; dd smallblock ) | ( cat > file; cat > /dev/null ) */
485 print_vfs_message(_("fish: store %s: sending command..."), name
);
486 quoted_name
= name_quote (name
, 0);
488 * FIXME: Limit size to unsigned long for now.
489 * Files longer than 256 * ULONG_MAX are not supported.
491 if (!fh
->u
.fish
.append
)
492 n
= command (me
, super
, WAIT_REPLY
,
497 "dd ibs=256 obs=4096 count=%lu\n"
498 "dd bs=%lu count=1\n"
499 ") 2>/dev/null | (\n"
502 "); echo '### 200'\n",
503 (unsigned long) s
.st_size
, name
, quoted_name
,
504 (unsigned long) (s
.st_size
>> 8),
505 ((unsigned long) s
.st_size
) & (256 - 1), quoted_name
);
507 n
= command (me
, super
, WAIT_REPLY
,
511 "dd ibs=256 obs=4096 count=%lu\n"
512 "dd bs=%lu count=1\n"
513 ") 2>/dev/null | (\n"
516 "); echo '### 200'\n",
517 (unsigned long) s
.st_size
, name
,
518 (unsigned long) (s
.st_size
>> 8),
519 ((unsigned long) s
.st_size
) & (256 - 1), quoted_name
);
521 g_free (quoted_name
);
524 ERRNOR(E_REMOTE
, -1);
529 while ((n
= read(h
, buffer
, sizeof(buffer
))) < 0) {
530 if ((errno
== EINTR
) && got_interrupt
)
532 print_vfs_message(_("fish: Local read failed, sending zeros") );
534 h
= open( "/dev/zero", O_RDONLY
);
538 while (write(SUP
.sockw
, buffer
, n
) < 0) {
542 disable_interrupt_key();
544 print_vfs_message(_("fish: storing %s %d (%lu)"),
545 was_error
? _("zeros") : _("file"), total
,
546 (unsigned long) s
.st_size
);
549 if ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
) || was_error
)
550 ERRNOR (E_REMOTE
, -1);
554 get_reply(me
, SUP
.sockr
, NULL
, 0);
558 static int linear_start(vfs
*me
, vfs_s_fh
*fh
, int offset
)
563 ERRNOR (E_NOTSUPP
, 0);
564 /* fe->local_stat.st_mtime = 0; FIXME: what is this good for? */
565 name
= vfs_s_fullpath (me
, fh
->ino
);
568 quoted_name
= name_quote (name
, 0);
571 fh
->u
.fish
.append
= 0;
572 offset
= command(me
, FH_SUPER
, WANT_STRING
,
574 "ls -l /%s 2>/dev/null | (\n"
575 "read var1 var2 var3 var4 var5 var6\n"
583 if (offset
!= PRELIM
) ERRNOR (E_REMOTE
, 0);
584 fh
->linear
= LS_LINEAR_OPEN
;
586 if (sscanf( reply_str
, "%d", &fh
->u
.fish
.total
)!=1)
587 ERRNOR (E_REMOTE
, 0);
592 linear_abort (vfs
*me
, vfs_s_fh
*fh
)
594 vfs_s_super
*super
= FH_SUPER
;
598 print_vfs_message( _("Aborting transfer...") );
600 n
= MIN(8192, fh
->u
.fish
.total
- fh
->u
.fish
.got
);
602 if ((n
= read(SUP
.sockr
, buffer
, n
)) < 0)
606 if (get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)
607 print_vfs_message( _("Error reported after abort.") );
609 print_vfs_message( _("Aborted transfer would be successful.") );
613 linear_read (vfs
*me
, vfs_s_fh
*fh
, void *buf
, int len
)
615 vfs_s_super
*super
= FH_SUPER
;
617 len
= MIN( fh
->u
.fish
.total
- fh
->u
.fish
.got
, len
);
618 disable_interrupt_key();
619 while (len
&& ((n
= read (SUP
.sockr
, buf
, len
))<0)) {
620 if ((errno
== EINTR
) && !got_interrupt())
624 enable_interrupt_key();
626 if (n
>0) fh
->u
.fish
.got
+= n
;
627 if (n
<0) linear_abort(me
, fh
);
628 if ((!n
) && ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)))
629 ERRNOR (E_REMOTE
, -1);
634 linear_close (vfs
*me
, vfs_s_fh
*fh
)
636 if (fh
->u
.fish
.total
!= fh
->u
.fish
.got
)
637 linear_abort(me
, fh
);
638 else if (stat (fh
->ino
->localname
, &fh
->ino
->u
.fish
.local_stat
) < 0)
639 fh
->ino
->u
.fish
.local_stat
.st_mtime
= 0;
643 fish_ctl (void *fh
, int ctlop
, int arg
)
647 case MCCTL_IS_NOTREADY
:
652 vfs_die ("You may not do this");
653 if (FH
->linear
== LS_LINEAR_CLOSED
)
656 v
= vfs_s_select_on_two (FH_SUPER
->u
.fish
.sockr
, 0);
657 if (((v
< 0) && (errno
== EINTR
)) || v
== 0)
667 send_fish_command(vfs
*me
, vfs_s_super
*super
, char *cmd
, int flags
)
671 r
= command (me
, super
, WAIT_REPLY
, cmd
);
672 vfs_add_noncurrent_stamps (&vfs_fish_ops
, (vfsid
) super
, NULL
);
673 if (r
!= COMPLETE
) ERRNOR (E_REMOTE
, -1);
674 if (flags
& OPT_FLUSH
)
675 vfs_s_invalidate(me
, super
);
680 char buf[BUF_LARGE]; \
682 vfs_s_super *super; \
683 if (!(rpath = vfs_s_get_path_mangle(me, path, &super, 0))) \
685 rpath = name_quote (rpath, 0);
687 #define POSTFIX(flags) \
689 return send_fish_command(me, super, buf, flags);
692 fish_chmod (vfs
*me
, char *path
, int mode
)
695 g_snprintf(buf
, sizeof(buf
), "#CHMOD %4.4o /%s\n"
696 "chmod %4.4o \"/%s\" 2>/dev/null\n"
699 mode
& 07777, rpath
);
703 #define FISH_OP(name, chk, string) \
704 static int fish_##name (vfs *me, char *path1, char *path2) \
706 char buf[BUF_LARGE]; \
707 char *rpath1, *rpath2; \
708 vfs_s_super *super1, *super2; \
709 if (!(rpath1 = vfs_s_get_path_mangle(me, path1, &super1, 0))) \
711 if (!(rpath2 = vfs_s_get_path_mangle(me, path2, &super2, 0))) \
713 rpath1 = name_quote (rpath1, 0); \
714 rpath2 = name_quote (rpath2, 0); \
715 g_snprintf(buf, sizeof(buf), string "\n", rpath1, rpath2, rpath1, rpath2); \
718 return send_fish_command(me, super2, buf, OPT_FLUSH); \
721 #define XTEST if (bucket1 != bucket2) { ERRNOR (EXDEV, -1); }
722 FISH_OP(rename
, XTEST
, "#RENAME /%s /%s\n"
723 "mv /%s /%s 2>/dev/null\n"
725 FISH_OP(link
, XTEST
, "#LINK /%s /%s\n"
726 "ln /%s /%s 2>/dev/null\n"
729 static int fish_symlink (vfs
*me
, char *setto
, char *path
)
732 setto
= name_quote (setto
, 0);
733 g_snprintf(buf
, sizeof(buf
),
735 "ln -s %s /%s 2>/dev/null\n"
737 setto
, rpath
, setto
, rpath
);
743 fish_chown (vfs
*me
, char *path
, int owner
, int group
)
745 char *sowner
, *sgroup
;
750 if ((pw
= getpwuid (owner
)) == NULL
)
753 if ((gr
= getgrgid (group
)) == NULL
)
756 sowner
= pw
->pw_name
;
757 sgroup
= gr
->gr_name
;
758 g_snprintf(buf
, sizeof(buf
),
760 "chown %s /%s 2>/dev/null\n"
764 send_fish_command(me
, super
, buf
, OPT_FLUSH
);
765 /* FIXME: what should we report if chgrp succeeds but chown fails? */
766 g_snprintf(buf
, sizeof(buf
),
768 "chgrp %s /%s 2>/dev/null\n"
772 /* send_fish_command(me, super, buf, OPT_FLUSH); */
776 static int fish_unlink (vfs
*me
, char *path
)
779 g_snprintf(buf
, sizeof(buf
),
781 "rm -f /%s 2>/dev/null\n"
787 static int fish_mkdir (vfs
*me
, char *path
, mode_t mode
)
790 g_snprintf(buf
, sizeof(buf
),
792 "mkdir /%s 2>/dev/null\n"
798 static int fish_rmdir (vfs
*me
, char *path
)
801 g_snprintf(buf
, sizeof(buf
),
803 "rmdir /%s 2>/dev/null\n"
809 static int fish_fh_open (vfs
*me
, vfs_s_fh
*fh
, int flags
, int mode
)
811 fh
->u
.fish
.append
= 0;
812 /* File will be written only, so no need to retrieve it */
813 if (((flags
& O_WRONLY
) == O_WRONLY
) && !(flags
& (O_RDONLY
|O_RDWR
))){
814 fh
->u
.fish
.append
= flags
& O_APPEND
;
815 if (!fh
->ino
->localname
){
816 int tmp_handle
= mc_mkstemps (&fh
->ino
->localname
, me
->name
, NULL
);
817 if (tmp_handle
== -1)
823 if (!fh
->ino
->localname
)
824 if (vfs_s_retrieve_file (me
, fh
->ino
)==-1)
826 if (!fh
->ino
->localname
)
827 vfs_die( "retrieve_file failed to fill in localname" );
831 static struct vfs_s_data fish_data
= {
837 NULL
, /* init_inode */
838 NULL
, /* free_inode */
839 NULL
, /* init_entry */
841 NULL
, /* archive_check */
846 fish_fh_open
, /* fh_open */
849 vfs_s_find_entry_linear
,
860 fish_fill_names (vfs
*me
, void (*func
)(char *))
862 struct vfs_s_super
* super
= fish_data
.supers
;
867 switch (SUP
.flags
& (FISH_FLAG_RSH
| FISH_FLAG_COMPRESSED
)) {
871 case FISH_FLAG_COMPRESSED
:
874 case FISH_FLAG_RSH
| FISH_FLAG_COMPRESSED
:
882 name
= g_strconcat ("/#sh:", SUP
.user
, "@", SUP
.host
, flags
,
883 "/", SUP
.cwdir
, NULL
);
891 NULL
, /* This is place of next pointer */
895 &fish_data
, /* data */
922 fish_symlink
, /* symlink */
923 fish_link
, /* link */
926 fish_rename
, /* rename */
936 NULL
, /* vfs_s_getlocalcopy, */
937 NULL
, /* vfs_s_ungetlocalcopy, */