xref: /plan9/sys/src/cmd/vnc/exportfs.c (revision 530efdcaff4f66de5639ecb5f7b3370f61b181e0)
1 #include	<u.h>
2 #include	<libc.h>
3 #include	<fcall.h>
4 #include	"compat.h"
5 #include	"error.h"
6 
7 typedef	struct Fid	Fid;
8 typedef	struct Export	Export;
9 typedef	struct Exq	Exq;
10 typedef	struct Exwork	Exwork;
11 
12 enum
13 {
14 	Nfidhash	= 32,
15 	Maxfdata	= 8192,
16 	Maxrpc		= IOHDRSZ + Maxfdata,
17 };
18 
19 struct Export
20 {
21 	Ref	r;
22 	Exq*	work;
23 	Lock	fidlock;
24 	Fid*	fid[Nfidhash];
25 	int	io;		/* fd to read/write */
26 	int	iounit;
27 	int	nroots;
28 	Chan	**roots;
29 };
30 
31 struct Fid
32 {
33 	Fid*	next;
34 	Fid**	last;
35 	Chan*	chan;
36 	long	offset;
37 	int	fid;
38 	int	ref;		/* fcalls using the fid; locked by Export.Lock */
39 	int	attached;	/* fid attached or cloned but not clunked */
40 };
41 
42 struct Exq
43 {
44 	Lock	lk;
45 	int	responding;	/* writing out reply message */
46 	int	noresponse;	/* don't respond to this one */
47 	Exq*	next;
48 	int	shut;		/* has been noted for shutdown */
49 	Export*	export;
50 	void*	slave;
51 	Fcall	rpc;
52 	uchar	buf[Maxrpc];
53 };
54 
55 struct Exwork
56 {
57 	Lock	l;
58 
59 	int	ref;
60 
61 	int	nwaiters;	/* queue of slaves waiting for work */
62 	QLock	qwait;
63 	Rendez	rwait;
64 
65 	Exq	*head;		/* work waiting for a slave */
66 	Exq	*tail;
67 };
68 
69 Exwork exq;
70 
71 static void	exshutdown(Export*);
72 static void	exflush(Export*, int, int);
73 static void	exslave(void*);
74 static void	exfree(Export*);
75 static void	exportproc(Export*);
76 
77 static char*	Exattach(Export*, Fcall*, uchar*);
78 static char*	Exauth(Export*, Fcall*, uchar*);
79 static char*	Exclunk(Export*, Fcall*, uchar*);
80 static char*	Excreate(Export*, Fcall*, uchar*);
81 static char*	Exversion(Export*, Fcall*, uchar*);
82 static char*	Exopen(Export*, Fcall*, uchar*);
83 static char*	Exread(Export*, Fcall*, uchar*);
84 static char*	Exremove(Export*, Fcall*, uchar*);
85 static char*	Exsession(Export*, Fcall*, uchar*);
86 static char*	Exstat(Export*, Fcall*, uchar*);
87 static char*	Exwalk(Export*, Fcall*, uchar*);
88 static char*	Exwrite(Export*, Fcall*, uchar*);
89 static char*	Exwstat(Export*, Fcall*, uchar*);
90 
91 static char	*(*fcalls[Tmax])(Export*, Fcall*, uchar*);
92 
93 static char	Enofid[]   = "no such fid";
94 static char	Eseekdir[] = "can't seek on a directory";
95 static char	Ereaddir[] = "unaligned read of a directory";
96 static int	exdebug = 0;
97 
98 int
sysexport(int fd,Chan ** roots,int nroots)99 sysexport(int fd, Chan **roots, int nroots)
100 {
101 	Export *fs;
102 
103 	fs = smalloc(sizeof(Export));
104 	fs->r.ref = 1;
105 	fs->io = fd;
106 	fs->roots = roots;
107 	fs->nroots = nroots;
108 
109 	exportproc(fs);
110 
111 	return 0;
112 }
113 
114 static void
exportinit(void)115 exportinit(void)
116 {
117 	lock(&exq.l);
118 	exq.ref++;
119 	if(fcalls[Tversion] != nil){
120 		unlock(&exq.l);
121 		return;
122 	}
123 
124 	fmtinstall('F', fcallfmt);
125 	fcalls[Tversion] = Exversion;
126 	fcalls[Tauth] = Exauth;
127 	fcalls[Tattach] = Exattach;
128 	fcalls[Twalk] = Exwalk;
129 	fcalls[Topen] = Exopen;
130 	fcalls[Tcreate] = Excreate;
131 	fcalls[Tread] = Exread;
132 	fcalls[Twrite] = Exwrite;
133 	fcalls[Tclunk] = Exclunk;
134 	fcalls[Tremove] = Exremove;
135 	fcalls[Tstat] = Exstat;
136 	fcalls[Twstat] = Exwstat;
137 	unlock(&exq.l);
138 }
139 
140 static void
exportproc(Export * fs)141 exportproc(Export *fs)
142 {
143 	Exq *q;
144 	int n, ed;
145 
146 	exportinit();
147 	ed = errdepth(-1);
148 	for(;;){
149 		errdepth(ed);
150 		q = smalloc(sizeof(Exq));
151 
152 		n = read9pmsg(fs->io, q->buf, Maxrpc);
153 		if(n <= 0 || convM2S(q->buf, n, &q->rpc) != n)
154 			goto bad;
155 
156 		if(exdebug)
157 			print("export %d <- %F\n", getpid(), &q->rpc);
158 
159 		if(q->rpc.type == Tflush){
160 			exflush(fs, q->rpc.tag, q->rpc.oldtag);
161 			free(q);
162 			continue;
163 		}
164 
165 		q->export = fs;
166 		incref(&fs->r);
167 
168 		lock(&exq.l);
169 		if(exq.head == nil)
170 			exq.head = q;
171 		else
172 			exq.tail->next = q;
173 		q->next = nil;
174 		exq.tail = q;
175 		n = exq.nwaiters;
176 		if(n)
177 			exq.nwaiters = n - 1;
178 		unlock(&exq.l);
179 		if(!n)
180 			kproc("exportfs", exslave, nil);
181 		rendwakeup(&exq.rwait);
182 	}
183 bad:
184 	free(q);
185 	if(exdebug)
186 		fprint(2, "export proc shutting down: %r\n");
187 	exshutdown(fs);
188 	exfree(fs);
189 }
190 
191 static void
exflush(Export * fs,int flushtag,int tag)192 exflush(Export *fs, int flushtag, int tag)
193 {
194 	Exq *q, **last;
195 	Fcall fc;
196 	uchar buf[Maxrpc];
197 	int n;
198 
199 	/* hasn't been started? */
200 	lock(&exq.l);
201 	last = &exq.head;
202 	for(q = exq.head; q != nil; q = q->next){
203 		if(q->export == fs && q->rpc.tag == tag){
204 			*last = q->next;
205 			unlock(&exq.l);
206 			exfree(fs);
207 			free(q);
208 			goto Respond;
209 		}
210 		last = &q->next;
211 	}
212 	unlock(&exq.l);
213 
214 	/* in progress? */
215 	lock(&fs->r);
216 	for(q = fs->work; q != nil; q = q->next){
217 		if(q->rpc.tag == tag){
218 			lock(&q->lk);
219 			q->noresponse = 1;
220 			if(!q->responding)
221 				rendintr(q->slave);
222 			unlock(&q->lk);
223 			break;
224 		}
225 	}
226 	unlock(&fs->r);
227 
228 Respond:
229 	fc.type = Rflush;
230 	fc.tag = flushtag;
231 
232 	n = convS2M(&fc, buf, Maxrpc);
233 	if(n == 0)
234 		panic("convS2M error on write");
235 	if(write(fs->io, buf, n) != n)
236 		panic("mount write");
237 }
238 
239 static void
exshutdown(Export * fs)240 exshutdown(Export *fs)
241 {
242 	Exq *q, **last;
243 
244 	lock(&exq.l);
245 	last = &exq.head;
246 	for(q = exq.head; q != nil; q = *last){
247 		if(q->export == fs){
248 			*last = q->next;
249 			exfree(fs);
250 			free(q);
251 			continue;
252 		}
253 		last = &q->next;
254 	}
255 
256 	/*
257 	 * cleanly shut down the slaves if this is the last fs around
258 	 */
259 	exq.ref--;
260 	if(!exq.ref)
261 		rendwakeup(&exq.rwait);
262 	unlock(&exq.l);
263 
264 	/*
265 	 * kick any sleepers
266 	 */
267 	lock(&fs->r);
268 	for(q = fs->work; q != nil; q = q->next){
269 		lock(&q->lk);
270 		q->noresponse = 1;
271 		if(!q->responding)
272 			rendintr(q->slave);
273 		unlock(&q->lk);
274 	}
275 	unlock(&fs->r);
276 }
277 
278 static void
exfree(Export * fs)279 exfree(Export *fs)
280 {
281 	Fid *f, *n;
282 	int i;
283 
284 	if(decref(&fs->r) != 0)
285 		return;
286 	for(i = 0; i < Nfidhash; i++){
287 		for(f = fs->fid[i]; f != nil; f = n){
288 			if(f->chan != nil)
289 				cclose(f->chan);
290 			n = f->next;
291 			free(f);
292 		}
293 	}
294 	free(fs);
295 }
296 
297 static int
exwork(void *)298 exwork(void *)
299 {
300 	int work;
301 
302 	lock(&exq.l);
303 	work = exq.head != nil || !exq.ref;
304 	unlock(&exq.l);
305 	return work;
306 }
307 
308 static void
exslave(void *)309 exslave(void *)
310 {
311 	Export *fs;
312 	Exq *q, *t, **last;
313 	char *volatile err;
314 	int n, ed;
315 
316 	while(waserror())
317 		fprint(2, "exslave %d errored out of loop -- heading back in!\n", getpid());
318 	ed = errdepth(-1);
319 	for(;;){
320 		errdepth(ed);
321 		qlock(&exq.qwait);
322 		if(waserror()){
323 			qunlock(&exq.qwait);
324 			nexterror();
325 		}
326 		rendsleep(&exq.rwait, exwork, nil);
327 
328 		lock(&exq.l);
329 		if(!exq.ref){
330 			unlock(&exq.l);
331 			poperror();
332 			qunlock(&exq.qwait);
333 			break;
334 		}
335 		q = exq.head;
336 		if(q == nil){
337 			unlock(&exq.l);
338 			poperror();
339 			qunlock(&exq.qwait);
340 			continue;
341 		}
342 		exq.head = q->next;
343 		if(exq.head == nil)
344 			exq.tail = nil;
345 		poperror();
346 		qunlock(&exq.qwait);
347 
348 		/*
349 		 * put the job on the work queue before it's
350 		 * visible as off of the head queue, so it's always
351 		 * findable for flushes and shutdown
352 		 */
353 		q->slave = up;
354 		q->noresponse = 0;
355 		q->responding = 0;
356 		rendclearintr();
357 		fs = q->export;
358 		lock(&fs->r);
359 		q->next = fs->work;
360 		fs->work = q;
361 		unlock(&fs->r);
362 
363 		unlock(&exq.l);
364 
365 		if(exdebug > 1)
366 			print("exslave dispatch %d %F\n", getpid(), &q->rpc);
367 
368 		if(waserror()){
369 			print("exslave err %r\n");
370 			err = up->error;
371 		}else{
372 			if(q->rpc.type >= Tmax || !fcalls[q->rpc.type])
373 				err = "bad fcall type";
374 			else
375 				err = (*fcalls[q->rpc.type])(fs, &q->rpc, &q->buf[IOHDRSZ]);
376 			poperror();
377 		}
378 
379 		q->rpc.type++;
380 		if(err){
381 			q->rpc.type = Rerror;
382 			q->rpc.ename = err;
383 		}
384 		n = convS2M(&q->rpc, q->buf, Maxrpc);
385 
386 		if(exdebug)
387 			print("exslave %d -> %F\n", getpid(), &q->rpc);
388 
389 		lock(&q->lk);
390 		if(!q->noresponse){
391 			q->responding = 1;
392 			unlock(&q->lk);
393 			write(fs->io, q->buf, n);
394 		}else
395 			unlock(&q->lk);
396 
397 		/*
398 		 * exflush might set noresponse at this point, but
399 		 * setting noresponse means don't send a response now;
400 		 * it's okay that we sent a response already.
401 		 */
402 		if(exdebug > 1)
403 			print("exslave %d written %d\n", getpid(), q->rpc.tag);
404 
405 		lock(&fs->r);
406 		last = &fs->work;
407 		for(t = fs->work; t != nil; t = t->next){
408 			if(t == q){
409 				*last = q->next;
410 				break;
411 			}
412 			last = &t->next;
413 		}
414 		unlock(&fs->r);
415 
416 		exfree(q->export);
417 		free(q);
418 
419 		rendclearintr();
420 		lock(&exq.l);
421 		exq.nwaiters++;
422 		unlock(&exq.l);
423 	}
424 	if(exdebug)
425 		fprint(2, "export slaveshutting down\n");
426 	kexit();
427 }
428 
429 Fid*
Exmkfid(Export * fs,int fid)430 Exmkfid(Export *fs, int fid)
431 {
432 	ulong h;
433 	Fid *f, *nf;
434 
435 	nf = mallocz(sizeof(Fid), 1);
436 	if(nf == nil)
437 		return nil;
438 	lock(&fs->fidlock);
439 	h = fid % Nfidhash;
440 	for(f = fs->fid[h]; f != nil; f = f->next){
441 		if(f->fid == fid){
442 			unlock(&fs->fidlock);
443 			free(nf);
444 			return nil;
445 		}
446 	}
447 
448 	nf->next = fs->fid[h];
449 	if(nf->next != nil)
450 		nf->next->last = &nf->next;
451 	nf->last = &fs->fid[h];
452 	fs->fid[h] = nf;
453 
454 	nf->fid = fid;
455 	nf->ref = 1;
456 	nf->attached = 1;
457 	nf->offset = 0;
458 	nf->chan = nil;
459 	unlock(&fs->fidlock);
460 	return nf;
461 }
462 
463 Fid*
Exgetfid(Export * fs,int fid)464 Exgetfid(Export *fs, int fid)
465 {
466 	Fid *f;
467 	ulong h;
468 
469 	lock(&fs->fidlock);
470 	h = fid % Nfidhash;
471 	for(f = fs->fid[h]; f; f = f->next){
472 		if(f->fid == fid){
473 			if(f->attached == 0)
474 				break;
475 			f->ref++;
476 			unlock(&fs->fidlock);
477 			return f;
478 		}
479 	}
480 	unlock(&fs->fidlock);
481 	return nil;
482 }
483 
484 void
Exputfid(Export * fs,Fid * f)485 Exputfid(Export *fs, Fid *f)
486 {
487 	lock(&fs->fidlock);
488 	f->ref--;
489 	if(f->ref == 0 && f->attached == 0){
490 		if(f->chan != nil)
491 			cclose(f->chan);
492 		f->chan = nil;
493 		*f->last = f->next;
494 		if(f->next != nil)
495 			f->next->last = f->last;
496 		unlock(&fs->fidlock);
497 		free(f);
498 		return;
499 	}
500 	unlock(&fs->fidlock);
501 }
502 
503 static char*
Exversion(Export * fs,Fcall * rpc,uchar *)504 Exversion(Export *fs, Fcall *rpc, uchar *)
505 {
506 	if(rpc->msize > Maxrpc)
507 		rpc->msize = Maxrpc;
508 	if(strncmp(rpc->version, "9P", 2) != 0){
509 		rpc->version = "unknown";
510 		return nil;
511 	}
512 
513 	fs->iounit = rpc->msize - IOHDRSZ;
514 	rpc->version = "9P2000";
515 	return nil;
516 }
517 
518 static char*
Exauth(Export *,Fcall *,uchar *)519 Exauth(Export *, Fcall *, uchar *)
520 {
521 	return "vnc: authentication not required";
522 }
523 
524 static char*
Exattach(Export * fs,Fcall * rpc,uchar *)525 Exattach(Export *fs, Fcall *rpc, uchar *)
526 {
527 	Fid *f;
528 	int w;
529 
530 	w = 0;
531 	if(rpc->aname != nil)
532 		w = strtol(rpc->aname, nil, 10);
533 	if(w < 0 || w > fs->nroots)
534 		error(Ebadspec);
535 	f = Exmkfid(fs, rpc->fid);
536 	if(f == nil)
537 		return Einuse;
538 	if(waserror()){
539 		f->attached = 0;
540 		Exputfid(fs, f);
541 		return up->error;
542 	}
543 	f->chan = cclone(fs->roots[w]);
544 	poperror();
545 	rpc->qid = f->chan->qid;
546 	Exputfid(fs, f);
547 	return nil;
548 }
549 
550 static char*
Exclunk(Export * fs,Fcall * rpc,uchar *)551 Exclunk(Export *fs, Fcall *rpc, uchar *)
552 {
553 	Fid *f;
554 
555 	f = Exgetfid(fs, rpc->fid);
556 	if(f != nil){
557 		f->attached = 0;
558 		Exputfid(fs, f);
559 	}
560 	return nil;
561 }
562 
563 static char*
Exwalk(Export * fs,Fcall * rpc,uchar *)564 Exwalk(Export *fs, Fcall *rpc, uchar *)
565 {
566 	Fid *volatile f, *volatile nf;
567 	Walkqid *wq;
568 	Chan *c;
569 	int i, nwname;
570 	int volatile isnew;
571 
572 	f = Exgetfid(fs, rpc->fid);
573 	if(f == nil)
574 		return Enofid;
575 	nf = nil;
576 	if(waserror()){
577 		Exputfid(fs, f);
578 		if(nf != nil)
579 			Exputfid(fs, nf);
580 		return up->error;
581 	}
582 
583 	/*
584 	 * optional clone, but don't attach it until the walk succeeds.
585 	 */
586 	if(rpc->fid != rpc->newfid){
587 		nf = Exmkfid(fs, rpc->newfid);
588 		if(nf == nil)
589 			error(Einuse);
590 		nf->attached = 0;
591 		isnew = 1;
592 	}else{
593 		nf = Exgetfid(fs, rpc->fid);
594 		isnew = 0;
595 	}
596 
597 	/*
598 	 * let the device do the work
599 	 */
600 	c = f->chan;
601 	nwname = rpc->nwname;
602 	wq = (*devtab[c->type]->walk)(c, nf->chan, rpc->wname, nwname);
603 	if(wq == nil)
604 		error(Enonexist);
605 
606 	poperror();
607 
608 	/*
609 	 * copy qid array
610 	 */
611 	for(i = 0; i < wq->nqid; i++)
612 		rpc->wqid[i] = wq->qid[i];
613 	rpc->nwqid = wq->nqid;
614 
615 	/*
616 	 * update the channel if everything walked correctly.
617 	 */
618 	if(isnew && wq->nqid == nwname){
619 		nf->chan = wq->clone;
620 		nf->attached = 1;
621 	}
622 
623 	free(wq);
624 	Exputfid(fs, f);
625 	Exputfid(fs, nf);
626 	return nil;
627 }
628 
629 static char*
Exopen(Export * fs,Fcall * rpc,uchar *)630 Exopen(Export *fs, Fcall *rpc, uchar *)
631 {
632 	Fid *volatile f;
633 	Chan *c;
634 	int iou;
635 
636 	f = Exgetfid(fs, rpc->fid);
637 	if(f == nil)
638 		return Enofid;
639 	if(waserror()){
640 		Exputfid(fs, f);
641 		return up->error;
642 	}
643 	c = f->chan;
644 	c = (*devtab[c->type]->open)(c, rpc->mode);
645 	poperror();
646 
647 	f->chan = c;
648 	f->offset = 0;
649 	rpc->qid = f->chan->qid;
650 	iou = f->chan->iounit;
651 	if(iou > fs->iounit)
652 		iou = fs->iounit;
653 	rpc->iounit = iou;
654 	Exputfid(fs, f);
655 	return nil;
656 }
657 
658 static char*
Excreate(Export * fs,Fcall * rpc,uchar *)659 Excreate(Export *fs, Fcall *rpc, uchar *)
660 {
661 	Fid *f;
662 	Chan *c;
663 	int iou;
664 
665 	f = Exgetfid(fs, rpc->fid);
666 	if(f == nil)
667 		return Enofid;
668 	if(waserror()){
669 		Exputfid(fs, f);
670 		return up->error;
671 	}
672 	c = f->chan;
673 	(*devtab[c->type]->create)(c, rpc->name, rpc->mode, rpc->perm);
674 	poperror();
675 
676 	f->chan = c;
677 	rpc->qid = f->chan->qid;
678 	iou = f->chan->iounit;
679 	if(iou > fs->iounit)
680 		iou = fs->iounit;
681 	rpc->iounit = iou;
682 	Exputfid(fs, f);
683 	return nil;
684 }
685 
686 static char*
Exread(Export * fs,Fcall * rpc,uchar * buf)687 Exread(Export *fs, Fcall *rpc, uchar *buf)
688 {
689 	Fid *f;
690 	Chan *c;
691 	long off;
692 
693 	f = Exgetfid(fs, rpc->fid);
694 	if(f == nil)
695 		return Enofid;
696 
697 	c = f->chan;
698 
699 	if(waserror()){
700 		Exputfid(fs, f);
701 		return up->error;
702 	}
703 
704 	rpc->data = (char*)buf;
705 	off = rpc->offset;
706 	c->offset = off;
707 	rpc->count = (*devtab[c->type]->read)(c, rpc->data, rpc->count, off);
708 	poperror();
709 	Exputfid(fs, f);
710 	return nil;
711 }
712 
713 static char*
Exwrite(Export * fs,Fcall * rpc,uchar *)714 Exwrite(Export *fs, Fcall *rpc, uchar *)
715 {
716 	Fid *f;
717 	Chan *c;
718 
719 	f = Exgetfid(fs, rpc->fid);
720 	if(f == nil)
721 		return Enofid;
722 	if(waserror()){
723 		Exputfid(fs, f);
724 		return up->error;
725 	}
726 	c = f->chan;
727 	if(c->qid.type & QTDIR)
728 		error(Eisdir);
729 	rpc->count = (*devtab[c->type]->write)(c, rpc->data, rpc->count, rpc->offset);
730 	poperror();
731 	Exputfid(fs, f);
732 	return nil;
733 }
734 
735 static char*
Exstat(Export * fs,Fcall * rpc,uchar * buf)736 Exstat(Export *fs, Fcall *rpc, uchar *buf)
737 {
738 	Fid *f;
739 	Chan *c;
740 
741 	f = Exgetfid(fs, rpc->fid);
742 	if(f == nil)
743 		return Enofid;
744 	if(waserror()){
745 		Exputfid(fs, f);
746 		return up->error;
747 	}
748 	c = f->chan;
749 	rpc->stat = buf;
750 	rpc->nstat = (*devtab[c->type]->stat)(c, rpc->stat, Maxrpc);
751 	poperror();
752 	Exputfid(fs, f);
753 	return nil;
754 }
755 
756 static char*
Exwstat(Export * fs,Fcall * rpc,uchar *)757 Exwstat(Export *fs, Fcall *rpc, uchar *)
758 {
759 	Fid *f;
760 	Chan *c;
761 
762 	f = Exgetfid(fs, rpc->fid);
763 	if(f == nil)
764 		return Enofid;
765 	if(waserror()){
766 		Exputfid(fs, f);
767 		return up->error;
768 	}
769 	c = f->chan;
770 	(*devtab[c->type]->wstat)(c, rpc->stat, rpc->nstat);
771 	poperror();
772 	Exputfid(fs, f);
773 	return nil;
774 }
775 
776 static char*
Exremove(Export * fs,Fcall * rpc,uchar *)777 Exremove(Export *fs, Fcall *rpc, uchar *)
778 {
779 	Fid *f;
780 	Chan *c;
781 
782 	f = Exgetfid(fs, rpc->fid);
783 	if(f == nil)
784 		return Enofid;
785 	if(waserror()){
786 		Exputfid(fs, f);
787 		return up->error;
788 	}
789 	c = f->chan;
790 	(*devtab[c->type]->remove)(c);
791 	poperror();
792 
793 	/*
794 	 * chan is already clunked by remove.
795 	 * however, we need to recover the chan,
796 	 * and follow sysremove's lead in making to point to root.
797 	 */
798 	c->type = 0;
799 
800 	f->attached = 0;
801 	Exputfid(fs, f);
802 	return nil;
803 }
804