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