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