mc/vfs/fish.c

915 lines
23 KiB
C
Raw Normal View History

/* Virtual File System: FISH implementation for transfering files over
shell connections.
Copyright (C) 1998 The Free Software Foundation
Written by: 1998 Pavel Machek
Derived from ftpfs.c.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/*
* Read README.fish for protocol specification.
*
* Syntax of path is: /#sh:user@host[:Cr]/path
* where C means you want compressed connection,
* and r means you want to use rsh
*/
#include <config.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <stdarg.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h> /* For isdigit */
#ifdef SCO_FLAVOR
# include <sys/timeb.h> /* alex: for struct timeb definition */
#endif /* SCO_FLAVOR */
#include <time.h>
#include <sys/types.h>
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif
#if HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include "../src/fs.h"
#include "../src/mad.h"
#include "../src/setup.h"
#include "../src/tty.h" /* enable/disable interrupt key */
#include "../src/main.h"
#ifndef SCO_FLAVOR
# include <sys/time.h> /* alex: this redefines struct timeval */
#endif /* SCO_FLAVOR */
#include <sys/param.h>
#include "../src/mem.h"
#define WANT_PARSE_LS_LGA
#include "vfs.h"
#include "tcputil.h"
#include "../src/util.h"
#include "../src/dialog.h"
#include "container.h"
#include "fish.h"
#ifndef MAXHOSTNAMELEN
# define MAXHOSTNAMELEN 64
#endif
/*
* Reply codes.
*/
#define PRELIM 1 /* positive preliminary */
#define COMPLETE 2 /* positive completion */
#define CONTINUE 3 /* positive intermediate */
#define TRANSIENT 4 /* transient negative completion */
#define ERROR 5 /* permanent negative completion */
#define UPLOAD_ZERO_LENGTH_FILE
static int my_errno;
static int code;
/* Where we store the transactions */
static FILE *logfile = NULL;
/* If true, the directory cache is forced to reload */
static int force_expiration = 0;
static struct linklist *connections_list;
/* command wait_flag: */
#define NONE 0x00
#define WAIT_REPLY 0x01
#define WANT_STRING 0x02
static char reply_str [80];
static char *fish_get_current_directory(struct connection *bucket);
static void free_bucket (void *data);
static void connection_destructor(void *data);
static void flush_all_directory(struct connection *bucket);
static int get_line (int sock, char *buf, int buf_len, char term);
static char *get_path (struct connection **bucket, char *path);
static char *my_get_host_and_username (char *path, char **host, char **user, int *flags, char **pass)
{
return get_host_and_username (path, host, user, flags, 0, 0, pass);
}
static int decode_reply (char *s, int was_garbage)
{
if (!sscanf(s, "%d", &code)) {
code = 500;
return 5;
}
if (code<100) return was_garbage ? ERROR : (!code ? COMPLETE : PRELIM);
return code / 100;
}
/* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
static int get_reply (int sock, char *string_buf, int string_len)
{
char answer[1024];
int was_garbage = 0;
for (;;) {
if (!get_line(sock, answer, sizeof(answer), '\n')) {
if (string_buf)
*string_buf = 0;
code = 421;
return 4;
}
if (strncmp(answer, "### ", 4)) {
was_garbage = 1;
if (string_buf) {
strncpy(string_buf, answer, string_len - 1);
*(string_buf + string_len - 1) = 0;
}
} else return decode_reply(answer+4, was_garbage);
}
}
int got_sigpipe = 0;
static int command (struct connection *bucket, int wait_reply,
char *fmt, ...)
{
va_list ap;
char buf[2048]; /* FIXME: buffer exceed ?? */
int n, status;
va_start (ap, fmt);
vsprintf (buf, fmt, ap);
va_end (ap);
n = strlen(buf);
buf[n] = 0;
if (logfile){
fwrite (buf, strlen (buf), 1, logfile);
fflush (logfile);
}
enable_interrupt_key();
status = write(qsockw(bucket), buf, strlen(buf));
if (status < 0){
code = 421;
if (errno == EPIPE){
got_sigpipe = 1;
}
disable_interrupt_key();
return TRANSIENT;
}
disable_interrupt_key();
if (wait_reply)
return get_reply (qsockr(bucket), (wait_reply & WANT_STRING) ? reply_str : NULL, sizeof (reply_str)-1);
return COMPLETE;
}
static void
connection_close (void *data)
{
struct connection *bucket = data;
if ((qsockw (bucket) != -1) || (qsockr (bucket) != -1)){
print_vfs_message ("fish: Disconnecting from %s", qhost(bucket));
command(bucket, NONE, "logout\n");
close(qsockw(bucket));
close(qsockr(bucket));
}
}
static void
pipeopen(struct connection *bucket, char *path, char *argv[])
{
int fileset1[2], fileset2[2];
FILE *retval;
int res;
if (pipe(fileset1)<0) vfs_die("Could not pipe(): %m.");
if (pipe(fileset2)<0) vfs_die("Could not pipe(): %m.");
if (res = fork()) {
if (res<0) vfs_die("Could not fork(): %m.");
/* We are the parent */
close(fileset1[0]);
qsockw(bucket) = fileset1[1];
close(fileset2[1]);
qsockr(bucket) = fileset2[0];
if (!retval) vfs_die( "Could not fdopen(): %m." );
} else {
close(0);
dup(fileset1[0]);
close(fileset1[0]); close(fileset1[1]);
close(1); close(2);
dup(fileset2[1]);
dup(fileset2[1]);
close(fileset2[0]); close(fileset2[1]);
execvp(path, argv);
vfs_die("Exec failed.");
}
}
static struct connection *
open_command_connection (char *host, char *user, int flags, char *netrcpass)
{
struct connection *bucket;
char *argv[100];
int i = 0;
bucket = xmalloc(sizeof(struct connection),
"struct connection");
if (bucket == NULL) ERRNOR (ENOMEM, NULL);
qhost(bucket) = strdup (host);
quser(bucket) = strdup (user);
qcdir(bucket) = NULL;
qflags(bucket) = flags;
qlock(bucket) = 0;
qhome(bucket) = NULL;
qupdir(bucket)= 0;
qdcache(bucket)=0;
bucket->__inode_counter = 0;
bucket->lock = 0;
bucket->password = 0;
my_errno = ENOMEM;
if ((qdcache(bucket) = linklist_init()) == NULL)
goto error;
#define XSH (flags == FISH_FLAG_RSH ? "rsh" : "ssh")
argv[i++] = XSH;
argv[i++] = "-l";
argv[i++] = user;
argv[i++] = host;
if (flags == FISH_FLAG_COMPRESSED)
argv[i++] = "-C";
argv[i++] = "echo FISH:; /bin/sh";
argv[i++] = NULL;
pipeopen(bucket, XSH, argv );
{
char answer[2048];
print_vfs_message( "FISH: Waiting for initial line..." );
if (!get_line(qsockr(bucket), answer, sizeof(answer), ':'))
goto error_2;
print_vfs_message( answer );
if (strstr(answer, "assword")) {
/* Currently, this does not work. ssh reads passwords from
/dev/tty, not from stdin :-(. */
message_1s (1, MSG_ERROR, _("Sorry, we can not do password authenticated connections for now."));
my_errno = EPERM;
goto error_2;
if (!bucket->password){
char *p, *op;
p = copy_strings (" FISH: Password required for ", quser(bucket),
" ", NULL);
op = vfs_get_password (p);
free (p);
my_errno = EPERM;
if (op == NULL)
goto error_2;
bucket->password = strdup (op);
wipe_password(op);
}
print_vfs_message( "FISH: Sending password..." );
write(qsockw(bucket), bucket->password, strlen(bucket->password));
write(qsockw(bucket), "\r\n", 2);
}
}
print_vfs_message( "FISH: Sending initial line..." );
my_errno = ECONNREFUSED;
if (command (bucket, WAIT_REPLY, "#FISH\necho; start_fish_server; echo '### 200'\n") != COMPLETE)
goto error_2;
print_vfs_message( "FISH: Handshaking version..." );
if (command (bucket, WAIT_REPLY, "#VER 0.0.0\necho '### 000'\n") != COMPLETE)
goto error_2;
print_vfs_message( "FISH: Setting up current directory..." );
qhome(bucket) = fish_get_current_directory (bucket);
if (!qhome(bucket))
qhome(bucket) = strdup ("/");
print_vfs_message( "FISH: Connected." );
return bucket;
error_2:
close(qsockr(bucket));
close(qsockw(bucket));
error:
free (qhost(bucket));
free (quser(bucket));
free (bucket);
return NULL;
}
/* This routine keeps track of open connections */
/* Returns a connected socket to host */
static struct connection *
open_link (char *host, char *user, int flags, char *netrcpass)
{
struct connection *bucket;
struct linklist *lptr;
for (lptr = connections_list->next;
lptr != connections_list; lptr = lptr->next) {
bucket = lptr->data;
if ((strcmp (host, qhost(bucket)) == 0) &&
(strcmp (user, quser(bucket)) == 0) &&
(flags == qflags(bucket)))
return bucket;
}
bucket = open_command_connection(host, user, flags, netrcpass);
if (bucket == NULL)
return NULL;
if (!linklist_insert(connections_list, bucket)) {
connection_destructor(bucket);
ERRNOR (ENOMEM, NULL);
}
return bucket;
}
/* The returned directory should always contain a trailing slash */
static char *fish_get_current_directory(struct connection *bucket)
{
if (command(bucket, WANT_STRING, "#PWD\npwd; echo '### 200'\n") == COMPLETE)
return copy_strings(reply_str, "/", NULL);
ERRNOR (EIO, NULL);
}
#define X "fish"
#define X_myname "/#sh:"
#define X_vfs_ops fish_vfs_ops
#define X_fill_names fish_fill_names
#define X_hint_reread fish_hint_reread
#define X_flushdir fish_flushdir
#define X_done fish_done
#include "shared_ftp_fish.c"
static char*
get_path (struct connection **bucket, char *path)
{
char *res;
if (res = s_get_path (bucket, path, "/#sh:"))
return res;
if (res = s_get_path (bucket, path, "/#ssh:"))
return res;
if (res = s_get_path (bucket, path, "/#rsh:")) {
qflags((*bucket)) |= FISH_FLAG_RSH;
return res;
}
1998-09-18 18:28:07 +04:00
return NULL;
}
/*
* This is the 'new' code
*/
Tue Sep 15 20:51:42 1998 Norbert Warmuth <k3190@fh-sw.de> * setup.c: save and restore new global variable/option ftp_use_unix_list_options Tue Sep 15 20:31:32 1998 Norbert Warmuth <k3190@fh-sw.de> * ftpfs.c (ftp_use_unix_list_options): New global variable/option. If true we try to use 'LIST -la <path>'. When it fails we use the two commands 'CWD <path>' and 'LIST' instead. (resolve_symlink): rewritten. Don't get a second directory listing with `LIST -lLa'. Instead use the cache to get the file stat of symbolic links. If the directory the symlink points to isn't already in the cache the directory listing will be fetched and stored in the directory cache (without resolving symlinks recursively). The new method to resolve symlinks is faster if symlinks the same directory or the directory the symlink points to is already in the cache. This function was small and nice until I discovered that it was broken for symlinks to symlinks. Now it looks ugly and perhaps I will revert it to use "LIST -lLa" again. With a fast connection it doesn't matter which methode we use but with a slow connection I wouldn't hesitate to burn more cpu cycles on the client side. (retrieve_dir): Added parameter to tell whether to resolve symlinks (don't resolve symlinks in directory listings retrieved while resolving symlinks). When we don't get a directory listing with 'LIST -la <path>' then try to get it with `CWD <path>; LIST'. Tue Sep 15 20:27:29 1998 Norbert Warmuth <k3190@fh-sw.de * ftpfs.c (login_server): s/ftpfs_get_host/my_get_host/ (retrieve_file_start2): Don't create target file O_EXCL, in copy_file_file we check existance of the target file and know that we want to truncate it (this change was already done a while back but it was reverted with the vfs-split). Tue Sep 15 20:15:42 1998 Norbert Warmuth <k3190@fh-sw.de> * ftpfs.h (struct connection): added boolean which indicates that the ftp server doesn't unterstand Unix ls options * ftpfs.h (struct dir): added enum to store symlink status of the in memory directory cache (directory has no symbolic links; symbolic links but not yet resolved; symbolic links which are resolved) Tue Sep 15 20:02:08 1998 Norbert Warmuth <k3190@fh-sw.de> * shared_ftp_fish.c, fish.c: updated references to retrieve_dir to honour the additional boolean parameter
1998-09-15 23:41:22 +04:00
/*
* Last parameter (resolve_symlinks) is currently not used. Due to
* the code sharing (file shared_ftp_fish.c) the fish and ftp interface
* have to be the same (Norbert).
*/
static struct dir *
Tue Sep 15 20:51:42 1998 Norbert Warmuth <k3190@fh-sw.de> * setup.c: save and restore new global variable/option ftp_use_unix_list_options Tue Sep 15 20:31:32 1998 Norbert Warmuth <k3190@fh-sw.de> * ftpfs.c (ftp_use_unix_list_options): New global variable/option. If true we try to use 'LIST -la <path>'. When it fails we use the two commands 'CWD <path>' and 'LIST' instead. (resolve_symlink): rewritten. Don't get a second directory listing with `LIST -lLa'. Instead use the cache to get the file stat of symbolic links. If the directory the symlink points to isn't already in the cache the directory listing will be fetched and stored in the directory cache (without resolving symlinks recursively). The new method to resolve symlinks is faster if symlinks the same directory or the directory the symlink points to is already in the cache. This function was small and nice until I discovered that it was broken for symlinks to symlinks. Now it looks ugly and perhaps I will revert it to use "LIST -lLa" again. With a fast connection it doesn't matter which methode we use but with a slow connection I wouldn't hesitate to burn more cpu cycles on the client side. (retrieve_dir): Added parameter to tell whether to resolve symlinks (don't resolve symlinks in directory listings retrieved while resolving symlinks). When we don't get a directory listing with 'LIST -la <path>' then try to get it with `CWD <path>; LIST'. Tue Sep 15 20:27:29 1998 Norbert Warmuth <k3190@fh-sw.de * ftpfs.c (login_server): s/ftpfs_get_host/my_get_host/ (retrieve_file_start2): Don't create target file O_EXCL, in copy_file_file we check existance of the target file and know that we want to truncate it (this change was already done a while back but it was reverted with the vfs-split). Tue Sep 15 20:15:42 1998 Norbert Warmuth <k3190@fh-sw.de> * ftpfs.h (struct connection): added boolean which indicates that the ftp server doesn't unterstand Unix ls options * ftpfs.h (struct dir): added enum to store symlink status of the in memory directory cache (directory has no symbolic links; symbolic links but not yet resolved; symbolic links which are resolved) Tue Sep 15 20:02:08 1998 Norbert Warmuth <k3190@fh-sw.de> * shared_ftp_fish.c, fish.c: updated references to retrieve_dir to honour the additional boolean parameter
1998-09-15 23:41:22 +04:00
retrieve_dir(struct connection *bucket, char *remote_path, int resolve_symlinks)
{
1998-09-18 18:28:07 +04:00
int has_symlinks;
struct linklist *file_list, *p;
struct direntry *fe;
char buffer[8192];
struct dir *dcache;
int got_intr = 0;
for (p = qdcache(bucket)->next;p != qdcache(bucket);
p = p->next) {
dcache = p->data;
if (strcmp(dcache->remote_path, remote_path) == 0) {
struct timeval tim;
gettimeofday(&tim, NULL);
if ((tim.tv_sec < dcache->timestamp.tv_sec) && !force_expiration)
return dcache;
else {
force_expiration = 0;
p->next->prev = p->prev;
p->prev->next = p->next;
dir_destructor(dcache);
free (p);
break;
}
}
}
has_symlinks = 0;
print_vfs_message("fish: Reading FTP directory...");
my_errno = ENOMEM;
if (!(file_list = linklist_init()))
return NULL;
if (!(dcache = xmalloc(sizeof(struct dir), "struct dir"))) {
linklist_destroy(file_list, NULL);
return NULL;
}
gettimeofday(&dcache->timestamp, NULL);
dcache->timestamp.tv_sec += 360;
dcache->file_list = file_list;
dcache->remote_path = strdup(remote_path);
dcache->count = 1;
command(bucket, NONE,
"#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"
"S$s\nd$m $d $y\n:$n\n\"; done )\n"
"ls -lLa %s | grep '^[cb]' | ( while read p x u g a i m d y n; do echo \"P$p $u.$g\n"
"E$a$i\nd$m $d $y\n:$n\n\"; done ); echo '### 200'\n",
remote_path, remote_path, remote_path);
/* Clear the interrupt flag */
enable_interrupt_key ();
fe = NULL;
errno = 0;
my_errno = ENOMEM;
while ((got_intr = get_line_interruptible (buffer, sizeof (buffer), qsockr(bucket))) != EINTR){
int eof = (got_intr == 0);
if (logfile){
fputs (buffer, logfile);
fputs ("\n", logfile);
fflush (logfile);
}
if (eof) {
if (fe)
free(fe);
my_errno = ECONNRESET;
goto error_1;
}
if (!strncmp(buffer, "### ", 4))
break;
if ((!buffer[0]) && fe) {
if (!linklist_insert(file_list, fe)) {
free(fe);
goto error_1;
}
fe = NULL;
continue;
}
if (!fe) {
if (!(fe = xmalloc(sizeof(struct direntry), "struct direntry")))
goto error_1;
bzero(fe, sizeof(struct direntry));
fe->count = 1;
fe->bucket = bucket;
fe->s.st_ino = bucket->__inode_counter++;
fe->s.st_nlink = 1;
}
switch(buffer[0]) {
case ':': fe->name = strdup(buffer+1); break;
case 'S': fe->s.st_size = atoi(buffer+1); break;
case 'P': {
int i;
if ((i = parse_filetype(buffer[1])) ==-1)
break;
fe->s.st_mode = i;
if ((i = parse_filemode(buffer+2)) ==-1)
break;
fe->s.st_mode |= i;
}
break;
case 'd': {
split_text(buffer+1);
if (!parse_filedate(0, &fe->s.st_ctime))
break;
fe->s.st_atime = fe->s.st_mtime = fe->s.st_ctime;
}
break;
case 'D': {
struct tm tim;
if (sscanf(buffer+1, "%d %d %d %d %d %d", &tim.tm_year, &tim.tm_mon,
&tim.tm_mday, &tim.tm_hour, &tim.tm_min, &tim.tm_sec) != 6)
break;
fe->s.st_atime = fe->s.st_mtime = fe->s.st_ctime = mktime(&tim);
}
break;
case 'E': {
int maj, min;
if (sscanf(buffer+1, "%d,%d", &maj, &min) != 2)
break;
#ifdef HAVE_ST_RDEV
fe->s.st_rdev = (maj << 8) | min;
#endif
}
case 'L': fe->linkname = strdup(buffer+1);
break;
}
}
disable_interrupt_key();
#if 0
if (got_intr)
vfs_die("fish: reading FTP directory interrupted by user");
#endif
if (decode_reply(buffer+4, 0) != COMPLETE) {
my_errno = EIO;
goto error_3;
}
if (file_list->next == file_list) {
my_errno = EACCES;
goto error_3;
}
if (!linklist_insert(qdcache(bucket), dcache)) {
my_errno = ENOMEM;
goto error_3;
}
print_vfs_message("fish: got listing");
return dcache;
error_1:
disable_interrupt_key();
error_3:
free(dcache->remote_path);
free(dcache);
linklist_destroy(file_list, direntry_destructor);
print_vfs_message("fish: failed");
return NULL;
}
static int
store_file(struct direntry *fe)
{
1998-09-18 18:28:07 +04:00
int local_handle, n, total;
char buffer[8192];
struct stat s;
int was_error = 0;
local_handle = open(fe->local_filename, O_RDONLY);
unlink (fe->local_filename);
my_errno = EIO;
if (local_handle == -1)
return 0;
fstat(local_handle, &s);
/* Use this as stor: ( dd block ; dd malyblock ) | ( cat > file; cat > /dev/null ) */
print_vfs_message("FISH: store: sending command..." );
if (command (fe->bucket, WAIT_REPLY,
"#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",
s.st_size, fe->remote_filename,
fe->remote_filename,
s.st_size / 4096, s.st_size % 4096, fe->remote_filename)
!= PRELIM)
return 0;
total = 0;
enable_interrupt_key();
while (1) {
while ((n = read(local_handle, buffer, sizeof(buffer))) < 0) {
if ((errno == EINTR) && got_interrupt)
continue;
print_vfs_message("FISH: Local read failed, sending zeros" );
close(local_handle);
local_handle = open( "/dev/zero", O_RDONLY );
}
if (n == 0)
break;
while (write(qsockw(fe->bucket), buffer, n) < 0) {
if (errno == EINTR) {
if (got_interrupt()) {
my_errno = EINTR;
goto error_return;
}
else
continue;
}
my_errno = errno;
goto error_return;
}
total += n;
print_vfs_message("fish: storing %s %d (%d)",
was_error ? "zeros" : "file", total, s.st_size);
}
disable_interrupt_key();
close(local_handle);
if (get_reply (qsockr(fe->bucket), NULL, 0) != COMPLETE)
ERRNOR (EIO, 0);
return (!was_error);
error_return:
disable_interrupt_key();
close(local_handle);
get_reply(qsockr(fe->bucket), NULL, 0);
return 0;
}
static int linear_start(struct direntry *fe)
{
fe->local_stat.st_mtime = 0;
if (command(fe->bucket, WANT_STRING,
"#RETR %s\nls -l %s | ( read var1 var2 var3 var4 var5 var6; echo $var5 ); echo '### 100'; cat %s; echo '### 200'\n",
fe->remote_filename, fe->remote_filename, fe->remote_filename )
!= PRELIM) ERRNOR (EACCES, 0);
fe->got = 0;
fe->total = atoi(reply_str);
return 1;
}
static void
linear_abort (struct direntry *fe)
{
char buffer[8192];
int n;
print_vfs_message( "Aborting transfer..." );
do {
n = MIN(8192, fe->total - fe->got);
if (n)
if ((n = read(qsockr(fe->bucket), buffer, n)) < 0)
return;
} while (n);
if (get_reply (qsockr(fe->bucket), NULL, 0) != COMPLETE)
print_vfs_message( "Error reported after abort." );
else
print_vfs_message( "Aborted transfer would be successfull." );
}
static int
linear_read (struct direntry *fe, void *buf, int len)
{
int n = 0;
len = MIN( fe->total - fe->got, len );
while (len && ((n = read (qsockr(fe->bucket), buf, len))<0)) {
if ((errno == EINTR) && !got_interrupt())
continue;
break;
}
if (n>0) fe->got += n;
if (n<0) linear_abort(fe);
if ((!n) && ((get_reply (qsockr (fe->bucket), NULL, 0) != COMPLETE)))
ERRNOR (EIO, -1);
ERRNOR (errno, n);
}
static int
linear_close (struct direntry *fe)
{
if (fe->total != fe->got)
linear_abort(fe);
}
int fish_ctl (void *data, int ctlop, int arg)
{
int n = 0;
struct filp *fp = data;
int v;
switch (ctlop) {
case MCCTL_IS_NOTREADY:
{
int v = select_on_two (qsockr(fp->fe->bucket), 0);
if (((v < 0) && (errno == EINTR)) || v == 0)
return 1;
return 0;
}
default:
return 0;
}
}
static int
send_fish_command(struct connection *bucket, char *cmd, int flags)
{
int r;
int flush_directory_cache = (flags & OPT_FLUSH) && (normal_flush > 0);
r = command (bucket, WAIT_REPLY, cmd);
vfs_add_noncurrent_stamps (&fish_vfs_ops, (vfsid) bucket, NULL);
if (r != COMPLETE) ERRNOR (EPERM, -1);
if (flush_directory_cache)
flush_all_directory(bucket);
return 0;
}
void
fish_init (void)
{
connections_list = linklist_init();
logfile = fopen ("/tmp/talk.fish", "w+");
}
#define PREFIX \
char buf[999]; \
char *remote_path; \
struct connection *bucket; \
if (!(remote_path = get_path(&bucket, path))) \
return -1;
#define POSTFIX(flags) \
free(remote_path); \
return send_fish_command(bucket, buf, flags);
int fish_chmod (char *path, int mode)
{
PREFIX
sprintf(buf, "#CHMOD %4.4o %s\nchmod %4.4o %s; echo '### 000'\n",
mode & 07777, remote_path,
mode & 07777, remote_path);
POSTFIX(OPT_FLUSH);
}
#define FISH_OP(name, chk, string) \
int fish_##name (char *path1, char *path2) \
{ \
char buf[120]; \
char *remote_path1 = NULL, *remote_path2 = NULL; \
struct connection *bucket1, *bucket2; \
if (!(remote_path1 = get_path(&bucket1, path1))) \
return -1; \
if (!(remote_path2 = get_path(&bucket2, path2))) { \
free(remote_path1); \
return -1; \
} \
sprintf(buf, string, path1, path2, path1, path2 ); \
free(remote_path1); \
free(remote_path2); \
return send_fish_command(bucket2, buf, OPT_FLUSH); \
}
#define XTEST if (bucket1 != bucket2) { free(remote_path1); free(remote_path2); ERRNOR (EXDEV, -1); }
FISH_OP(rename, XTEST, "#RENAME %s %s\nmv %s %s; echo '*** 000'" );
FISH_OP(link, XTEST, "#LINK %s %s\nln %s %s; echo '*** 000'" );
FISH_OP(symlink, , "#SYMLINK %s %s\nln -s %s %s; echo '*** 000'" );
int fish_chown (char *path, int owner, int group)
{
char *sowner, *sgroup;
PREFIX
sowner = getpwuid( owner )->pw_name;
sgroup = getgrgid( group )->gr_name;
sprintf(buf, "#CHOWN %s %s\nchown %s %s; echo '### 000'\n",
sowner, remote_path,
sowner, remote_path);
send_fish_command(bucket, buf, OPT_FLUSH);
/* FIXME: what should we report if chgrp succeeds but chown fails? */
sprintf(buf, "#CHGRP %s %s\nchgrp %s %s; echo '### 000'\n",
sgroup, remote_path,
sgroup, remote_path);
free(remote_path);
POSTFIX(OPT_FLUSH)
}
static int fish_unlink (char *path)
{
PREFIX
sprintf(buf, "#DELE %s\nrm -f %s; echo '### 000'\n", remote_path, remote_path);
POSTFIX(OPT_FLUSH);
}
static int fish_mkdir (char *path, mode_t mode)
{
PREFIX
sprintf(buf, "#MKD %s\nmkdir %s; echo '### 000'\n", remote_path, remote_path);
POSTFIX(OPT_FLUSH);
}
static int fish_rmdir (char *path)
{
PREFIX
sprintf(buf, "#RMD %s\nrmdir %s; echo '### 000'\n", remote_path, remote_path);
POSTFIX(OPT_FLUSH);
}
void fish_set_debug (char *file)
{
}
void fish_forget (char *file)
{
#if 0
struct linklist *l;
char *host, *user, *pass, *rp;
int port;
#ifndef BROKEN_PATHS
if (strncmp (file, "/#sh:", 5))
return; /* Normal: consider cd /bla/#ftp */
#else
if (!(file = strstr (file, "/#sh:")))
return;
#endif
file += 6;
if (!(rp = fish_get_host_and_username (file, &host, &user, &port, &pass))) {
free (host);
free (user);
if (pass)
wipe_password (pass);
return;
}
/* we do not care about the path actually */
free (rp);
for (l = connections_list->next; l != connections_list; l = l->next){
struct connection *bucket = l->data;
if ((strcmp (host, qhost (bucket)) == 0) &&
(strcmp (user, quser (bucket)) == 0) &&
(port == qport (bucket))){
/* close socket: the child owns it now */
close (bucket->sock);
bucket->sock = -1;
/* reopen the connection */
bucket->sock = fish_open_socket (bucket);
if (bucket->sock != -1)
login_server (bucket, pass);
break;
}
}
free (host);
free (user);
if (pass)
wipe_password (pass);
#endif
}
vfs fish_vfs_ops = {
s_open,
s_close,
s_read,
s_write,
s_opendir,
s_readdir,
s_closedir,
s_telldir,
s_seekdir,
s_stat,
s_lstat,
s_fstat,
fish_chmod,
fish_chown, /* not really implemented but returns success */
NULL, /* utime */
s_readlink,
fish_symlink, /* symlink */
fish_link, /* link */
fish_unlink,
fish_rename, /* rename */
s_chdir,
s_errno,
s_lseek,
NULL, /* mknod */
s_getid,
s_nothingisopen,
s_free,
s_getlocalcopy,
s_ungetlocalcopy,
fish_mkdir,
fish_rmdir,
fish_ctl,
s_setctl,
fish_forget
#ifdef HAVE_MMAP
, NULL,
NULL
#endif
};