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