]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/ftpfs/ftpfs.c
merge
[plan9front.git] / sys / src / cmd / ip / ftpfs / ftpfs.c
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
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
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 *
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
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
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(n == 0)
272                         continue;
273                 if(convM2S(mdata, n, &thdr) == 0)
274                         continue;
275
276                 if(debug)
277                         fprint(2, "<-%F\n", &thdr);/**/
278
279                 if(!fcalls[thdr.type])
280                         err = "bad fcall type";
281                 else
282                         err = (*fcalls[thdr.type])(newfid(thdr.fid));
283                 if(err){
284                         rhdr.type = Rerror;
285                         rhdr.ename = err;
286                 }else{
287                         rhdr.type = thdr.type + 1;
288                         rhdr.fid = thdr.fid;
289                 }
290                 rhdr.tag = thdr.tag;
291                 if(debug)
292                         fprint(2, "->%F\n", &rhdr);/**/
293                 n = convS2M(&rhdr, mdata, messagesize);
294                 if(write(mfd, mdata, n) != n)
295                         fatal("mount write");
296         }
297 }
298
299 char*
300 rnop(Fid *f)
301 {
302         USED(f);
303         return 0;
304 }
305
306 char*
307 rversion(Fid*)
308 {
309         if(thdr.msize < 256)
310                 return "version: message size too small";
311         if(thdr.msize > sizeof mdata)
312                 rhdr.msize = sizeof mdata;
313         else
314                 rhdr.msize = thdr.msize;
315         messagesize = rhdr.msize;
316
317         if(strncmp(thdr.version, "9P2000", 6) != 0)
318                 return "unknown 9P version";
319         rhdr.version = "9P2000";
320         return nil;
321 }
322
323 char*
324 rflush(Fid*)
325 {
326         return 0;
327 }
328
329 char*
330 rauth(Fid*)
331 {
332         return "auth unimplemented";
333 }
334
335 char*
336 rattach(Fid *f)
337 {
338         f->busy = 1;
339         f->node = remroot;
340         rhdr.qid = f->node->d->qid;
341         return 0;
342 }
343
344 char*
345 rwalk(Fid *f)
346 {
347         Node *np;
348         Fid *nf;
349         char **elems;
350         int i, nelems;
351         char *err;
352         Node *node;
353
354         /* clone fid */
355         nf = nil;
356         if(thdr.newfid != thdr.fid){
357                 nf = newfid(thdr.newfid);
358                 if(nf->busy)
359                         return "newfid in use";
360                 nf->busy = 1;
361                 nf->node = f->node;
362                 f = nf;
363         }
364
365         err = nil;
366         elems = thdr.wname;
367         nelems = thdr.nwname;
368         node = f->node;
369         rhdr.nwqid = 0;
370         if(nelems > 0){
371                 /* walk fid */
372                 for(i=0; i<nelems && i<MAXWELEM; i++){
373                         if((node->d->qid.type & QTDIR) == 0){
374                                 err = "not a directory";
375                                 break;
376                         }
377                         if(strcmp(elems[i], ".") == 0){
378    Found:
379                                 rhdr.wqid[i] = node->d->qid;
380                                 rhdr.nwqid++;
381                                 continue;
382                         }
383                         if(strcmp(elems[i], "..") == 0){
384                                 node = node->parent;
385                                 goto Found;
386                         }
387                         if(strcmp(elems[i], ".flush.ftpfs") == 0){
388                                 /* hack to flush the cache */
389                                 invalidate(node);
390                                 readdir(node);
391                                 goto Found;
392                         }
393
394                         /* some top level names are special */
395                         if((os == Tops || os == VM || os == VMS) && node == remroot){
396                                 np = newtopsdir(elems[i]);
397                                 if(np){
398                                         node = np;
399                                         goto Found;
400                                 } else {
401                                         err = nosuchfile;
402                                         break;
403                                 }
404                         }
405
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 */
410                                 if(!ISVALID(node)){
411                                         err = nosuchfile;
412                                         break;
413                                 }
414                                 if(node->parent->chdirunknown || (node->d->mode & DMSYML))
415                                         fixsymbolic(node);
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;
421                                 }else{
422                                         node->d->qid.type = QTFILE;
423                                         node->d->mode &= ~DMDIR;
424                                 }
425                         }
426                         goto Found;
427                 }
428                 if(i == 0 && err == 0)
429                         err = "file does not exist";
430         }
431
432         /* clunk a newly cloned fid if the walk didn't succeed */
433         if(nf != nil && (err != nil || rhdr.nwqid<nelems)){
434                 nf->busy = 0;
435                 nf->fid = 0;
436         }
437
438         /* if it all worked, point the fid to the enw node */
439         if(err == nil)
440                 f->node = node;
441
442         return err;
443 }
444
445 char *
446 ropen(Fid *f)
447 {
448         int mode;
449
450         mode = thdr.mode;
451         if(f->node->d->qid.type & QTDIR)
452                 if(mode != OREAD)
453                         return "permission denied";
454
455         if(mode & OTRUNC){
456                 uncache(f->node);
457                 uncache(f->node->parent);
458                 filedirty(f->node);
459         } else {
460                 /* read the remote file or directory */
461                 if(!ISCACHED(f->node)){
462                         filefree(f->node);
463                         if(f->node->d->qid.type & QTDIR){
464                                 invalidate(f->node);
465                                 if(readdir(f->node) < 0)
466                                         return nosuchfile;
467                         } else {
468                                 if(readfile(f->node) < 0)
469                                         return errstring;
470                         }
471                         CACHED(f->node);
472                 }
473         }
474
475         rhdr.qid = f->node->d->qid;
476         f->open = 1;
477         f->node->opens++;
478         return 0;
479 }
480
481 char*
482 rcreate(Fid *f)
483 {
484         char *name;
485
486         if((f->node->d->qid.type&QTDIR) == 0)
487                 return "not a directory";
488
489         name = thdr.name;
490         f->node = extendpath(f->node, s_copy(name));
491         uncache(f->node);
492         if(thdr.perm & DMDIR){
493                 if(createdir(f->node) < 0)
494                         return "permission denied";
495         } else
496                 filedirty(f->node);
497         invalidate(f->node->parent);
498         uncache(f->node->parent);
499
500         rhdr.qid = f->node->d->qid;
501         f->open = 1;
502         f->node->opens++;
503         return 0;
504 }
505
506 char*
507 rread(Fid *f)
508 {
509         long off;
510         int n, cnt, rv;
511         Node *np;
512
513         rhdr.count = 0;
514         off = thdr.offset;
515         cnt = thdr.count;
516         if(cnt > messagesize-IOHDRSZ)
517                 cnt = messagesize-IOHDRSZ;
518
519         if(f->node->d->qid.type & QTDIR){
520                 rv = 0;
521                 for(np = f->node->children; np != nil; np = np->sibs){
522                         if(!ISVALID(np))
523                                 continue;
524                         if(off <= BIT16SZ)
525                                 break;
526                         n = convD2M(np->d, mbuf, messagesize-IOHDRSZ);
527                         off -= n;
528                 }
529                 for(; rv < cnt && np != nil; np = np->sibs){
530                         if(!ISVALID(np))
531                                 continue;
532                         if(np->d->mode & DMSYML)
533                                 fixsymbolic(np);
534                         n = convD2M(np->d, mbuf + rv, cnt-rv);
535                         if(n <= BIT16SZ)
536                                 break;
537                         rv += n;
538                 }
539         } else {
540                 /* reread file if it's fallen out of the cache */
541                 if(!ISCACHED(f->node))
542                         if(readfile(f->node) < 0)
543                                 return errstring;
544                 CACHED(f->node);
545                 rv = fileread(f->node, (char*)mbuf, off, cnt);
546                 if(rv < 0)
547                         return errstring;
548         }
549
550         rhdr.data = (char*)mbuf;
551         rhdr.count = rv;
552         return 0;
553 }
554
555 char*
556 rwrite(Fid *f)
557 {
558         long off;
559         int cnt;
560
561         if(f->node->d->qid.type & QTDIR)
562                 return "directories are not writable";
563
564         rhdr.count = 0;
565         off = thdr.offset;
566         cnt = thdr.count;
567         cnt = filewrite(f->node, thdr.data, off, cnt);
568         if(cnt < 0)
569                 return errstring;
570         filedirty(f->node);
571         rhdr.count = cnt;
572         return 0;
573 }
574
575 char *
576 rclunk(Fid *f)
577 {
578         if(fileisdirty(f->node)){
579                 if(createfile(f->node) < 0)
580                         fprint(2, "ftpfs: couldn't create %s\n", f->node->d->name);
581                 fileclean(f->node);
582                 uncache(f->node);
583         }
584         if(f->open){
585                 f->open = 0;
586                 f->node->opens--;
587         }
588         f->busy = 0;
589         return 0;
590 }
591
592 /*
593  *  remove is an implicit clunk
594  */
595 char *
596 rremove(Fid *f)
597 {
598         char *e;
599         e = nil;
600         if(QTDIR & f->node->d->qid.type){
601                 if(removedir(f->node) < 0)
602                         e = errstring;
603         } else {
604                 if(removefile(f->node) < 0)
605                         e = errstring;
606         }
607         uncache(f->node->parent);
608         uncache(f->node);
609         if(e == nil)
610                 INVALID(f->node);
611         f->busy = 0;
612         return e;
613 }
614
615 char *
616 rstat(Fid *f)
617 {
618         Node *p;
619
620         p = f->node->parent;
621         if(!ISCACHED(p)){
622                 invalidate(p);
623                 readdir(p);
624                 CACHED(p);
625         }
626         if(!ISVALID(f->node))
627                 return nosuchfile;
628         if(p->chdirunknown)
629                 fixsymbolic(f->node);
630         rhdr.nstat = convD2M(f->node->d, mbuf, messagesize-IOHDRSZ);
631         rhdr.stat = mbuf;
632         return 0;
633 }
634
635 char *
636 rwstat(Fid *f)
637 {
638         USED(f);
639         return "wstat not implemented";
640 }
641
642 /*
643  *  print message and die
644  */
645 void
646 fatal(char *fmt, ...)
647 {
648         va_list arg;
649         char buf[8*1024];
650
651         dying = 1;
652
653         va_start(arg, fmt);
654         vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg);
655         va_end(arg);
656
657         fprint(2, "ftpfs: %s\n", buf);
658         if(kapid > 0)
659                 postnote(PNGROUP, kapid, "die");
660         exits(buf);
661 }
662
663 /*
664  *  like strncpy but make sure there's a terminating null
665  */
666 void*
667 safecpy(void *to, void *from, int n)
668 {
669         char *a = ((char*)to) + n - 1;
670
671         strncpy(to, from, n);
672         *a = 0;
673         return to;
674 }
675
676 /*
677  *  set the error string
678  */
679 int
680 seterr(char *fmt, ...)
681 {
682         va_list arg;
683
684         va_start(arg, fmt);
685         vsnprint(errstring, sizeof errstring, fmt, arg);
686         va_end(arg);
687         return -1;
688 }
689
690 /*
691  *  create a new node
692  */
693 Node*
694 newnode(Node *parent, String *name)
695 {
696         Node *np;
697         static ulong path;
698         Dir d;
699
700         np = mallocz(sizeof(Node), 1);
701         if(np == 0)
702                 fatal("out of memory");
703
704         np->children = 0;
705         if(parent){
706                 np->parent = parent;
707                 np->sibs = parent->children;
708                 parent->children = np;
709                 np->depth = parent->depth + 1;
710                 d.dev = 0;              /* not stat'd */
711         } else {
712                 /* the root node */
713                 np->parent = np;
714                 np->sibs = 0;
715                 np->depth = 0;
716                 d.dev = 1;
717         }
718         np->remname = name;
719         d.name = s_to_c(name);
720         d.atime = time(0);
721         d.mtime = d.atime;
722         d.uid = nouid;
723         d.gid = nouid;
724         d.muid = nouid;
725         np->fp = 0;
726         d.qid.path = ++path;
727         d.qid.vers = 0;
728         d.qid.type = QTFILE;
729         d.type = 0;
730         np->d = reallocdir(&d, 0);
731
732         return np;
733 }
734
735 /*
736  *  walk one down the local mirror of the remote directory tree
737  */
738 Node*
739 extendpath(Node *parent, String *elem)
740 {
741         Node *np;
742
743         for(np = parent->children; np; np = np->sibs)
744                 if(strcmp(s_to_c(np->remname), s_to_c(elem)) == 0){
745                         s_free(elem);
746                         return np;
747                 }
748
749         return newnode(parent, elem);
750 }
751
752 /*
753  *  flush the cached file, write it back if it's dirty
754  */
755 void
756 uncache(Node *np)
757 {
758         if(fileisdirty(np))
759                 createfile(np);
760         filefree(np);
761         UNCACHED(np);
762 }
763
764 /*
765  *  invalidate all children of a node
766  */
767 void
768 invalidate(Node *node)
769 {
770         Node *np;
771
772         if(node->opens)
773                 return;         /* don't invalidate something that's open */
774
775         uncachedir(node, 0);
776
777         /* invalidate children */
778         for(np = node->children; np; np = np->sibs){
779                 if(np->opens)
780                         continue;       /* don't invalidate something that's open */
781                 UNCACHED(np);
782                 invalidate(np);
783                 np->d->dev = 0;
784         }
785 }
786
787 /*
788  *  make a top level tops-20 directory.  They are automaticly valid.
789  */
790 Node*
791 newtopsdir(char *name)
792 {
793         Node *np;
794
795         np = extendpath(remroot, s_copy(name));
796         if(!ISVALID(np)){
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;
804                 np->d->length = 0;
805                 np->d = reallocdir(np->d, 1);
806                 
807                 if(changedir(np) >= 0)
808                         VALID(np);
809         }
810         return np;
811 }
812
813 /*
814  *  figure out if a symbolic link is to a directory or a file
815  */
816 void
817 fixsymbolic(Node *node)
818 {
819         if(changedir(node) == 0){
820                 node->d->mode |= DMDIR;
821                 node->d->qid.type = QTDIR;
822         } else
823                 node->d->qid.type = QTFILE;
824         node->d->mode &= ~DMSYML; 
825 }