xref: /plan9-contrib/sys/src/lib9p/srv.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5 #include <thread.h>
6 #include <9p.h>
7 
8 static char Ebadattach[] = "unknown specifier in attach";
9 static char Ebadoffset[] = "bad offset";
10 static char Ebadcount[] = "bad count";
11 static char Ebotch[] = "9P protocol botch";
12 static char Ecreatenondir[] = "create in non-directory";
13 static char Edupfid[] = "duplicate fid";
14 static char Eduptag[] = "duplicate tag";
15 static char Eisdir[] = "is a directory";
16 static char Enocreate[] = "create prohibited";
17 static char Enomem[] = "out of memory";
18 static char Enoremove[] = "remove prohibited";
19 static char Enostat[] = "stat prohibited";
20 static char Enotfound[] = "file not found";
21 static char Enowrite[] = "write prohibited";
22 static char Enowstat[] = "wstat prohibited";
23 static char Eperm[] = "permission denied";
24 static char Eunknownfid[] = "unknown fid";
25 static char Ebaddir[] = "bad directory in wstat";
26 static char Ewalknodir[] = "walk in non-directory";
27 
28 static void
29 setfcallerror(Fcall *f, char *err)
30 {
31 	f->ename = err;
32 	f->type = Rerror;
33 }
34 
35 static void
36 changemsize(Srv *srv, int msize)
37 {
38 	if(srv->rbuf && srv->wbuf && srv->msize == msize)
39 		return;
40 	qlock(&srv->rlock);
41 	qlock(&srv->wlock);
42 	srv->msize = msize;
43 	free(srv->rbuf);
44 	free(srv->wbuf);
45 	srv->rbuf = emalloc9p(msize);
46 	srv->wbuf = emalloc9p(msize);
47 	if(srv->rbuf==nil || srv->wbuf==nil)
48 		sysfatal("out of memory");	/* BUG */
49 	qunlock(&srv->rlock);
50 	qunlock(&srv->wlock);
51 }
52 
53 static Req*
54 getreq(Srv *s)
55 {
56 	long n;
57 	uchar *buf;
58 	Fcall f;
59 	Req *r;
60 
61 	qlock(&s->rlock);
62 	if((n = read9pmsg(s->infd, s->rbuf, s->msize)) <= 0){
63 		qunlock(&s->rlock);
64 		return nil;
65 	}
66 
67 	buf = emalloc9p(n);
68 	memmove(buf, s->rbuf, n);
69 	qunlock(&s->rlock);
70 
71 	if(convM2S(buf, n, &f) != n){
72 		free(buf);
73 		return nil;
74 	}
75 
76 	if((r=allocreq(s->rpool, f.tag)) == nil){	/* duplicate tag: cons up a fake Req */
77 		r = emalloc9p(sizeof *r);
78 		incref(&r->ref);
79 		r->tag = f.tag;
80 		r->ifcall = f;
81 		r->error = Eduptag;
82 		r->buf = buf;
83 		r->responded = 0;
84 		r->type = 0;
85 		r->srv = s;
86 		r->pool = nil;
87 if(chatty9p)
88 	fprint(2, "<-%d- %F: dup tag\n", s->infd, &f);
89 		return r;
90 	}
91 
92 	r->srv = s;
93 	r->responded = 0;
94 	r->buf = buf;
95 	r->ifcall = f;
96 	memset(&r->ofcall, 0, sizeof r->ofcall);
97 	r->type = r->ifcall.type;
98 
99 if(chatty9p)
100 	if(r->error)
101 		fprint(2, "<-%d- %F: %s\n", s->infd, &r->ifcall, r->error);
102 	else
103 		fprint(2, "<-%d- %F\n", s->infd, &r->ifcall);
104 
105 	return r;
106 }
107 
108 static void
109 filewalk(Req *r)
110 {
111 	int i;
112 	File *f;
113 
114 	f = r->fid->file;
115 	assert(f != nil);
116 
117 	incref(f);
118 	for(i=0; i<r->ifcall.nwname; i++)
119 		if(f = walkfile(f, r->ifcall.wname[i]))
120 			r->ofcall.wqid[i] = f->qid;
121 		else
122 			break;
123 
124 	r->ofcall.nwqid = i;
125 	if(f){
126 		r->newfid->file = f;
127 		r->newfid->qid = r->newfid->file->qid;
128 	}
129 	respond(r, nil);
130 }
131 
132 static void
133 conswalk(Req *r, char *(*clone)(Fid*, Fid*), char *(*walk1)(Fid*, char*, Qid*))
134 {
135 	int i;
136 	char *e;
137 
138 	if(r->fid == r->newfid && r->ifcall.nwname > 1){
139 		respond(r, "lib9p: unused documented feature not implemented");
140 		return;
141 	}
142 
143 	if(r->fid != r->newfid){
144 		r->newfid->qid = r->fid->qid;
145 		if(clone && (e = clone(r->fid, r->newfid))){
146 			respond(r, e);
147 			return;
148 		}
149 	}
150 
151 	e = nil;
152 	for(i=0; i<r->ifcall.nwname; i++){
153 		if(e = walk1(r->newfid, r->ifcall.wname[i], &r->ofcall.wqid[i]))
154 			break;
155 		r->newfid->qid = r->ofcall.wqid[i];
156 	}
157 
158 	r->ofcall.nwqid = i;
159 	if(e && i==0)
160 		respond(r, e);
161 	else
162 		respond(r, nil);
163 }
164 
165 void
166 srv(Srv *srv)
167 {
168 	int o, p;
169 	Req *r;
170 	char e[ERRMAX];
171 
172 	fmtinstall('D', dirfmt);
173 	fmtinstall('F', fcallfmt);
174 
175 	if(srv->fpool == nil)
176 		srv->fpool = allocfidpool(srv->destroyfid);
177 	if(srv->rpool == nil)
178 		srv->rpool = allocreqpool(srv->destroyreq);
179 	if(srv->msize == 0)
180 		srv->msize = 8192+IOHDRSZ;
181 
182 	changemsize(srv, srv->msize);
183 
184 	srv->fpool->srv = srv;
185 	srv->rpool->srv = srv;
186 
187 	while(r = getreq(srv)){
188 		if(r->error){
189 			respond(r, r->error);
190 			continue;
191 		}
192 
193 		switch(r->type){
194 		default:
195 			respond(r, "unknown message");
196 			break;
197 
198 		case Tversion:
199 			if(strncmp(r->ifcall.version, "9P", 2) != 0){
200 				r->ofcall.version = "unknown";
201 				respond(r, nil);
202 				break;
203 			}
204 
205 			r->ofcall.version = "9P2000";
206 			r->ofcall.msize = r->ifcall.msize;
207 			respond(r, nil);
208 			break;
209 
210 		case Tauth:
211 			if((r->afid = allocfid(srv->fpool, r->ifcall.afid)) == nil){
212 				respond(r, Edupfid);
213 				break;
214 			}
215 			if(srv->auth)
216 				srv->auth(r);
217 			else{
218 				snprint(e, sizeof e, "%s: authentication not required", argv0);
219 				respond(r, e);
220 			}
221 			break;
222 
223 		case Tattach:
224 			if((r->fid = allocfid(srv->fpool, r->ifcall.fid)) == nil){
225 				respond(r, Edupfid);
226 				break;
227 			}
228 			r->afid = nil;
229 			if(r->ifcall.afid != NOFID && (r->afid = lookupfid(srv->fpool, r->ifcall.afid)) == nil){
230 				respond(r, Eunknownfid);
231 				break;
232 			}
233 			r->fid->uid = estrdup9p(r->ifcall.uname);
234 			if(srv->tree){
235 				r->fid->file = srv->tree->root;
236 				/* BUG? incref(r->fid->file) ??? */
237 				r->ofcall.qid = r->fid->file->qid;
238 				r->fid->qid = r->ofcall.qid;
239 			}
240 			if(srv->attach)
241 				srv->attach(r);
242 			else
243 				respond(r, nil);
244 			break;
245 
246 		case Tflush:
247 			r->oldreq = lookupreq(srv->rpool, r->ifcall.oldtag);
248 			if(r->oldreq == nil || r->oldreq == r)
249 				respond(r, nil);
250 			else if(srv->flush)
251 				srv->flush(r);
252 			else
253 				sysfatal("outstanding message but flush not provided");
254 			break;
255 
256 		case Twalk:
257 			if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
258 				respond(r, Eunknownfid);
259 				break;
260 			}
261 			if(r->fid->omode != -1){
262 				respond(r, "cannot clone open fid");
263 				break;
264 			}
265 			if(r->ifcall.nwname && !(r->fid->qid.type&QTDIR)){
266 				respond(r, Ewalknodir);
267 				break;
268 			}
269 			if(r->ifcall.fid != r->ifcall.newfid){
270 				if((r->newfid = allocfid(srv->fpool, r->ifcall.newfid)) == nil){
271 					respond(r, Edupfid);
272 					break;
273 				}
274 				r->newfid->uid = estrdup9p(r->fid->uid);
275 			}else{
276 				incref(&r->fid->ref);
277 				r->newfid = r->fid;
278 			}
279 
280 			if(r->fid->file){
281 				filewalk(r);
282 			}else if(srv->walk1)
283 				conswalk(r, srv->clone, srv->walk1);
284 			else if(srv->walk)
285 				srv->walk(r);
286 			else
287 				sysfatal("no walk function, no file trees");
288 			break;
289 
290 		case Topen:
291 			if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
292 				respond(r, Eunknownfid);
293 				break;
294 			}
295 			if(r->fid->omode != -1){
296 				respond(r, Ebotch);
297 				break;
298 			}
299 			if((r->fid->qid.type&QTDIR) && (r->ifcall.mode&~ORCLOSE) != OREAD){
300 				respond(r, Eisdir);
301 				break;
302 			}
303 			r->ofcall.qid = r->fid->qid;
304 			switch(r->ifcall.mode&3){
305 			default:
306 				assert(0);
307 			case OREAD:
308 				p = AREAD;
309 				break;
310 			case OWRITE:
311 				p = AWRITE;
312 				break;
313 			case ORDWR:
314 				p = AREAD|AWRITE;
315 				break;
316 			case OEXEC:
317 				p = AEXEC;
318 				break;
319 			}
320 			if(r->ifcall.mode&OTRUNC)
321 				p |= AWRITE;
322 			if((r->fid->qid.type&QTDIR) && p!=AREAD){
323 				respond(r, Eperm);
324 				break;
325 			}
326 			if(r->fid->file){
327 				if(!hasperm(r->fid->file, r->fid->uid, p)){
328 					respond(r, Eperm);
329 					break;
330 				}
331 			/* BUG RACE */
332 				if((r->ifcall.mode&ORCLOSE)
333 				&& !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){
334 					respond(r, Eperm);
335 					break;
336 				}
337 				r->ofcall.qid = r->fid->file->qid;
338 				if((r->ofcall.qid.type&QTDIR)
339 				&& (r->fid->rdir = opendirfile(r->fid->file)) == nil){
340 					respond(r, "opendirfile failed");
341 					break;
342 				}
343 			}
344 			if(srv->open)
345 				srv->open(r);
346 			else
347 				respond(r, nil);
348 			break;
349 
350 		case Tcreate:
351 			if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil)
352 				respond(r, Eunknownfid);
353 			else if(r->fid->omode != -1)
354 				respond(r, Ebotch);
355 			else if(!(r->fid->qid.type&QTDIR))
356 				respond(r, Ecreatenondir);
357 			else if(r->fid->file && !hasperm(r->fid->file, r->fid->uid, AWRITE))
358 				respond(r, Eperm);
359 			else if(srv->create)
360 				srv->create(r);
361 			else
362 				respond(r, Enocreate);
363 			break;
364 
365 		case Tread:
366 			if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
367 				respond(r, Eunknownfid);
368 				break;
369 			}
370 			if(r->ifcall.count < 0){
371 				respond(r, Ebotch);
372 				break;
373 			}
374 			if(r->ifcall.offset < 0
375 			|| ((r->fid->qid.type&QTDIR) && r->ifcall.offset != 0 && r->ifcall.offset != r->fid->diroffset)){
376 				respond(r, Ebadoffset);
377 				break;
378 			}
379 
380 			if(r->ifcall.count > srv->msize - IOHDRSZ)
381 				r->ifcall.count = srv->msize - IOHDRSZ;
382 			if((r->rbuf = emalloc9p(r->ifcall.count)) == nil){
383 				respond(r, Enomem);
384 				break;
385 			}
386 			r->ofcall.data = r->rbuf;
387 			o = r->fid->omode & 3;
388 			if(o != OREAD && o != ORDWR && o != OEXEC){
389 				respond(r, Ebotch);
390 				break;
391 			}
392 			if((r->fid->qid.type&QTDIR) && r->fid->file){
393 				r->ofcall.count = readdirfile(r->fid->rdir, r->rbuf, r->ifcall.count);
394 				respond(r, nil);
395 				break;
396 			}
397 			if(srv->read)
398 				srv->read(r);
399 			else
400 				respond(r, "no srv->read");
401 			break;
402 
403 		case Twrite:
404 			if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
405 				respond(r, Eunknownfid);
406 				break;
407 			}
408 			if(r->ifcall.count < 0){
409 				respond(r, Ebotch);
410 				break;
411 			}
412 			if(r->ifcall.offset < 0){
413 				respond(r, Ebotch);
414 				break;
415 			}
416 			if(r->ifcall.count > srv->msize - IOHDRSZ)
417 				r->ifcall.count = srv->msize - IOHDRSZ;
418 			o = r->fid->omode & 3;
419 			if(o != OWRITE && o != ORDWR){
420 				snprint(e, sizeof e, "write on fid with open mode 0x%ux", r->fid->omode);
421 				respond(r, e);
422 				break;
423 			}
424 			if(srv->write)
425 				srv->write(r);
426 			else
427 				respond(r, "no srv->write");
428 			break;
429 
430 		case Tclunk:
431 			if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil)
432 				respond(r, Eunknownfid);
433 			else
434 				respond(r, nil);
435 			break;
436 
437 		case Tremove:
438 			if((r->fid = removefid(srv->fpool, r->ifcall.fid)) == nil){
439 				respond(r, Eunknownfid);
440 				break;
441 			}
442 			/* BUG RACE */
443 			if(r->fid->file && !hasperm(r->fid->file->parent, r->fid->uid, AWRITE)){
444 				respond(r, Eperm);
445 				break;
446 			}
447 			if(srv->remove)
448 				srv->remove(r);
449 			else
450 				respond(r, r->fid->file ? nil : Enoremove);
451 			break;
452 
453 		case Tstat:
454 			if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil){
455 				respond(r, Eunknownfid);
456 				break;
457 			}
458 			if(r->fid->file){
459 				r->d = r->fid->file->Dir;
460 				if(r->d.name)
461 					r->d.name = estrdup9p(r->d.name);
462 				if(r->d.uid)
463 					r->d.uid = estrdup9p(r->d.uid);
464 				if(r->d.gid)
465 					r->d.gid = estrdup9p(r->d.gid);
466 				if(r->d.muid)
467 					r->d.muid = estrdup9p(r->d.muid);
468 			}
469 			if(srv->stat)
470 				srv->stat(r);
471 			else if(r->fid->file)
472 				respond(r, nil);
473 			else
474 				respond(r, Enostat);
475 			break;
476 
477 		case Twstat:
478 			if((r->fid = lookupfid(srv->fpool, r->ifcall.fid)) == nil)
479 				respond(r, Eunknownfid);
480 			else if(srv->wstat){
481 				if(convM2D(r->ifcall.stat, r->ifcall.nstat, &r->d, (char*)r->ifcall.stat) != r->ifcall.nstat)
482 					respond(r, Ebaddir);
483 				else
484 					srv->wstat(r);
485 			}else
486 				respond(r, Enowstat);
487 			break;
488 		}
489 	}
490 
491 	if(srv->end)
492 		srv->end(srv);
493 }
494 
495 void
496 respond(Req *r, char *error)
497 {
498 	int m, n;
499 	Req *or;
500 	uchar *statbuf, tmp[BIT16SZ];
501 	char errbuf[ERRMAX];
502 	Srv *srv;
503 
504 	srv = r->srv;
505 	assert(srv != nil);
506 
507 	assert(r->responded == 0);
508 	r->responded = 1;
509 
510 	switch(r->ifcall.type){
511 	default:
512 		assert(0);
513 
514 	case Tversion:
515 		assert(error == nil);
516 		changemsize(srv, r->ofcall.msize);
517 		break;
518 
519 	case Tauth:
520 		if(error){
521 			if(r->afid)
522 				closefid(removefid(srv->fpool, r->afid->fid));
523 			break;
524 		}
525 		break;
526 
527 	case Tattach:
528 		if(error){
529 			if(r->fid)
530 				closefid(removefid(srv->fpool, r->fid->fid));
531 			break;
532 		}
533 		break;
534 
535 	case Tflush:
536 		assert(error == nil);
537 		if(r->oldreq){
538 			if(or = removereq(r->pool, r->oldreq->tag)){
539 				assert(or == r->oldreq);
540 				if(or->ifcall.type == Twalk && or->ifcall.fid != or->ifcall.newfid)
541 					closefid(removefid(srv->fpool, or->newfid->fid));
542 				closereq(or);
543 			}
544 			closereq(r->oldreq);
545 		}
546 		r->oldreq = nil;
547 		break;
548 
549 	case Twalk:
550 		if(error || r->ofcall.nwqid < r->ifcall.nwname){
551 			if(r->ifcall.fid != r->ifcall.newfid && r->newfid)
552 				closefid(removefid(srv->fpool, r->newfid->fid));
553 			if (r->ofcall.nwqid==0){
554 				if(error==nil && r->ifcall.nwname!=0)
555 					error = Enotfound;
556 			}else
557 				error = nil;	// No error on partial walks
558 			break;
559 		}else{
560 			if(r->ofcall.nwqid == 0){
561 				/* Just a clone */
562 				r->newfid->qid = r->fid->qid;
563 			}else{
564 				/* if file trees are in use, filewalk took care of the rest */
565 				r->newfid->qid = r->ofcall.wqid[r->ofcall.nwqid-1];
566 			}
567 		}
568 		break;
569 
570 	case Topen:
571 		if(error){
572 			break;
573 		}
574 		if(chatty9p){
575 			snprint(errbuf, sizeof errbuf, "fid mode is 0x%ux\n", r->ifcall.mode);
576 			write(2, errbuf, strlen(errbuf));
577 		}
578 		r->fid->omode = r->ifcall.mode;
579 		r->fid->qid = r->ofcall.qid;
580 		if(r->ofcall.qid.type&QTDIR)
581 			r->fid->diroffset = 0;
582 		break;
583 
584 	case Tcreate:
585 		if(error){
586 			break;
587 		}
588 		r->fid->omode = r->ifcall.mode;
589 		r->fid->qid = r->ofcall.qid;
590 		break;
591 
592 	case Tread:
593 		if(error==nil && (r->fid->qid.type&QTDIR))
594 			r->fid->diroffset += r->ofcall.count;
595 		break;
596 
597 	case Twrite:
598 		if(error)
599 			break;
600 		if(r->fid->file)
601 			r->fid->file->qid.vers++;
602 		break;
603 
604 	case Tclunk:
605 		break;
606 
607 	case Tremove:
608 		if(error)
609 			break;
610 		if(r->fid->file){
611 			if(removefile(r->fid->file) < 0){
612 				snprint(errbuf, ERRMAX, "remove %s: %r",
613 					r->fid->file->name);
614 				error = errbuf;
615 			}
616 			r->fid->file = nil;
617 		}
618 		break;
619 
620 	case Tstat:
621 		if(error)
622 			break;
623 		if(convD2M(&r->d, tmp, BIT16SZ) != BIT16SZ){
624 			error = "convD2M(_,_,BIT16SZ) did not return BIT16SZ";
625 			break;
626 		}
627 		n = GBIT16(tmp)+BIT16SZ;
628 		statbuf = emalloc9p(n);
629 		if(statbuf == nil){
630 			error = "out of memory";
631 			break;
632 		}
633 		r->ofcall.nstat = convD2M(&r->d, statbuf, n);
634 		r->ofcall.stat = statbuf;
635 		if(r->ofcall.nstat < 0){
636 			error = "convD2M fails";
637 			break;
638 		}
639 		break;
640 
641 	case Twstat:
642 		break;
643 	}
644 
645 	r->ofcall.tag = r->ifcall.tag;
646 	r->ofcall.type = r->ifcall.type+1;
647 	if(error)
648 		setfcallerror(&r->ofcall, error);
649 
650 if(chatty9p)
651 	fprint(2, "-%d-> %F\n", srv->outfd, &r->ofcall);
652 
653 	qlock(&srv->wlock);
654 	n = convS2M(&r->ofcall, srv->wbuf, srv->msize);
655 	if(n <= 0){
656 		fprint(2, "n = %d %F\n", n, &r->ofcall);
657 		abort();
658 	}
659 	assert(n > 2);
660 	if(r->pool){	/* not a fake */
661 		closereq(removereq(r->pool, r->ifcall.tag));
662 		closereq(r);
663 	}else
664 		free(r);
665 	m = write(srv->outfd, srv->wbuf, n);
666 	if(m != n)
667 		sysfatal("lib9p srv: write %d returned %d on fd %d: %r", n, m, srv->outfd);
668 	qunlock(&srv->wlock);
669 
670 }
671 
672 int
673 postfd(char *name, int pfd)
674 {
675 	int fd;
676 	char buf[80];
677 
678 	snprint(buf, sizeof buf, "/srv/%s", name);
679 	if(chatty9p)
680 		fprint(2, "postfd %s\n", buf);
681 	fd = create(buf, OWRITE|ORCLOSE|OCEXEC, 0600);
682 	if(fd < 0){
683 		if(chatty9p)
684 			fprint(2, "create fails: %r\n");
685 		return -1;
686 	}
687 	if(fprint(fd, "%d", pfd) < 0){
688 		if(chatty9p)
689 			fprint(2, "write fails: %r\n");
690 		close(fd);
691 		return -1;
692 	}
693 	if(chatty9p)
694 		fprint(2, "postfd successful\n");
695 	return 0;
696 }
697 
698