#include <u.h>
#include <libc.h>
#include <auth.h>
#include <fcall.h>
#include <String.h>
#include "ftpfs.h"
/* an active fid */
typedef struct Fid Fid;
struct Fid
{
int fid;
Node *node; /* path to remote file */
int busy;
Fid *next;
int open;
};
Fid *fids; /* linked list of fids */
char errstring[128]; /* error to return */
int mfd; /* fd for 9fs */
int messagesize = 4*1024*IOHDRSZ;
uchar mdata[8*1024*IOHDRSZ];
uchar mbuf[8*1024*IOHDRSZ];
Fcall rhdr;
Fcall thdr;
int debug;
int usenlst;
int usetls;
char *ext;
int quiet;
int kapid = -1;
int dying; /* set when any process decides to die */
int dokeepalive;
char *rflush(Fid*), *rnop(Fid*), *rversion(Fid*),
*rattach(Fid*), *rclone(Fid*), *rwalk(Fid*),
*rclwalk(Fid*), *ropen(Fid*), *rcreate(Fid*),
*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
*rauth(Fid*);;
void mountinit(char*);
void io(void);
int readpdir(Node*);
char *(*fcalls[])(Fid*) = {
[Tflush] rflush,
[Tversion] rversion,
[Tattach] rattach,
[Tauth] rauth,
[Twalk] rwalk,
[Topen] ropen,
[Tcreate] rcreate,
[Tread] rread,
[Twrite] rwrite,
[Tclunk] rclunk,
[Tremove] rremove,
[Tstat] rstat,
[Twstat] rwstat,
};
/* these names are matched as prefixes, so VMS must precede VM */
OS oslist[] = {
{ Plan9, "Plan 9", },
{ Plan9, "Plan9", },
{ Plan9, "UNIX Type: L8 Version: Plan 9", },
{ Unix, "SUN", },
{ Unix, "UNIX", },
{ VMS, "VMS", },
{ VM, "VM", },
{ Tops, "TOPS", },
{ MVS, "MVS", },
{ NetWare, "NetWare", },
{ NetWare, "NETWARE", },
{ OS½, "OS/2", },
{ TSO, "TSO", },
{ NT, "Windows_NT", }, /* DOS like interface */
{ NT, "WINDOWS_NT", }, /* Unix like interface */
{ Unknown, 0 },
};
char *nouid = "?uid?";
#define S2P(x) (((ulong)(x)) & 0xffffff)
char *nosuchfile = "file does not exist";
char *keyspec = "";
void
usage(void)
{
fprint(2, "ftpfs [-/dqnt] [-a passwd] [-m mountpoint] [-e ext] [-o os] [-r root] [net!]address\n");
exits("usage");
}
void
main(int argc, char *argv[])
{
char *mountroot = 0;
char *mountpoint = "/n/ftp";
char *cpassword = 0;
char *cp;
int p[2];
OS *o;
defos = Unix;
user = strdup(getuser());
usetls = 0;
ARGBEGIN {
case '/':
mountroot = "/";
break;
case 'a':
cpassword = ARGF();
break;
case 'd':
debug = 1;
break;
case 'k':
keyspec = EARGF(usage());
break;
case 'K':
dokeepalive = 1;
break;
case 'm':
mountpoint = ARGF();
break;
case 'n':
usenlst = 1;
break;
case 'e':
ext = ARGF();
break;
case 't':
usetls = 1;
break;
case 'o':
cp = ARGF();
for(o = oslist; o->os != Unknown; o++)
if(strncmp(cp, o->name, strlen(o->name)) == 0){
defos = o->os;
break;
}
break;
case 'r':
mountroot = ARGF();
break;
case 'q':
quiet = 1;
break;
} ARGEND
if(argc != 1)
usage();
/* get a pipe to mount and run 9fs on */
if(pipe(p) < 0)
fatal("pipe failed: %r");
mfd = p[0];
/* initial handshakes with remote side */
hello(*argv);
if(cpassword == 0)
rlogin(*argv, keyspec);
else
clogin("anonymous", cpassword);
preamble(mountroot);
/* start the 9fs protocol */
switch(rfork(RFPROC|RFNAMEG|RFENVG|RFFDG|RFNOTEG|RFREND)){
case -1:
fatal("fork: %r");
case 0:
/* seal off standard input/output */
close(0);
open("/dev/null", OREAD);
close(1);
open("/dev/null", OWRITE);
close(p[1]);
fmtinstall('F', fcallfmt); /* debugging */
fmtinstall('D', dirfmt); /* expected by %F */
fmtinstall('M', dirmodefmt); /* expected by %F */
io();
quit();
break;
default:
close(p[0]);
if(mount(p[1], -1, mountpoint, MREPL|MCREATE, "") < 0)
fatal("mount failed: %r");
}
exits(0);
}
/*
* lookup an fid. if not found, create a new one.
*/
Fid *
newfid(int fid)
{
Fid *f, *ff;
ff = 0;
for(f = fids; f; f = f->next){
if(f->fid == fid){
if(f->busy)
return f;
else{
ff = f;
break;
}
} else if(!ff && !f->busy)
ff = f;
}
if(ff == 0){
ff = mallocz(sizeof(*f), 1);
ff->next = fids;
fids = ff;
}
ff->node = nil;
ff->fid = fid;
return ff;
}
/*
* a process that sends keep alive messages to
* keep the server from shutting down the connection
*/
int
kaproc(void)
{
int pid;
if(!dokeepalive)
return -1;
switch(pid = rfork(RFPROC|RFMEM)){
case -1:
return -1;
case 0:
break;
default:
return pid;
}
while(!dying){
sleep(5000);
nop();
}
_exits(0);
return -1;
}
void
io(void)
{
char *err, buf[ERRMAX];
int n;
kapid = kaproc();
while(!dying){
n = read9pmsg(mfd, mdata, messagesize);
if(n <= 0){
errstr(buf, sizeof buf);
if(buf[0]=='\0' || strstr(buf, "hungup"))
exits("");
fatal("mount read: %s\n", buf);
}
if(convM2S(mdata, n, &thdr) == 0)
continue;
if(debug)
fprint(2, "<-%F\n", &thdr);/**/
if(!fcalls[thdr.type])
err = "bad fcall type";
else
err = (*fcalls[thdr.type])(newfid(thdr.fid));
if(err){
rhdr.type = Rerror;
rhdr.ename = err;
}else{
rhdr.type = thdr.type + 1;
rhdr.fid = thdr.fid;
}
rhdr.tag = thdr.tag;
if(debug)
fprint(2, "->%F\n", &rhdr);/**/
n = convS2M(&rhdr, mdata, messagesize);
if(write(mfd, mdata, n) != n)
fatal("mount write");
}
}
char*
rnop(Fid *f)
{
USED(f);
return 0;
}
char*
rversion(Fid*)
{
if(thdr.msize > sizeof(mdata))
rhdr.msize = messagesize;
else
rhdr.msize = thdr.msize;
messagesize = thdr.msize;
if(strncmp(thdr.version, "9P2000", 6) != 0)
return "unknown 9P version";
rhdr.version = "9P2000";
return nil;
}
char*
rflush(Fid*)
{
return 0;
}
char*
rauth(Fid*)
{
return "auth unimplemented";
}
char*
rattach(Fid *f)
{
f->busy = 1;
f->node = remroot;
rhdr.qid = f->node->d->qid;
return 0;
}
char*
rwalk(Fid *f)
{
Node *np;
Fid *nf;
char **elems;
int i, nelems;
char *err;
Node *node;
/* clone fid */
nf = nil;
if(thdr.newfid != thdr.fid){
nf = newfid(thdr.newfid);
if(nf->busy)
return "newfid in use";
nf->busy = 1;
nf->node = f->node;
f = nf;
}
err = nil;
elems = thdr.wname;
nelems = thdr.nwname;
node = f->node;
rhdr.nwqid = 0;
if(nelems > 0){
/* walk fid */
for(i=0; i<nelems && i<MAXWELEM; i++){
if((node->d->qid.type & QTDIR) == 0){
err = "not a directory";
break;
}
if(strcmp(elems[i], ".") == 0){
Found:
rhdr.wqid[i] = node->d->qid;
rhdr.nwqid++;
continue;
}
if(strcmp(elems[i], "..") == 0){
node = node->parent;
goto Found;
}
if(strcmp(elems[i], ".flush.ftpfs") == 0){
/* hack to flush the cache */
invalidate(node);
readdir(node);
goto Found;
}
/* some top level names are special */
if((os == Tops || os == VM || os == VMS) && node == remroot){
np = newtopsdir(elems[i]);
if(np){
node = np;
goto Found;
} else {
err = nosuchfile;
break;
}
}
/* everything else */
node = extendpath(node, s_copy(elems[i]));
if(ISCACHED(node->parent)){
/* the cache of the parent is good, believe it */
if(!ISVALID(node)){
err = nosuchfile;
break;
}
if(node->parent->chdirunknown || (node->d->mode & DMSYML))
fixsymbolic(node);
} else if(!ISVALID(node)){
/* this isn't a valid node, try cd'ing */
if(changedir(node) == 0){
node->d->qid.type = QTDIR;
node->d->mode |= DMDIR;
}else{
node->d->qid.type = QTFILE;
node->d->mode &= ~DMDIR;
}
}
goto Found;
}
if(i == 0 && err == 0)
err = "file does not exist";
}
/* clunk a newly cloned fid if the walk didn't succeed */
if(nf != nil && (err != nil || rhdr.nwqid<nelems)){
nf->busy = 0;
nf->fid = 0;
}
/* if it all worked, point the fid to the enw node */
if(err == nil)
f->node = node;
return err;
}
char *
ropen(Fid *f)
{
int mode;
mode = thdr.mode;
if(f->node->d->qid.type & QTDIR)
if(mode != OREAD)
return "permission denied";
if(mode & OTRUNC){
uncache(f->node);
uncache(f->node->parent);
filedirty(f->node);
} else {
/* read the remote file or directory */
if(!ISCACHED(f->node)){
filefree(f->node);
if(f->node->d->qid.type & QTDIR){
invalidate(f->node);
if(readdir(f->node) < 0)
return nosuchfile;
} else {
if(readfile(f->node) < 0)
return errstring;
}
CACHED(f->node);
}
}
rhdr.qid = f->node->d->qid;
f->open = 1;
f->node->opens++;
return 0;
}
char*
rcreate(Fid *f)
{
char *name;
if((f->node->d->qid.type&QTDIR) == 0)
return "not a directory";
name = thdr.name;
f->node = extendpath(f->node, s_copy(name));
uncache(f->node);
if(thdr.perm & DMDIR){
if(createdir(f->node) < 0)
return "permission denied";
} else
filedirty(f->node);
invalidate(f->node->parent);
uncache(f->node->parent);
rhdr.qid = f->node->d->qid;
f->open = 1;
f->node->opens++;
return 0;
}
char*
rread(Fid *f)
{
long off;
int n, cnt, rv;
Node *np;
rhdr.count = 0;
off = thdr.offset;
cnt = thdr.count;
if(cnt > messagesize-IOHDRSZ)
cnt = messagesize-IOHDRSZ;
if(f->node->d->qid.type & QTDIR){
rv = 0;
for(np = f->node->children; np != nil; np = np->sibs){
if(!ISVALID(np))
continue;
if(off <= BIT16SZ)
break;
n = convD2M(np->d, mbuf, messagesize-IOHDRSZ);
off -= n;
}
for(; rv < cnt && np != nil; np = np->sibs){
if(!ISVALID(np))
continue;
if(np->d->mode & DMSYML)
fixsymbolic(np);
n = convD2M(np->d, mbuf + rv, cnt-rv);
if(n <= BIT16SZ)
break;
rv += n;
}
} else {
/* reread file if it's fallen out of the cache */
if(!ISCACHED(f->node))
if(readfile(f->node) < 0)
return errstring;
CACHED(f->node);
rv = fileread(f->node, (char*)mbuf, off, cnt);
if(rv < 0)
return errstring;
}
rhdr.data = (char*)mbuf;
rhdr.count = rv;
return 0;
}
char*
rwrite(Fid *f)
{
long off;
int cnt;
if(f->node->d->qid.type & QTDIR)
return "directories are not writable";
rhdr.count = 0;
off = thdr.offset;
cnt = thdr.count;
cnt = filewrite(f->node, thdr.data, off, cnt);
if(cnt < 0)
return errstring;
filedirty(f->node);
rhdr.count = cnt;
return 0;
}
char *
rclunk(Fid *f)
{
if(fileisdirty(f->node)){
if(createfile(f->node) < 0)
fprint(2, "ftpfs: couldn't create %s\n", f->node->d->name);
fileclean(f->node);
uncache(f->node);
}
if(f->open){
f->open = 0;
f->node->opens--;
}
f->busy = 0;
return 0;
}
/*
* remove is an implicit clunk
*/
char *
rremove(Fid *f)
{
char *e;
e = nil;
if(QTDIR & f->node->d->qid.type){
if(removedir(f->node) < 0)
e = errstring;
} else {
if(removefile(f->node) < 0)
e = errstring;
}
uncache(f->node->parent);
uncache(f->node);
if(e == nil)
INVALID(f->node);
f->busy = 0;
return e;
}
char *
rstat(Fid *f)
{
Node *p;
p = f->node->parent;
if(!ISCACHED(p)){
invalidate(p);
readdir(p);
CACHED(p);
}
if(!ISVALID(f->node))
return nosuchfile;
if(p->chdirunknown)
fixsymbolic(f->node);
rhdr.nstat = convD2M(f->node->d, mbuf, messagesize-IOHDRSZ);
rhdr.stat = mbuf;
return 0;
}
char *
rwstat(Fid *f)
{
USED(f);
return "wstat not implemented";
}
/*
* print message and die
*/
void
fatal(char *fmt, ...)
{
va_list arg;
char buf[8*1024];
dying = 1;
va_start(arg, fmt);
vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg);
va_end(arg);
fprint(2, "ftpfs: %s\n", buf);
if(kapid > 0)
postnote(PNGROUP, kapid, "die");
exits(buf);
}
/*
* like strncpy but make sure there's a terminating null
*/
void*
safecpy(void *to, void *from, int n)
{
char *a = ((char*)to) + n - 1;
strncpy(to, from, n);
*a = 0;
return to;
}
/*
* set the error string
*/
int
seterr(char *fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vsnprint(errstring, sizeof errstring, fmt, arg);
va_end(arg);
return -1;
}
/*
* create a new node
*/
Node*
newnode(Node *parent, String *name)
{
Node *np;
static ulong path;
Dir d;
np = mallocz(sizeof(Node), 1);
if(np == 0)
fatal("out of memory");
np->children = 0;
if(parent){
np->parent = parent;
np->sibs = parent->children;
parent->children = np;
np->depth = parent->depth + 1;
d.dev = 0; /* not stat'd */
} else {
/* the root node */
np->parent = np;
np->sibs = 0;
np->depth = 0;
d.dev = 1;
}
np->remname = name;
d.name = s_to_c(name);
d.atime = time(0);
d.mtime = d.atime;
d.uid = nouid;
d.gid = nouid;
d.muid = nouid;
np->fp = 0;
d.qid.path = ++path;
d.qid.vers = 0;
d.qid.type = QTFILE;
d.type = 0;
np->d = reallocdir(&d, 0);
return np;
}
/*
* walk one down the local mirror of the remote directory tree
*/
Node*
extendpath(Node *parent, String *elem)
{
Node *np;
for(np = parent->children; np; np = np->sibs)
if(strcmp(s_to_c(np->remname), s_to_c(elem)) == 0){
s_free(elem);
return np;
}
return newnode(parent, elem);
}
/*
* flush the cached file, write it back if it's dirty
*/
void
uncache(Node *np)
{
if(fileisdirty(np))
createfile(np);
filefree(np);
UNCACHED(np);
}
/*
* invalidate all children of a node
*/
void
invalidate(Node *node)
{
Node *np;
if(node->opens)
return; /* don't invalidate something that's open */
uncachedir(node, 0);
/* invalidate children */
for(np = node->children; np; np = np->sibs){
if(np->opens)
continue; /* don't invalidate something that's open */
UNCACHED(np);
invalidate(np);
np->d->dev = 0;
}
}
/*
* make a top level tops-20 directory. They are automaticly valid.
*/
Node*
newtopsdir(char *name)
{
Node *np;
np = extendpath(remroot, s_copy(name));
if(!ISVALID(np)){
np->d->qid.type = QTDIR;
np->d->atime = time(0);
np->d->mtime = np->d->atime;
np->d->uid = "?uid?";
np->d->gid = "?uid?";
np->d->muid = "?uid?";
np->d->mode = DMDIR|0777;
np->d->length = 0;
np->d = reallocdir(np->d, 1);
if(changedir(np) >= 0)
VALID(np);
}
return np;
}
/*
* figure out if a symbolic link is to a directory or a file
*/
void
fixsymbolic(Node *node)
{
if(changedir(node) == 0){
node->d->mode |= DMDIR;
node->d->qid.type = QTDIR;
} else
node->d->qid.type = QTFILE;
node->d->mode &= ~DMSYML;
}
|