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