1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5 #include <String.h>
6 #include "ftpfs.h"
7
8 /* an active fid */
9 typedef struct Fid Fid;
10 struct Fid
11 {
12 int fid;
13 Node *node; /* path to remote file */
14 int busy;
15 Fid *next;
16 int open;
17 };
18
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];
25 Fcall rhdr;
26 Fcall thdr;
27 int debug;
28 int usenlst;
29 int usetls;
30 char *ext;
31 int quiet;
32 int kapid = -1;
33 int dying; /* set when any process decides to die */
34 int dokeepalive;
35
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*),
41 *rauth(Fid*);;
42 void mountinit(char*);
43 void io(void);
44 int readpdir(Node*);
45
46 char *(*fcalls[])(Fid*) = {
47 [Tflush] rflush,
48 [Tversion] rversion,
49 [Tattach] rattach,
50 [Tauth] rauth,
51 [Twalk] rwalk,
52 [Topen] ropen,
53 [Tcreate] rcreate,
54 [Tread] rread,
55 [Twrite] rwrite,
56 [Tclunk] rclunk,
57 [Tremove] rremove,
58 [Tstat] rstat,
59 [Twstat] rwstat,
60 };
61
62 /* these names are matched as prefixes, so VMS must precede VM */
63 OS oslist[] = {
64 { Plan9, "Plan 9", },
65 { Plan9, "Plan9", },
66 { Plan9, "UNIX Type: L8 Version: Plan 9", },
67 { Unix, "SUN", },
68 { Unix, "UNIX", },
69 { VMS, "VMS", },
70 { VM, "VM", },
71 { Tops, "TOPS", },
72 { MVS, "MVS", },
73 { NetWare, "NetWare", },
74 { NetWare, "NETWARE", },
75 { OS½, "OS/2", },
76 { TSO, "TSO", },
77 { NT, "Windows_NT", }, /* DOS like interface */
78 { NT, "WINDOWS_NT", }, /* Unix like interface */
79 { Unknown, 0 },
80 };
81
82 char *nouid = "?uid?";
83
84 #define S2P(x) (((ulong)(x)) & 0xffffff)
85
86 char *nosuchfile = "file does not exist";
87 char *keyspec = "";
88
89 void
usage(void)90 usage(void)
91 {
92 fprint(2, "ftpfs [-/dqnt] [-a passwd] [-m mountpoint] [-e ext] [-k keyspec] [-o os] [-r root] [net!]address\n");
93 exits("usage");
94 }
95
96 void
main(int argc,char * argv[])97 main(int argc, char *argv[])
98 {
99 char *mountroot = 0;
100 char *mountpoint = "/n/ftp";
101 char *cpassword = 0;
102 char *cp;
103 int p[2];
104 OS *o;
105
106 defos = Unix;
107 user = strdup(getuser());
108 usetls = 0;
109
110 ARGBEGIN {
111 case '/':
112 mountroot = "/";
113 break;
114 case 'a':
115 cpassword = ARGF();
116 break;
117 case 'd':
118 debug = 1;
119 break;
120 case 'k':
121 keyspec = EARGF(usage());
122 break;
123 case 'K':
124 dokeepalive = 1;
125 break;
126 case 'm':
127 mountpoint = ARGF();
128 break;
129 case 'n':
130 usenlst = 1;
131 break;
132 case 'e':
133 ext = ARGF();
134 break;
135 case 't':
136 usetls = 1;
137 break;
138 case 'o':
139 cp = ARGF();
140 for(o = oslist; o->os != Unknown; o++)
141 if(strncmp(cp, o->name, strlen(o->name)) == 0){
142 defos = o->os;
143 break;
144 }
145 break;
146 case 'r':
147 mountroot = ARGF();
148 break;
149 case 'q':
150 quiet = 1;
151 break;
152 } ARGEND
153 if(argc != 1)
154 usage();
155
156 /* get a pipe to mount and run 9fs on */
157 if(pipe(p) < 0)
158 fatal("pipe failed: %r");
159 mfd = p[0];
160
161 /* initial handshakes with remote side */
162 hello(*argv);
163 if(cpassword == 0)
164 rlogin(*argv, keyspec);
165 else
166 clogin("anonymous", cpassword);
167 preamble(mountroot);
168
169 /* start the 9fs protocol */
170 switch(rfork(RFPROC|RFNAMEG|RFENVG|RFFDG|RFNOTEG|RFREND)){
171 case -1:
172 fatal("fork: %r");
173 case 0:
174 /* seal off standard input/output */
175 close(0);
176 open("/dev/null", OREAD);
177 close(1);
178 open("/dev/null", OWRITE);
179
180 close(p[1]);
181 fmtinstall('F', fcallfmt); /* debugging */
182 fmtinstall('D', dirfmt); /* expected by %F */
183 fmtinstall('M', dirmodefmt); /* expected by %F */
184 io();
185 quit();
186 break;
187 default:
188 close(p[0]);
189 if(mount(p[1], -1, mountpoint, MREPL|MCREATE, "") < 0)
190 fatal("mount failed: %r");
191 }
192 exits(0);
193 }
194
195 /*
196 * lookup an fid. if not found, create a new one.
197 */
198 Fid *
newfid(int fid)199 newfid(int fid)
200 {
201 Fid *f, *ff;
202
203 ff = 0;
204 for(f = fids; f; f = f->next){
205 if(f->fid == fid){
206 if(f->busy)
207 return f;
208 else{
209 ff = f;
210 break;
211 }
212 } else if(!ff && !f->busy)
213 ff = f;
214 }
215 if(ff == 0){
216 ff = mallocz(sizeof(*f), 1);
217 ff->next = fids;
218 fids = ff;
219 }
220 ff->node = nil;
221 ff->fid = fid;
222 return ff;
223 }
224
225 /*
226 * a process that sends keep alive messages to
227 * keep the server from shutting down the connection
228 */
229 int
kaproc(void)230 kaproc(void)
231 {
232 int pid;
233
234 if(!dokeepalive)
235 return -1;
236
237 switch(pid = rfork(RFPROC|RFMEM)){
238 case -1:
239 return -1;
240 case 0:
241 break;
242 default:
243 return pid;
244 }
245
246 while(!dying){
247 sleep(5000);
248 nop();
249 }
250
251 _exits(0);
252 return -1;
253 }
254
255 void
io(void)256 io(void)
257 {
258 char *err, buf[ERRMAX];
259 int n;
260
261 kapid = kaproc();
262
263 while(!dying){
264 n = read9pmsg(mfd, mdata, messagesize);
265 if(n <= 0){
266 errstr(buf, sizeof buf);
267 if(buf[0]=='\0' || strstr(buf, "hungup"))
268 exits("");
269 fatal("mount read: %s\n", buf);
270 }
271 if(convM2S(mdata, n, &thdr) == 0)
272 continue;
273
274 if(debug)
275 fprint(2, "<-%F\n", &thdr);/**/
276
277 if(!fcalls[thdr.type])
278 err = "bad fcall type";
279 else
280 err = (*fcalls[thdr.type])(newfid(thdr.fid));
281 if(err){
282 rhdr.type = Rerror;
283 rhdr.ename = err;
284 }else{
285 rhdr.type = thdr.type + 1;
286 rhdr.fid = thdr.fid;
287 }
288 rhdr.tag = thdr.tag;
289 if(debug)
290 fprint(2, "->%F\n", &rhdr);/**/
291 n = convS2M(&rhdr, mdata, messagesize);
292 if(write(mfd, mdata, n) != n)
293 fatal("mount write");
294 }
295 }
296
297 char*
rnop(Fid * f)298 rnop(Fid *f)
299 {
300 USED(f);
301 return 0;
302 }
303
304 char*
rversion(Fid *)305 rversion(Fid*)
306 {
307 if(thdr.msize > sizeof(mdata))
308 rhdr.msize = messagesize;
309 else
310 rhdr.msize = thdr.msize;
311 messagesize = thdr.msize;
312
313 if(strncmp(thdr.version, "9P2000", 6) != 0)
314 return "unknown 9P version";
315 rhdr.version = "9P2000";
316 return nil;
317 }
318
319 char*
rflush(Fid *)320 rflush(Fid*)
321 {
322 return 0;
323 }
324
325 char*
rauth(Fid *)326 rauth(Fid*)
327 {
328 return "auth unimplemented";
329 }
330
331 char*
rattach(Fid * f)332 rattach(Fid *f)
333 {
334 f->busy = 1;
335 f->node = remroot;
336 rhdr.qid = f->node->d->qid;
337 return 0;
338 }
339
340 char*
rwalk(Fid * f)341 rwalk(Fid *f)
342 {
343 Node *np;
344 Fid *nf;
345 char **elems;
346 int i, nelems;
347 char *err;
348 Node *node;
349
350 /* clone fid */
351 nf = nil;
352 if(thdr.newfid != thdr.fid){
353 nf = newfid(thdr.newfid);
354 if(nf->busy)
355 return "newfid in use";
356 nf->busy = 1;
357 nf->node = f->node;
358 f = nf;
359 }
360
361 err = nil;
362 elems = thdr.wname;
363 nelems = thdr.nwname;
364 node = f->node;
365 rhdr.nwqid = 0;
366 if(nelems > 0){
367 /* walk fid */
368 for(i=0; i<nelems && i<MAXWELEM; i++){
369 if((node->d->qid.type & QTDIR) == 0){
370 err = "not a directory";
371 break;
372 }
373 if(strcmp(elems[i], ".") == 0){
374 Found:
375 rhdr.wqid[i] = node->d->qid;
376 rhdr.nwqid++;
377 continue;
378 }
379 if(strcmp(elems[i], "..") == 0){
380 node = node->parent;
381 goto Found;
382 }
383 if(strcmp(elems[i], ".flush.ftpfs") == 0){
384 /* hack to flush the cache */
385 invalidate(node);
386 readdir(node);
387 goto Found;
388 }
389
390 /* some top level names are special */
391 if((os == Tops || os == VM || os == VMS) && node == remroot){
392 np = newtopsdir(elems[i]);
393 if(np){
394 node = np;
395 goto Found;
396 } else {
397 err = nosuchfile;
398 break;
399 }
400 }
401
402 /* everything else */
403 node = extendpath(node, s_copy(elems[i]));
404 if(ISCACHED(node->parent)){
405 /* the cache of the parent is good, believe it */
406 if(!ISVALID(node)){
407 err = nosuchfile;
408 break;
409 }
410 if(node->parent->chdirunknown || (node->d->mode & DMSYML))
411 fixsymbolic(node);
412 } else if(!ISVALID(node)){
413 /* this isn't a valid node, try cd'ing */
414 if(changedir(node) == 0){
415 node->d->qid.type = QTDIR;
416 node->d->mode |= DMDIR;
417 }else{
418 node->d->qid.type = QTFILE;
419 node->d->mode &= ~DMDIR;
420 }
421 }
422 goto Found;
423 }
424 if(i == 0 && err == 0)
425 err = "file does not exist";
426 }
427
428 /* clunk a newly cloned fid if the walk didn't succeed */
429 if(nf != nil && (err != nil || rhdr.nwqid<nelems)){
430 nf->busy = 0;
431 nf->fid = 0;
432 }
433
434 /* if it all worked, point the fid to the enw node */
435 if(err == nil)
436 f->node = node;
437
438 return err;
439 }
440
441 char *
ropen(Fid * f)442 ropen(Fid *f)
443 {
444 int mode;
445
446 mode = thdr.mode;
447 if(f->node->d->qid.type & QTDIR)
448 if(mode != OREAD)
449 return "permission denied";
450
451 if(mode & OTRUNC){
452 uncache(f->node);
453 uncache(f->node->parent);
454 filedirty(f->node);
455 } else {
456 /* read the remote file or directory */
457 if(!ISCACHED(f->node)){
458 filefree(f->node);
459 if(f->node->d->qid.type & QTDIR){
460 invalidate(f->node);
461 if(readdir(f->node) < 0)
462 return nosuchfile;
463 } else {
464 if(readfile(f->node) < 0)
465 return errstring;
466 }
467 CACHED(f->node);
468 }
469 }
470
471 rhdr.qid = f->node->d->qid;
472 f->open = 1;
473 f->node->opens++;
474 return 0;
475 }
476
477 char*
rcreate(Fid * f)478 rcreate(Fid *f)
479 {
480 char *name;
481
482 if((f->node->d->qid.type&QTDIR) == 0)
483 return "not a directory";
484
485 name = thdr.name;
486 f->node = extendpath(f->node, s_copy(name));
487 uncache(f->node);
488 if(thdr.perm & DMDIR){
489 if(createdir(f->node) < 0)
490 return "permission denied";
491 } else
492 filedirty(f->node);
493 invalidate(f->node->parent);
494 uncache(f->node->parent);
495
496 rhdr.qid = f->node->d->qid;
497 f->open = 1;
498 f->node->opens++;
499 return 0;
500 }
501
502 char*
rread(Fid * f)503 rread(Fid *f)
504 {
505 long off;
506 int n, cnt, rv;
507 Node *np;
508
509 rhdr.count = 0;
510 off = thdr.offset;
511 cnt = thdr.count;
512 if(cnt > messagesize-IOHDRSZ)
513 cnt = messagesize-IOHDRSZ;
514
515 if(f->node->d->qid.type & QTDIR){
516 rv = 0;
517 for(np = f->node->children; np != nil; np = np->sibs){
518 if(!ISVALID(np))
519 continue;
520 if(off <= BIT16SZ)
521 break;
522 n = convD2M(np->d, mbuf, messagesize-IOHDRSZ);
523 off -= n;
524 }
525 for(; rv < cnt && np != nil; np = np->sibs){
526 if(!ISVALID(np))
527 continue;
528 if(np->d->mode & DMSYML)
529 fixsymbolic(np);
530 n = convD2M(np->d, mbuf + rv, cnt-rv);
531 if(n <= BIT16SZ)
532 break;
533 rv += n;
534 }
535 } else {
536 /* reread file if it's fallen out of the cache */
537 if(!ISCACHED(f->node))
538 if(readfile(f->node) < 0)
539 return errstring;
540 CACHED(f->node);
541 rv = fileread(f->node, (char*)mbuf, off, cnt);
542 if(rv < 0)
543 return errstring;
544 }
545
546 rhdr.data = (char*)mbuf;
547 rhdr.count = rv;
548 return 0;
549 }
550
551 char*
rwrite(Fid * f)552 rwrite(Fid *f)
553 {
554 long off;
555 int cnt;
556
557 if(f->node->d->qid.type & QTDIR)
558 return "directories are not writable";
559
560 rhdr.count = 0;
561 off = thdr.offset;
562 cnt = thdr.count;
563 cnt = filewrite(f->node, thdr.data, off, cnt);
564 if(cnt < 0)
565 return errstring;
566 filedirty(f->node);
567 rhdr.count = cnt;
568 return 0;
569 }
570
571 char *
rclunk(Fid * f)572 rclunk(Fid *f)
573 {
574 if(fileisdirty(f->node)){
575 if(createfile(f->node) < 0)
576 fprint(2, "ftpfs: couldn't create %s\n", f->node->d->name);
577 fileclean(f->node);
578 uncache(f->node);
579 }
580 if(f->open){
581 f->open = 0;
582 f->node->opens--;
583 }
584 f->busy = 0;
585 return 0;
586 }
587
588 /*
589 * remove is an implicit clunk
590 */
591 char *
rremove(Fid * f)592 rremove(Fid *f)
593 {
594 char *e;
595 e = nil;
596 if(QTDIR & f->node->d->qid.type){
597 if(removedir(f->node) < 0)
598 e = errstring;
599 } else {
600 if(removefile(f->node) < 0)
601 e = errstring;
602 }
603 uncache(f->node->parent);
604 uncache(f->node);
605 if(e == nil)
606 INVALID(f->node);
607 f->busy = 0;
608 return e;
609 }
610
611 char *
rstat(Fid * f)612 rstat(Fid *f)
613 {
614 Node *p;
615
616 p = f->node->parent;
617 if(!ISCACHED(p)){
618 invalidate(p);
619 readdir(p);
620 CACHED(p);
621 }
622 if(!ISVALID(f->node))
623 return nosuchfile;
624 if(p->chdirunknown)
625 fixsymbolic(f->node);
626 rhdr.nstat = convD2M(f->node->d, mbuf, messagesize-IOHDRSZ);
627 rhdr.stat = mbuf;
628 return 0;
629 }
630
631 char *
rwstat(Fid * f)632 rwstat(Fid *f)
633 {
634 USED(f);
635 return "wstat not implemented";
636 }
637
638 /*
639 * print message and die
640 */
641 void
fatal(char * fmt,...)642 fatal(char *fmt, ...)
643 {
644 va_list arg;
645 char buf[8*1024];
646
647 dying = 1;
648
649 va_start(arg, fmt);
650 vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg);
651 va_end(arg);
652
653 fprint(2, "ftpfs: %s\n", buf);
654 if(kapid > 0)
655 postnote(PNGROUP, kapid, "die");
656 exits(buf);
657 }
658
659 /*
660 * like strncpy but make sure there's a terminating null
661 */
662 void*
safecpy(void * to,void * from,int n)663 safecpy(void *to, void *from, int n)
664 {
665 char *a = ((char*)to) + n - 1;
666
667 strncpy(to, from, n);
668 *a = 0;
669 return to;
670 }
671
672 /*
673 * set the error string
674 */
675 int
seterr(char * fmt,...)676 seterr(char *fmt, ...)
677 {
678 va_list arg;
679
680 va_start(arg, fmt);
681 vsnprint(errstring, sizeof errstring, fmt, arg);
682 va_end(arg);
683 return -1;
684 }
685
686 /*
687 * create a new node
688 */
689 Node*
newnode(Node * parent,String * name)690 newnode(Node *parent, String *name)
691 {
692 Node *np;
693 static ulong path;
694 Dir d;
695
696 np = mallocz(sizeof(Node), 1);
697 if(np == 0)
698 fatal("out of memory");
699
700 np->children = 0;
701 if(parent){
702 np->parent = parent;
703 np->sibs = parent->children;
704 parent->children = np;
705 np->depth = parent->depth + 1;
706 d.dev = 0; /* not stat'd */
707 } else {
708 /* the root node */
709 np->parent = np;
710 np->sibs = 0;
711 np->depth = 0;
712 d.dev = 1;
713 }
714 np->remname = name;
715 d.name = s_to_c(name);
716 d.atime = time(0);
717 d.mtime = d.atime;
718 d.uid = nouid;
719 d.gid = nouid;
720 d.muid = nouid;
721 np->fp = 0;
722 d.qid.path = ++path;
723 d.qid.vers = 0;
724 d.qid.type = QTFILE;
725 d.type = 0;
726 np->d = reallocdir(&d, 0);
727
728 return np;
729 }
730
731 /*
732 * walk one down the local mirror of the remote directory tree
733 */
734 Node*
extendpath(Node * parent,String * elem)735 extendpath(Node *parent, String *elem)
736 {
737 Node *np;
738
739 for(np = parent->children; np; np = np->sibs)
740 if(strcmp(s_to_c(np->remname), s_to_c(elem)) == 0){
741 s_free(elem);
742 return np;
743 }
744
745 return newnode(parent, elem);
746 }
747
748 /*
749 * flush the cached file, write it back if it's dirty
750 */
751 void
uncache(Node * np)752 uncache(Node *np)
753 {
754 if(fileisdirty(np))
755 createfile(np);
756 filefree(np);
757 UNCACHED(np);
758 }
759
760 /*
761 * invalidate all children of a node
762 */
763 void
invalidate(Node * node)764 invalidate(Node *node)
765 {
766 Node *np;
767
768 if(node->opens)
769 return; /* don't invalidate something that's open */
770
771 uncachedir(node, 0);
772
773 /* invalidate children */
774 for(np = node->children; np; np = np->sibs){
775 if(np->opens)
776 continue; /* don't invalidate something that's open */
777 UNCACHED(np);
778 invalidate(np);
779 np->d->dev = 0;
780 }
781 }
782
783 /*
784 * make a top level tops-20 directory. They are automaticly valid.
785 */
786 Node*
newtopsdir(char * name)787 newtopsdir(char *name)
788 {
789 Node *np;
790
791 np = extendpath(remroot, s_copy(name));
792 if(!ISVALID(np)){
793 np->d->qid.type = QTDIR;
794 np->d->atime = time(0);
795 np->d->mtime = np->d->atime;
796 np->d->uid = "?uid?";
797 np->d->gid = "?uid?";
798 np->d->muid = "?uid?";
799 np->d->mode = DMDIR|0777;
800 np->d->length = 0;
801 np->d = reallocdir(np->d, 1);
802
803 if(changedir(np) >= 0)
804 VALID(np);
805 }
806 return np;
807 }
808
809 /*
810 * figure out if a symbolic link is to a directory or a file
811 */
812 void
fixsymbolic(Node * node)813 fixsymbolic(Node *node)
814 {
815 if(changedir(node) == 0){
816 node->d->mode |= DMDIR;
817 node->d->qid.type = QTDIR;
818 } else
819 node->d->qid.type = QTFILE;
820 node->d->mode &= ~DMSYML;
821 }
822