xref: /plan9/sys/src/cmd/lnfs.c (revision f6269f58ad15527f31cd4480aa79b67e93423b0f)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include <fcall.h>
6 #include <String.h>
7 #include <libsec.h>
8 
9 enum
10 {
11 	OPERM	= 0x3,		/* mask of all permission types in open mode */
12 	Maxsize	= 512*1024*1024,
13 	Maxfdata	= 8192,
14 	NAMELEN = 28,
15 };
16 
17 typedef struct Fid Fid;
18 struct Fid
19 {
20 	short	busy;
21 	int	fid;
22 	Fid	*next;
23 	char	*user;
24 	String	*path;		/* complete path */
25 
26 	int	fd;		/* set on open or create */
27 	Qid	qid;		/* set on open or create */
28 	int	attach;		/* this is an attach fd */
29 
30 	ulong	diroff;		/* directory offset */
31 	Dir	*dir;		/* directory entries */
32 	int	ndir;		/* number of directory entries */
33 };
34 
35 Fid	*fids;
36 int	mfd[2];
37 char	*user;
38 uchar	mdata[IOHDRSZ+Maxfdata];
39 uchar	rdata[Maxfdata];	/* buffer for data in reply */
40 uchar	statbuf[STATMAX];
41 Fcall	thdr;
42 Fcall	rhdr;
43 int	messagesize = sizeof mdata;
44 int	readonly;
45 char	*srvname;
46 int	debug;
47 
48 Fid *	newfid(int);
49 void	io(void);
50 void	*erealloc(void*, ulong);
51 void	*emalloc(ulong);
52 char	*estrdup(char*);
53 void	usage(void);
54 void	fidqid(Fid*, Qid*);
55 char*	short2long(char*);
56 char*	long2short(char*, int);
57 void	readnames(void);
58 void	post(char*, int);
59 
60 char	*rflush(Fid*), *rversion(Fid*), *rauth(Fid*),
61 	*rattach(Fid*), *rwalk(Fid*),
62 	*ropen(Fid*), *rcreate(Fid*),
63 	*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
64 	*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
65 
66 char 	*(*fcalls[])(Fid*) = {
67 	[Tversion]	rversion,
68 	[Tflush]	rflush,
69 	[Tauth]	rauth,
70 	[Tattach]	rattach,
71 	[Twalk]		rwalk,
72 	[Topen]		ropen,
73 	[Tcreate]	rcreate,
74 	[Tread]		rread,
75 	[Twrite]	rwrite,
76 	[Tclunk]	rclunk,
77 	[Tremove]	rremove,
78 	[Tstat]		rstat,
79 	[Twstat]	rwstat,
80 };
81 
82 char	Eperm[] =	"permission denied";
83 char	Enotdir[] =	"not a directory";
84 char	Enoauth[] =	"lnfs: authentication not required";
85 char	Enotexist[] =	"file does not exist";
86 char	Einuse[] =	"file in use";
87 char	Eexist[] =	"file exists";
88 char	Eisdir[] =	"file is a directory";
89 char	Enotowner[] =	"not owner";
90 char	Eisopen[] = 	"file already open for I/O";
91 char	Excl[] = 	"exclusive use file already open";
92 char	Ename[] = 	"illegal name";
93 char	Eversion[] =	"unknown 9P version";
94 
95 void
usage(void)96 usage(void)
97 {
98 	fprint(2, "usage: %s [-r] [-s srvname] mountpoint\n", argv0);
99 	exits("usage");
100 }
101 
102 void
notifyf(void * a,char * s)103 notifyf(void *a, char *s)
104 {
105 	USED(a);
106 	if(strncmp(s, "interrupt", 9) == 0)
107 		noted(NCONT);
108 	noted(NDFLT);
109 }
110 
111 void
main(int argc,char * argv[])112 main(int argc, char *argv[])
113 {
114 	char *defmnt;
115 	int p[2];
116 	Dir *d;
117 
118 	ARGBEGIN{
119 	case 'r':
120 		readonly = 1;
121 		break;
122 	case 'd':
123 		debug = 1;
124 		break;
125 	case 's':
126 		srvname = ARGF();
127 		if(srvname == nil)
128 			usage();
129 		break;
130 	default:
131 		usage();
132 	}ARGEND
133 
134 	if(argc < 1)
135 		usage();
136 	defmnt = argv[0];
137 	d = dirstat(defmnt);
138 	if(d == nil || !(d->qid.type & QTDIR))
139 		sysfatal("mountpoint must be an accessible directory");
140 	free(d);
141 
142 	if(pipe(p) < 0)
143 		sysfatal("pipe failed");
144 	mfd[0] = p[0];
145 	mfd[1] = p[0];
146 
147 	user = getuser();
148 	notify(notifyf);
149 
150 	if(srvname != nil)
151 		post(srvname, p[1]);
152 
153 	if(debug)
154 		fmtinstall('F', fcallfmt);
155 	switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
156 	case -1:
157 		sysfatal("fork: %r");
158 	case 0:
159 		close(p[1]);
160 		chdir(defmnt);
161 		io();
162 		break;
163 	default:
164 		close(p[0]);	/* don't deadlock if child fails */
165 		if(mount(p[1], -1, defmnt, MREPL|MCREATE, "") < 0)
166 			sysfatal("mount failed: %r");
167 	}
168 	exits(0);
169 }
170 
171 void
post(char * srvname,int pfd)172 post(char *srvname, int pfd)
173 {
174 	char name[128];
175 	int fd;
176 
177 	snprint(name, sizeof name, "#s/%s", srvname);
178 	fd = create(name, OWRITE, 0666);
179 	if(fd < 0)
180 		sysfatal("create of %s failed: %r", srvname);
181 	sprint(name, "%d", pfd);
182 	if(write(fd, name, strlen(name)) < 0)
183 		sysfatal("writing %s: %r", srvname);
184 }
185 char*
rversion(Fid *)186 rversion(Fid*)
187 {
188 	Fid *f;
189 
190 	for(f = fids; f; f = f->next)
191 		if(f->busy)
192 			rclunk(f);
193 	if(thdr.msize > sizeof mdata)
194 		rhdr.msize = sizeof mdata;
195 	else
196 		rhdr.msize = thdr.msize;
197 	messagesize = rhdr.msize;
198 	if(strncmp(thdr.version, "9P2000", 6) != 0)
199 		return Eversion;
200 	rhdr.version = "9P2000";
201 	return nil;
202 }
203 
204 char*
rauth(Fid *)205 rauth(Fid*)
206 {
207 	return Enoauth;
208 }
209 
210 char*
rflush(Fid * f)211 rflush(Fid *f)
212 {
213 	USED(f);
214 	return nil;
215 }
216 
217 char*
rattach(Fid * f)218 rattach(Fid *f)
219 {
220 	/* no authentication! */
221 	f->busy = 1;
222 	if(thdr.uname[0])
223 		f->user = estrdup(thdr.uname);
224 	else
225 		f->user = "none";
226 	if(strcmp(user, "none") == 0)
227 		user = f->user;
228 	if(f->path)
229 		s_free(f->path);
230 	f->path = s_copy(".");
231 	fidqid(f, &rhdr.qid);
232 	f->attach = 1;
233 	return nil;
234 }
235 
236 char*
clone(Fid * f,Fid ** nf)237 clone(Fid *f, Fid **nf)
238 {
239 	if(f->fd >= 0)
240 		return Eisopen;
241 	*nf = newfid(thdr.newfid);
242 	(*nf)->busy = 1;
243 	if((*nf)->path)
244 		s_free((*nf)->path);
245 	(*nf)->path = s_clone(f->path);
246 	(*nf)->fd = -1;
247 	(*nf)->user = f->user;
248 	(*nf)->attach = 0;
249 	return nil;
250 }
251 
252 char*
rwalk(Fid * f)253 rwalk(Fid *f)
254 {
255 	char *name;
256 	Fid *nf;
257 	char *err;
258 	int i;
259 	String *npath;
260 	Dir *d;
261 	char *cp;
262 	Qid qid;
263 
264 	err = nil;
265 	nf = nil;
266 	rhdr.nwqid = 0;
267 	if(rhdr.newfid != rhdr.fid){
268 		err = clone(f, &nf);
269 		if(err)
270 			return err;
271 		f = nf;	/* walk the new fid */
272 	}
273 	readnames();
274 	npath = s_clone(f->path);
275 	if(thdr.nwname > 0){
276 		for(i=0; i<thdr.nwname && i<MAXWELEM; i++){
277 			name = long2short(thdr.wname[i], 0);
278 			if(strcmp(name, ".") == 0){
279 				;
280 			} else if(strcmp(name, "..") == 0){
281 				cp = strrchr(s_to_c(npath), '/');
282 				if(cp != nil){
283 					*cp = 0;
284 					npath->ptr = cp;
285 				}
286 			} else {
287 				s_append(npath, "/");
288 				s_append(npath, name);
289 			}
290 			d = dirstat(s_to_c(npath));
291 			if(d == nil)
292 				break;
293 			rhdr.nwqid++;
294 			qid = d->qid;
295 			rhdr.wqid[i] = qid;
296 			free(d);
297 		}
298 		if(i==0 && err == nil)
299 			err = Enotexist;
300 	}
301 
302 	/* if there was an error and we cloned, get rid of the new fid */
303 	if(nf != nil && (err!=nil || rhdr.nwqid<thdr.nwname)){
304 		f->busy = 0;
305 		s_free(npath);
306 	}
307 
308 	/* update the fid after a successful walk */
309 	if(rhdr.nwqid == thdr.nwname){
310 		s_free(f->path);
311 		f->path = npath;
312 	}
313 	return err;
314 }
315 
316 static char*
passerror(void)317 passerror(void)
318 {
319 	static char err[256];
320 
321 	rerrstr(err, sizeof err);
322 	return err;
323 }
324 
325 char*
ropen(Fid * f)326 ropen(Fid *f)
327 {
328 	if(readonly && (thdr.mode & 3))
329 		return Eperm;
330 	if(f->fd >= 0)
331 		return Eisopen;
332 	f->fd = open(s_to_c(f->path), thdr.mode);
333 	if(f->fd < 0)
334 		return passerror();
335 	fidqid(f, &rhdr.qid);
336 	f->qid = rhdr.qid;
337 	rhdr.iounit = messagesize-IOHDRSZ;
338 	return nil;
339 }
340 
341 char*
rcreate(Fid * f)342 rcreate(Fid *f)
343 {
344 	char *name;
345 
346 	if(readonly)
347 		return Eperm;
348 	readnames();
349 	name = long2short(thdr.name, 1);
350 	if(f->fd >= 0)
351 		return Eisopen;
352 	s_append(f->path, "/");
353 	s_append(f->path, name);
354 	f->fd = create(s_to_c(f->path), thdr.mode, thdr.perm);
355 	if(f->fd < 0)
356 		return passerror();
357 	fidqid(f, &rhdr.qid);
358 	f->qid = rhdr.qid;
359 	rhdr.iounit = messagesize-IOHDRSZ;
360 	return nil;
361 }
362 
363 char*
rreaddir(Fid * f)364 rreaddir(Fid *f)
365 {
366 	int i;
367 	int n;
368 
369 	/* reread the directory */
370 	if(thdr.offset == 0){
371 		if(f->dir)
372 			free(f->dir);
373 		f->dir = nil;
374 		if(f->diroff != 0)
375 			seek(f->fd, 0, 0);
376 		f->ndir = dirreadall(f->fd, &f->dir);
377 		f->diroff = 0;
378 		if(f->ndir < 0)
379 			return passerror();
380 		readnames();
381 		for(i = 0; i < f->ndir; i++)
382 			f->dir[i].name = short2long(f->dir[i].name);
383 	}
384 
385 	/* copy in as many directory entries as possible */
386 	for(n = 0; f->diroff < f->ndir; n += i){
387 		i = convD2M(&f->dir[f->diroff], rdata+n, thdr.count - n);
388 		if(i <= BIT16SZ)
389 			break;
390 		f->diroff++;
391 	}
392 	rhdr.data = (char*)rdata;
393 	rhdr.count = n;
394 	return nil;
395 }
396 
397 char*
rread(Fid * f)398 rread(Fid *f)
399 {
400 	long n;
401 
402 	if(f->fd < 0)
403 		return Enotexist;
404 	if(thdr.count > messagesize-IOHDRSZ)
405 		thdr.count = messagesize-IOHDRSZ;
406 	if(f->qid.type & QTDIR)
407 		return rreaddir(f);
408 	n = pread(f->fd, rdata, thdr.count, thdr.offset);
409 	if(n < 0)
410 		return passerror();
411 	rhdr.data = (char*)rdata;
412 	rhdr.count = n;
413 	return nil;
414 }
415 
416 char*
rwrite(Fid * f)417 rwrite(Fid *f)
418 {
419 	long n;
420 
421 	if(readonly || (f->qid.type & QTDIR))
422 		return Eperm;
423 	if(f->fd < 0)
424 		return Enotexist;
425 	if(thdr.count > messagesize-IOHDRSZ)	/* shouldn't happen, anyway */
426 		thdr.count = messagesize-IOHDRSZ;
427 	n = pwrite(f->fd, thdr.data, thdr.count, thdr.offset);
428 	if(n < 0)
429 		return passerror();
430 	rhdr.count = n;
431 	return nil;
432 }
433 
434 char*
rclunk(Fid * f)435 rclunk(Fid *f)
436 {
437 	f->busy = 0;
438 	close(f->fd);
439 	f->fd = -1;
440 	f->path = s_reset(f->path);
441 	if(f->attach){
442 		free(f->user);
443 		f->user = nil;
444 	}
445 	f->attach = 0;
446 	if(f->dir != nil){
447 		free(f->dir);
448 		f->dir = nil;
449 	}
450 	f->diroff = f->ndir = 0;
451 	return nil;
452 }
453 
454 char*
rremove(Fid * f)455 rremove(Fid *f)
456 {
457 	if(remove(s_to_c(f->path)) < 0)
458 		return passerror();
459 	return nil;
460 }
461 
462 char*
rstat(Fid * f)463 rstat(Fid *f)
464 {
465 	int n;
466 	Dir *d;
467 
468 	d = dirstat(s_to_c(f->path));
469 	if(d == nil)
470 		return passerror();
471 	d->name = short2long(d->name);
472 	n = convD2M(d, statbuf, sizeof statbuf);
473 	free(d);
474 	if(n <= BIT16SZ)
475 		return passerror();
476 	rhdr.nstat = n;
477 	rhdr.stat = statbuf;
478 	return nil;
479 }
480 
481 char*
rwstat(Fid * f)482 rwstat(Fid *f)
483 {
484 	int n;
485 	Dir d;
486 
487 	if(readonly)
488 		return Eperm;
489 	convM2D(thdr.stat, thdr.nstat, &d, (char*)rdata);
490 	d.name = long2short(d.name, 1);
491 	n = dirwstat(s_to_c(f->path), &d);
492 	if(n < 0)
493 		return passerror();
494 	return nil;
495 }
496 
497 Fid *
newfid(int fid)498 newfid(int fid)
499 {
500 	Fid *f, *ff;
501 
502 	ff = 0;
503 	for(f = fids; f; f = f->next)
504 		if(f->fid == fid)
505 			return f;
506 		else if(!ff && !f->busy)
507 			ff = f;
508 	if(ff){
509 		ff->fid = fid;
510 		return ff;
511 	}
512 	f = emalloc(sizeof *f);
513 	f->path = s_reset(f->path);
514 	f->fd = -1;
515 	f->fid = fid;
516 	f->next = fids;
517 	fids = f;
518 	return f;
519 }
520 
521 void
io(void)522 io(void)
523 {
524 	char *err;
525 	int n, pid;
526 
527 	pid = getpid();
528 
529 	for(;;){
530 		/*
531 		 * reading from a pipe or a network device
532 		 * will give an error after a few eof reads.
533 		 * however, we cannot tell the difference
534 		 * between a zero-length read and an interrupt
535 		 * on the processes writing to us,
536 		 * so we wait for the error.
537 		 */
538 		n = read9pmsg(mfd[0], mdata, messagesize);
539 		if(n < 0)
540 			sysfatal("mount read");
541 		if(n == 0)
542 			continue;
543 		if(convM2S(mdata, n, &thdr) == 0)
544 			continue;
545 
546 		if(debug)
547 			fprint(2, "%s %d:<-%F\n", argv0, pid, &thdr);
548 
549 		if(!fcalls[thdr.type])
550 			err = "bad fcall type";
551 		else
552 			err = (*fcalls[thdr.type])(newfid(thdr.fid));
553 		if(err){
554 			rhdr.type = Rerror;
555 			rhdr.ename = err;
556 		}else{
557 			rhdr.type = thdr.type + 1;
558 			rhdr.fid = thdr.fid;
559 		}
560 		rhdr.tag = thdr.tag;
561 		if(debug)
562 			fprint(2, "%s %d:->%F\n", argv0, pid, &rhdr);/**/
563 		n = convS2M(&rhdr, mdata, messagesize);
564 		if(n == 0)
565 			sysfatal("convS2M error on write");
566 		if(write(mfd[1], mdata, n) != n)
567 			sysfatal("mount write");
568 	}
569 }
570 
571 void *
emalloc(ulong n)572 emalloc(ulong n)
573 {
574 	void *p;
575 
576 	p = malloc(n);
577 	if(!p)
578 		sysfatal("out of memory");
579 	memset(p, 0, n);
580 	return p;
581 }
582 
583 void *
erealloc(void * p,ulong n)584 erealloc(void *p, ulong n)
585 {
586 	p = realloc(p, n);
587 	if(!p)
588 		sysfatal("out of memory");
589 	return p;
590 }
591 
592 char *
estrdup(char * q)593 estrdup(char *q)
594 {
595 	char *p;
596 	int n;
597 
598 	n = strlen(q)+1;
599 	p = malloc(n);
600 	if(!p)
601 		sysfatal("out of memory");
602 	memmove(p, q, n);
603 	return p;
604 }
605 
606 void
fidqid(Fid * f,Qid * q)607 fidqid(Fid *f, Qid *q)
608 {
609 	Dir *d;
610 
611 	d = dirstat(s_to_c(f->path));
612 	if(d == nil)
613 		*q = (Qid){0, 0, QTFILE};
614 	else {
615 		*q = d->qid;
616 		free(d);
617 	}
618 }
619 
620 /*
621  *  table of name translations
622  *
623  *  the file ./.longnames contains all the known long names.
624  *  the short name is the first NAMELEN-1 bytes of the base64
625  *  encoding of the MD5 hash of the longname.
626  */
627 
628 typedef struct Name Name;
629 struct Name
630 {
631 	Name	*next;
632 	char	shortname[NAMELEN];
633 	char	*longname;
634 };
635 
636 Dir *dbstat;	/* last stat of the name file */
637 char *namefile = "./.longnames";
638 char *namebuf;
639 Name *names;
640 
641 Name*
newname(char * longname,int writeflag)642 newname(char *longname, int writeflag)
643 {
644 	Name *np;
645 	int n;
646 	uchar digest[MD5dlen];
647 	int fd;
648 
649 	/* chain in new name */
650 	n = strlen(longname);
651 	np = emalloc(sizeof(*np)+n+1);
652 	np->longname = (char*)&np[1];
653 	strcpy(np->longname, longname);
654 	md5((uchar*)longname, n, digest, nil);
655 	enc32(np->shortname, sizeof(np->shortname), digest, MD5dlen);
656 	np->next = names;
657 	names = np;
658 
659 	/* don't change namefile if we're read only */
660 	if(!writeflag)
661 		return np;
662 
663 	/* add to namefile */
664 	fd = open(namefile, OWRITE);
665 	if(fd >= 0){
666 		seek(fd, 0, 2);
667 		fprint(fd, "%s\n", longname);
668 		close(fd);
669 	}
670 	return np;
671 }
672 
673 void
freenames(void)674 freenames(void)
675 {
676 	Name *np, *next;
677 
678 	for(np = names; np != nil; np = next){
679 		next = np->next;
680 		free(np);
681 	}
682 	names = nil;
683 }
684 
685 /*
686  *  reread the file if the qid.path has changed.
687  *
688  *  read any new entries if length has changed.
689  */
690 void
readnames(void)691 readnames(void)
692 {
693 	Dir *d;
694 	int fd;
695 	vlong offset;
696 	Biobuf *b;
697 	char *p;
698 
699 	d = dirstat(namefile);
700 	if(d == nil){
701 		if(readonly)
702 			return;
703 
704 		/* create file if it doesn't exist */
705 		fd = create(namefile, OREAD, DMAPPEND|0666);
706 		if(fd < 0)
707 			return;
708 		if(dbstat != nil)
709 			free(dbstat);
710 		dbstat = nil;
711 		close(fd);
712 		return;
713 	}
714 
715 	/* up to date? */
716 	offset = 0;
717 	if(dbstat != nil){
718 		if(d->qid.path == dbstat->qid.path){
719 			if(d->length <= dbstat->length){
720 				free(d);
721 				return;
722 			}
723 			offset = dbstat->length;
724 		} else {
725 			freenames();
726 		}
727 		free(dbstat);
728 		dbstat = nil;
729 	}
730 
731 	/* read file */
732 	b = Bopen(namefile, OREAD);
733 	if(b == nil){
734 		free(d);
735 		return;
736 	}
737 	Bseek(b, offset, 0);
738 	while((p = Brdline(b, '\n')) != nil){
739 		p[Blinelen(b)-1] = 0;
740 		newname(p, 0);
741 	}
742 	Bterm(b);
743 	dbstat = d;
744 }
745 
746 /*
747  *  look up a long name,  if it doesn't exist in the
748  *  file, add an entry to the file if the writeflag is
749  *  non-zero.  Return a pointer to the short name.
750  */
751 char*
long2short(char * longname,int writeflag)752 long2short(char *longname, int writeflag)
753 {
754 	Name *np;
755 
756 	if(strlen(longname) < NAMELEN-1 && strpbrk(longname, " ")==nil)
757 		return longname;
758 
759 	for(np = names; np != nil; np = np->next)
760 		if(strcmp(longname, np->longname) == 0)
761 			return np->shortname;
762 	if(!writeflag)
763 		return longname;
764 	np = newname(longname, !readonly);
765 	return np->shortname;
766 }
767 
768 /*
769  *  look up a short name, if it doesn't exist, return the
770  *  longname.
771  */
772 char*
short2long(char * shortname)773 short2long(char *shortname)
774 {
775 	Name *np;
776 
777 	for(np = names; np != nil; np = np->next)
778 		if(strcmp(shortname, np->shortname) == 0)
779 			return np->longname;
780 	return shortname;
781 }
782