9 typedef struct Fid Fid;
13 Node *node; /* path to remote file */
19 Fid *fids; /* linked list of fids */
20 char errstring[128]; /* error to return */
21 int mfd; /* fd for 9fs */
22 int messagesize = 4*1024*IOHDRSZ;
23 uchar mdata[8*1024*IOHDRSZ];
24 uchar mbuf[8*1024*IOHDRSZ];
33 int dying; /* set when any process decides to die */
36 char *rflush(Fid*), *rnop(Fid*), *rversion(Fid*),
37 *rattach(Fid*), *rclone(Fid*), *rwalk(Fid*),
38 *rclwalk(Fid*), *ropen(Fid*), *rcreate(Fid*),
39 *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
40 *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
42 void mountinit(char*);
46 char *(*fcalls[])(Fid*) = {
62 /* these names are matched as prefixes, so VMS must precede VM */
66 { Plan9, "UNIX Type: L8 Version: Plan 9", },
73 { NetWare, "NetWare", },
74 { NetWare, "NETWARE", },
77 { NT, "Windows_NT", }, /* DOS like interface */
78 { NT, "WINDOWS_NT", }, /* Unix like interface */
82 char *nouid = "?uid?";
84 #define S2P(x) (((ulong)(x)) & 0xffffff)
86 char *nosuchfile = "file does not exist";
92 fprint(2, "ftpfs [-/dqnt] [-a passwd] [-m mountpoint] [-e ext] [-k keyspec] [-o os] [-r root] [net!]address\n");
97 main(int argc, char *argv[])
100 char *mountpoint = "/n/ftp";
107 user = strdup(getuser());
121 keyspec = EARGF(usage());
140 for(o = oslist; o->os != Unknown; o++)
141 if(strncmp(cp, o->name, strlen(o->name)) == 0){
156 /* get a pipe to mount and run 9fs on */
158 fatal("pipe failed: %r");
161 /* initial handshakes with remote side */
164 rlogin(*argv, keyspec);
166 clogin("anonymous", cpassword);
169 /* start the 9fs protocol */
170 switch(rfork(RFPROC|RFNAMEG|RFENVG|RFFDG|RFNOTEG|RFREND)){
174 /* seal off standard input/output */
176 open("/dev/null", OREAD);
178 open("/dev/null", OWRITE);
181 fmtinstall('F', fcallfmt); /* debugging */
182 fmtinstall('D', dirfmt); /* expected by %F */
183 fmtinstall('M', dirmodefmt); /* expected by %F */
189 if(mount(p[1], -1, mountpoint, MREPL|MCREATE, "") < 0)
190 fatal("mount failed: %r");
196 * lookup an fid. if not found, create a new one.
204 for(f = fids; f; f = f->next){
212 } else if(!ff && !f->busy)
216 ff = mallocz(sizeof(*f), 1);
226 * a process that sends keep alive messages to
227 * keep the server from shutting down the connection
237 switch(pid = rfork(RFPROC|RFMEM)){
258 char *err, buf[ERRMAX];
264 n = read9pmsg(mfd, mdata, messagesize);
266 errstr(buf, sizeof buf);
267 if(buf[0]=='\0' || strstr(buf, "hungup"))
269 fatal("mount read: %s\n", buf);
273 if(convM2S(mdata, n, &thdr) == 0)
277 fprint(2, "<-%F\n", &thdr);/**/
279 if(!fcalls[thdr.type])
280 err = "bad fcall type";
282 err = (*fcalls[thdr.type])(newfid(thdr.fid));
287 rhdr.type = thdr.type + 1;
292 fprint(2, "->%F\n", &rhdr);/**/
293 n = convS2M(&rhdr, mdata, messagesize);
294 if(write(mfd, mdata, n) != n)
295 fatal("mount write");
310 return "version: message size too small";
311 if(thdr.msize > sizeof mdata)
312 rhdr.msize = sizeof mdata;
314 rhdr.msize = thdr.msize;
315 messagesize = rhdr.msize;
317 if(strncmp(thdr.version, "9P2000", 6) != 0)
318 return "unknown 9P version";
319 rhdr.version = "9P2000";
332 return "auth unimplemented";
340 rhdr.qid = f->node->d->qid;
356 if(thdr.newfid != thdr.fid){
357 nf = newfid(thdr.newfid);
359 return "newfid in use";
367 nelems = thdr.nwname;
372 for(i=0; i<nelems && i<MAXWELEM; i++){
373 if((node->d->qid.type & QTDIR) == 0){
374 err = "not a directory";
377 if(strcmp(elems[i], ".") == 0){
379 rhdr.wqid[i] = node->d->qid;
383 if(strcmp(elems[i], "..") == 0){
387 if(strcmp(elems[i], ".flush.ftpfs") == 0){
388 /* hack to flush the cache */
394 /* some top level names are special */
395 if((os == Tops || os == VM || os == VMS) && node == remroot){
396 np = newtopsdir(elems[i]);
406 /* everything else */
407 node = extendpath(node, s_copy(elems[i]));
408 if(ISCACHED(node->parent)){
409 /* the cache of the parent is good, believe it */
414 if(node->parent->chdirunknown || (node->d->mode & DMSYML))
416 } else if(!ISVALID(node)){
417 /* this isn't a valid node, try cd'ing */
418 if(changedir(node) == 0){
419 node->d->qid.type = QTDIR;
420 node->d->mode |= DMDIR;
422 node->d->qid.type = QTFILE;
423 node->d->mode &= ~DMDIR;
428 if(i == 0 && err == 0)
429 err = "file does not exist";
432 /* clunk a newly cloned fid if the walk didn't succeed */
433 if(nf != nil && (err != nil || rhdr.nwqid<nelems)){
438 /* if it all worked, point the fid to the enw node */
451 if(f->node->d->qid.type & QTDIR)
453 return "permission denied";
457 uncache(f->node->parent);
460 /* read the remote file or directory */
461 if(!ISCACHED(f->node)){
463 if(f->node->d->qid.type & QTDIR){
465 if(readdir(f->node) < 0)
468 if(readfile(f->node) < 0)
475 rhdr.qid = f->node->d->qid;
486 if((f->node->d->qid.type&QTDIR) == 0)
487 return "not a directory";
490 f->node = extendpath(f->node, s_copy(name));
492 if(thdr.perm & DMDIR){
493 if(createdir(f->node) < 0)
494 return "permission denied";
497 invalidate(f->node->parent);
498 uncache(f->node->parent);
500 rhdr.qid = f->node->d->qid;
516 if(cnt > messagesize-IOHDRSZ)
517 cnt = messagesize-IOHDRSZ;
519 if(f->node->d->qid.type & QTDIR){
521 for(np = f->node->children; np != nil; np = np->sibs){
526 n = convD2M(np->d, mbuf, messagesize-IOHDRSZ);
529 for(; rv < cnt && np != nil; np = np->sibs){
532 if(np->d->mode & DMSYML)
534 n = convD2M(np->d, mbuf + rv, cnt-rv);
540 /* reread file if it's fallen out of the cache */
541 if(!ISCACHED(f->node))
542 if(readfile(f->node) < 0)
545 rv = fileread(f->node, (char*)mbuf, off, cnt);
550 rhdr.data = (char*)mbuf;
561 if(f->node->d->qid.type & QTDIR)
562 return "directories are not writable";
567 cnt = filewrite(f->node, thdr.data, off, cnt);
578 if(fileisdirty(f->node)){
579 if(createfile(f->node) < 0)
580 fprint(2, "ftpfs: couldn't create %s\n", f->node->d->name);
593 * remove is an implicit clunk
600 if(QTDIR & f->node->d->qid.type){
601 if(removedir(f->node) < 0)
604 if(removefile(f->node) < 0)
607 uncache(f->node->parent);
626 if(!ISVALID(f->node))
629 fixsymbolic(f->node);
630 rhdr.nstat = convD2M(f->node->d, mbuf, messagesize-IOHDRSZ);
639 return "wstat not implemented";
643 * print message and die
646 fatal(char *fmt, ...)
654 vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg);
657 fprint(2, "ftpfs: %s\n", buf);
659 postnote(PNGROUP, kapid, "die");
664 * like strncpy but make sure there's a terminating null
667 safecpy(void *to, void *from, int n)
669 char *a = ((char*)to) + n - 1;
671 strncpy(to, from, n);
677 * set the error string
680 seterr(char *fmt, ...)
685 vsnprint(errstring, sizeof errstring, fmt, arg);
694 newnode(Node *parent, String *name)
700 np = mallocz(sizeof(Node), 1);
702 fatal("out of memory");
707 np->sibs = parent->children;
708 parent->children = np;
709 np->depth = parent->depth + 1;
710 d.dev = 0; /* not stat'd */
719 d.name = s_to_c(name);
730 np->d = reallocdir(&d, 0);
736 * walk one down the local mirror of the remote directory tree
739 extendpath(Node *parent, String *elem)
743 for(np = parent->children; np; np = np->sibs)
744 if(strcmp(s_to_c(np->remname), s_to_c(elem)) == 0){
749 return newnode(parent, elem);
753 * flush the cached file, write it back if it's dirty
765 * invalidate all children of a node
768 invalidate(Node *node)
773 return; /* don't invalidate something that's open */
777 /* invalidate children */
778 for(np = node->children; np; np = np->sibs){
780 continue; /* don't invalidate something that's open */
788 * make a top level tops-20 directory. They are automaticly valid.
791 newtopsdir(char *name)
795 np = extendpath(remroot, s_copy(name));
797 np->d->qid.type = QTDIR;
798 np->d->atime = time(0);
799 np->d->mtime = np->d->atime;
800 np->d->uid = "?uid?";
801 np->d->gid = "?uid?";
802 np->d->muid = "?uid?";
803 np->d->mode = DMDIR|0777;
805 np->d = reallocdir(np->d, 1);
807 if(changedir(np) >= 0)
814 * figure out if a symbolic link is to a directory or a file
817 fixsymbolic(Node *node)
819 if(changedir(node) == 0){
820 node->d->mode |= DMDIR;
821 node->d->qid.type = QTDIR;
823 node->d->qid.type = QTFILE;
824 node->d->mode &= ~DMSYML;