xref: /plan9/sys/src/cmd/ip/ftpfs/ftpfs.c (revision 887f280b4b7e0c3e83f2519c369b4e32146ee75b)
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