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("Could not pipe(): %m.");
165 if ((res
= fork())) {
166 if (res
<0) vfs_die("Could not 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 can not 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 fish_which (vfs
*me
, char *path
)
329 if (!strncmp (path
, "/#sh:", 5))
331 if (!strncmp (path
, "/#ssh:", 6))
333 if (!strncmp (path
, "/#rsh:", 6))
339 dir_uptodate(vfs
*me
, vfs_s_inode
*ino
)
343 gettimeofday(&tim
, NULL
);
344 if (force_expiration
) {
345 force_expiration
= 0;
348 if (tim
.tv_sec
< ino
->u
.fish
.timestamp
.tv_sec
)
354 dir_load(vfs
*me
, vfs_s_inode
*dir
, char *remote_path
)
356 vfs_s_super
*super
= dir
->super
;
358 vfs_s_entry
*ent
= NULL
;
362 logfile
= MEDATA
->logfile
;
364 print_vfs_message(_("fish: Reading directory %s..."), remote_path
);
366 gettimeofday(&dir
->u
.fish
.timestamp
, NULL
);
367 dir
->u
.fish
.timestamp
.tv_sec
+= 10; /* was 360: 10 is good for
368 stressing direntry layer a bit */
369 quoted_path
= name_quote (remote_path
, 0);
370 command(me
, super
, NONE
,
372 "ls -lLa /%s 2>/dev/null | grep '^[^cbt]' | (\n"
373 "while read p x u g s m d y n; do\n"
374 "echo \"P$p $u.$g\nS$s\nd$m $d $y\n:$n\n\"\n"
377 "ls -lLa /%s 2>/dev/null | grep '^[cb]' | (\n"
378 "while read p x u g a i m d y n; do\n"
379 "echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"\n"
383 remote_path
, quoted_path
, quoted_path
);
384 g_free (quoted_path
);
385 #define SIMPLE_ENTRY vfs_s_generate_entry(me, NULL, dir, 0)
388 int res
= vfs_s_get_line_interruptible (me
, buffer
, sizeof (buffer
), SUP
.sockr
);
389 if ((!res
) || (res
== EINTR
)) {
390 vfs_s_free_entry(me
, ent
);
391 me
->verrno
= ECONNRESET
;
395 fputs (buffer
, logfile
);
396 fputs ("\n", logfile
);
399 if (!strncmp(buffer
, "### ", 4))
403 vfs_s_insert_entry(me
, dir
, ent
);
409 #define ST ent->ino->st
414 if (!strcmp(buffer
+1, ".") || !strcmp(buffer
+1, ".."))
415 break; /* We'll do . and .. ourself */
416 ent
->name
= g_strdup(buffer
+1);
417 /* if ((c=strchr(ent->name, ' ')))
418 *c = 0; / * this is ugly, but we can not handle " " in name */
421 case 'S': ST
.st_size
= atoi(buffer
+1); break;
424 if ((i
= vfs_parse_filetype(buffer
[1])) ==-1)
427 if ((i
= vfs_parse_filemode(buffer
+2)) ==-1)
430 if (S_ISLNK(ST
.st_mode
))
435 vfs_split_text(buffer
+1);
436 if (!vfs_parse_filedate(0, &ST
.st_ctime
))
438 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
;
443 if (sscanf(buffer
+1, "%d %d %d %d %d %d", &tim
.tm_year
, &tim
.tm_mon
,
444 &tim
.tm_mday
, &tim
.tm_hour
, &tim
.tm_min
, &tim
.tm_sec
) != 6)
446 ST
.st_atime
= ST
.st_mtime
= ST
.st_ctime
= mktime(&tim
);
451 if (sscanf(buffer
+1, "%d,%d", &maj
, &min
) != 2)
454 ST
.st_rdev
= (maj
<< 8) | min
;
457 case 'L': ent
->ino
->linkname
= g_strdup(buffer
+1);
462 vfs_s_free_entry (me
, ent
);
463 me
->verrno
= E_REMOTE
;
464 if (decode_reply(buffer
+4, 0) == COMPLETE
) {
466 SUP
.cwdir
= g_strdup (remote_path
);
467 print_vfs_message (_("%s: done."), me
->name
);
472 print_vfs_message (_("%s: failure"), me
->name
);
477 file_store(vfs
*me
, vfs_s_fh
*fh
, char *name
, char *localname
)
479 vfs_s_super
*super
= FH_SUPER
;
487 h
= open (localname
, O_RDONLY
);
491 if (fstat(h
, &s
)<0) {
495 /* Use this as stor: ( dd block ; dd smallblock ) | ( cat > file; cat > /dev/null ) */
497 print_vfs_message(_("fish: store %s: sending command..."), name
);
498 quoted_name
= name_quote (name
, 0);
500 * FIXME: Limit size to unsigned long for now.
501 * Files longer than 256 * ULONG_MAX are not supported.
503 if (!fh
->u
.fish
.append
)
504 n
= command (me
, super
, WAIT_REPLY
,
509 "dd ibs=256 obs=4096 count=%lu\n"
510 "dd bs=%lu count=1\n"
511 ") 2>/dev/null | (\n"
514 "); echo '### 200'\n",
515 (unsigned long) s
.st_size
, name
, quoted_name
,
516 (unsigned long) (s
.st_size
>> 8),
517 ((unsigned long) s
.st_size
) & (256 - 1), quoted_name
);
519 n
= command (me
, super
, WAIT_REPLY
,
523 "dd ibs=256 obs=4096 count=%lu\n"
524 "dd bs=%lu count=1\n"
525 ") 2>/dev/null | (\n"
528 "); echo '### 200'\n",
529 (unsigned long) s
.st_size
, name
,
530 (unsigned long) (s
.st_size
>> 8),
531 ((unsigned long) s
.st_size
) & (256 - 1), quoted_name
);
533 g_free (quoted_name
);
536 ERRNOR(E_REMOTE
, -1);
541 while ((n
= read(h
, buffer
, sizeof(buffer
))) < 0) {
542 if ((errno
== EINTR
) && got_interrupt
)
544 print_vfs_message(_("fish: Local read failed, sending zeros") );
546 h
= open( "/dev/zero", O_RDONLY
);
550 while (write(SUP
.sockw
, buffer
, n
) < 0) {
554 disable_interrupt_key();
556 print_vfs_message(_("fish: storing %s %d (%lu)"),
557 was_error
? _("zeros") : _("file"), total
,
558 (unsigned long) s
.st_size
);
561 if ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
) || was_error
)
562 ERRNOR (E_REMOTE
, -1);
566 get_reply(me
, SUP
.sockr
, NULL
, 0);
570 static int linear_start(vfs
*me
, vfs_s_fh
*fh
, int offset
)
575 ERRNOR (E_NOTSUPP
, 0);
576 /* fe->local_stat.st_mtime = 0; FIXME: what is this good for? */
577 name
= vfs_s_fullpath (me
, fh
->ino
);
580 quoted_name
= name_quote (name
, 0);
583 fh
->u
.fish
.append
= 0;
584 offset
= command(me
, FH_SUPER
, WANT_STRING
,
586 "ls -l /%s 2>/dev/null | (\n"
587 "read var1 var2 var3 var4 var5 var6\n"
595 if (offset
!= PRELIM
) ERRNOR (E_REMOTE
, 0);
596 fh
->linear
= LS_LINEAR_OPEN
;
598 if (sscanf( reply_str
, "%d", &fh
->u
.fish
.total
)!=1)
599 ERRNOR (E_REMOTE
, 0);
604 linear_abort (vfs
*me
, vfs_s_fh
*fh
)
606 vfs_s_super
*super
= FH_SUPER
;
610 print_vfs_message( _("Aborting transfer...") );
612 n
= MIN(8192, fh
->u
.fish
.total
- fh
->u
.fish
.got
);
614 if ((n
= read(SUP
.sockr
, buffer
, n
)) < 0)
618 if (get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)
619 print_vfs_message( _("Error reported after abort.") );
621 print_vfs_message( _("Aborted transfer would be successful.") );
625 linear_read (vfs
*me
, vfs_s_fh
*fh
, void *buf
, int len
)
627 vfs_s_super
*super
= FH_SUPER
;
629 len
= MIN( fh
->u
.fish
.total
- fh
->u
.fish
.got
, len
);
630 disable_interrupt_key();
631 while (len
&& ((n
= read (SUP
.sockr
, buf
, len
))<0)) {
632 if ((errno
== EINTR
) && !got_interrupt())
636 enable_interrupt_key();
638 if (n
>0) fh
->u
.fish
.got
+= n
;
639 if (n
<0) linear_abort(me
, fh
);
640 if ((!n
) && ((get_reply (me
, SUP
.sockr
, NULL
, 0) != COMPLETE
)))
641 ERRNOR (E_REMOTE
, -1);
646 linear_close (vfs
*me
, vfs_s_fh
*fh
)
648 if (fh
->u
.fish
.total
!= fh
->u
.fish
.got
)
649 linear_abort(me
, fh
);
650 else if (stat (fh
->ino
->localname
, &fh
->ino
->u
.fish
.local_stat
) < 0)
651 fh
->ino
->u
.fish
.local_stat
.st_mtime
= 0;
655 fish_ctl (void *fh
, int ctlop
, int arg
)
659 case MCCTL_IS_NOTREADY
:
664 vfs_die ("You may not do this");
665 if (FH
->linear
== LS_LINEAR_CLOSED
)
668 v
= vfs_s_select_on_two (FH_SUPER
->u
.fish
.sockr
, 0);
669 if (((v
< 0) && (errno
== EINTR
)) || v
== 0)
679 send_fish_command(vfs
*me
, vfs_s_super
*super
, char *cmd
, int flags
)
683 r
= command (me
, super
, WAIT_REPLY
, cmd
);
684 vfs_add_noncurrent_stamps (&vfs_fish_ops
, (vfsid
) super
, NULL
);
685 if (r
!= COMPLETE
) ERRNOR (E_REMOTE
, -1);
686 if (flags
& OPT_FLUSH
)
687 vfs_s_invalidate(me
, super
);
692 char buf[BUF_LARGE]; \
694 vfs_s_super *super; \
695 if (!(rpath = vfs_s_get_path_mangle(me, path, &super, 0))) \
697 rpath = name_quote (rpath, 0);
699 #define POSTFIX(flags) \
701 return send_fish_command(me, super, buf, flags);
704 fish_chmod (vfs
*me
, char *path
, int mode
)
707 g_snprintf(buf
, sizeof(buf
), "#CHMOD %4.4o /%s\n"
708 "chmod %4.4o \"/%s\" 2>/dev/null\n"
711 mode
& 07777, rpath
);
715 #define FISH_OP(name, chk, string) \
716 static int fish_##name (vfs *me, char *path1, char *path2) \
718 char buf[BUF_LARGE]; \
719 char *rpath1, *rpath2; \
720 vfs_s_super *super1, *super2; \
721 if (!(rpath1 = vfs_s_get_path_mangle(me, path1, &super1, 0))) \
723 if (!(rpath2 = vfs_s_get_path_mangle(me, path2, &super2, 0))) \
725 rpath1 = name_quote (rpath1, 0); \
726 rpath2 = name_quote (rpath2, 0); \
727 g_snprintf(buf, sizeof(buf), string "\n", rpath1, rpath2, rpath1, rpath2); \
730 return send_fish_command(me, super2, buf, OPT_FLUSH); \
733 #define XTEST if (bucket1 != bucket2) { ERRNOR (EXDEV, -1); }
734 FISH_OP(rename
, XTEST
, "#RENAME /%s /%s\n"
735 "mv /%s /%s 2>/dev/null\n"
737 FISH_OP(link
, XTEST
, "#LINK /%s /%s\n"
738 "ln /%s /%s 2>/dev/null\n"
741 static int fish_symlink (vfs
*me
, char *setto
, char *path
)
744 setto
= name_quote (setto
, 0);
745 g_snprintf(buf
, sizeof(buf
),
747 "ln -s %s /%s 2>/dev/null\n"
749 setto
, rpath
, setto
, rpath
);
755 fish_chown (vfs
*me
, char *path
, int owner
, int group
)
757 char *sowner
, *sgroup
;
762 if ((pw
= getpwuid (owner
)) == NULL
)
765 if ((gr
= getgrgid (group
)) == NULL
)
768 sowner
= pw
->pw_name
;
769 sgroup
= gr
->gr_name
;
770 g_snprintf(buf
, sizeof(buf
),
772 "chown %s /%s 2>/dev/null\n"
776 send_fish_command(me
, super
, buf
, OPT_FLUSH
);
777 /* FIXME: what should we report if chgrp succeeds but chown fails? */
778 g_snprintf(buf
, sizeof(buf
),
780 "chgrp %s /%s 2>/dev/null\n"
784 /* send_fish_command(me, super, buf, OPT_FLUSH); */
788 static int fish_unlink (vfs
*me
, char *path
)
791 g_snprintf(buf
, sizeof(buf
),
793 "rm -f /%s 2>/dev/null\n"
799 static int fish_mkdir (vfs
*me
, char *path
, mode_t mode
)
802 g_snprintf(buf
, sizeof(buf
),
804 "mkdir /%s 2>/dev/null\n"
810 static int fish_rmdir (vfs
*me
, char *path
)
813 g_snprintf(buf
, sizeof(buf
),
815 "rmdir /%s 2>/dev/null\n"
821 static int fish_fh_open (vfs
*me
, vfs_s_fh
*fh
, int flags
, int mode
)
823 fh
->u
.fish
.append
= 0;
824 /* File will be written only, so no need to retrieve it */
825 if (((flags
& O_WRONLY
) == O_WRONLY
) && !(flags
& (O_RDONLY
|O_RDWR
))){
826 fh
->u
.fish
.append
= flags
& O_APPEND
;
827 if (!fh
->ino
->localname
){
828 int tmp_handle
= mc_mkstemps (&fh
->ino
->localname
, me
->name
, NULL
);
829 if (tmp_handle
== -1)
835 if (!fh
->ino
->localname
)
836 if (vfs_s_retrieve_file (me
, fh
->ino
)==-1)
838 if (!fh
->ino
->localname
)
839 vfs_die( "retrieve_file failed to fill in localname" );
843 static struct vfs_s_data fish_data
= {
849 NULL
, /* init_inode */
850 NULL
, /* free_inode */
851 NULL
, /* init_entry */
853 NULL
, /* archive_check */
858 fish_fh_open
, /* fh_open */
861 vfs_s_find_entry_linear
,
872 fish_fill_names (vfs
*me
, void (*func
)(char *))
874 struct vfs_s_super
* super
= fish_data
.supers
;
879 switch (SUP
.flags
& (FISH_FLAG_RSH
| FISH_FLAG_COMPRESSED
)) {
883 case FISH_FLAG_COMPRESSED
:
886 case FISH_FLAG_RSH
| FISH_FLAG_COMPRESSED
:
894 name
= g_strconcat ("/#sh:", SUP
.user
, "@", SUP
.host
, flags
,
895 "/", SUP
.cwdir
, NULL
);
903 NULL
, /* This is place of next pointer */
907 &fish_data
, /* data */
934 fish_symlink
, /* symlink */
935 fish_link
, /* link */
938 fish_rename
, /* rename */
948 NULL
, /* vfs_s_getlocalcopy, */
949 NULL
, /* vfs_s_ungetlocalcopy, */