xref: /plan9/sys/src/cmd/webfs/fs.c (revision 9dfc0cb2ef9edafc5a26f7e8dbe4ef1ca2b37021)
1 /*
2  * Web file system.  Conventionally mounted at /mnt/web
3  *
4  *	ctl				send control messages (might go away)
5  *	cookies			list of cookies, editable
6  *	clone			open and read to obtain new connection
7  *	n				connection directory
8  *		ctl				control messages (like get url)
9  *		body				retrieved data
10  *		content-type		mime content-type of body
11  *		postbody			data to be posted
12  *		parsed			parsed version of url
13  * 			url				entire url
14  *			scheme			http, ftp, etc.
15  *			host				hostname
16  *			path				path on host
17  *			query			query after path
18  *			fragment			#foo anchor reference
19  *			user				user name (ftp)
20  *			password			password (ftp)
21  *			ftptype			transfer mode (ftp)
22  */
23 
24 #include <u.h>
25 #include <libc.h>
26 #include <bio.h>
27 #include <ip.h>
28 #include <plumb.h>
29 #include <thread.h>
30 #include <fcall.h>
31 #include <9p.h>
32 #include "dat.h"
33 #include "fns.h"
34 
35 int fsdebug;
36 
37 enum
38 {
39 	Qroot,
40 	Qrootctl,
41 	Qclone,
42 	Qcookies,
43 	Qclient,
44 	Qctl,
45 	Qbody,
46 	Qbodyext,
47 	Qcontenttype,
48 	Qpostbody,
49 	Qparsed,
50 	Qurl,
51 	Qscheme,
52 	Qschemedata,
53 	Quser,
54 	Qpasswd,
55 	Qhost,
56 	Qport,
57 	Qpath,
58 	Qquery,
59 	Qfragment,
60 	Qftptype,
61 	Qend,
62 };
63 
64 #define PATH(type, n)	((type)|((n)<<8))
65 #define TYPE(path)		((int)(path) & 0xFF)
66 #define NUM(path)		((uint)(path)>>8)
67 
68 Channel *creq;
69 Channel *creqwait;
70 Channel *cclunk;
71 Channel *cclunkwait;
72 
73 typedef struct Tab Tab;
74 struct Tab
75 {
76 	char *name;
77 	ulong mode;
78 	int offset;
79 };
80 
81 Tab tab[] =
82 {
83 	"/",			DMDIR|0555,		0,
84 	"ctl",			0666,			0,
85 	"clone",		0666,			0,
86 	"cookies",		0666,			0,
87 	"XXX",		DMDIR|0555,		0,
88 	"ctl",			0666,			0,
89 	"body",		0444,			0,
90 	"XXX",		0444,			0,
91 	"contenttype",	0444,			0,
92 	"postbody",	0666,			0,
93 	"parsed",		DMDIR|0555,		0,
94 	"url",			0444,			offsetof(Url, url),
95 	"scheme",		0444,			offsetof(Url, scheme),
96 	"schemedata",	0444,			offsetof(Url, schemedata),
97 	"user",		0444,			offsetof(Url, user),
98 	"passwd",		0444,			offsetof(Url, passwd),
99 	"host",		0444,			offsetof(Url, host),
100 	"port",		0444,			offsetof(Url, port),
101 	"path",		0444,			offsetof(Url, path),
102 	"query",		0444,			offsetof(Url, query),
103 	"fragment",	0444,			offsetof(Url, fragment),
104 	"ftptype",		0444,			offsetof(Url, ftp.type),
105 };
106 
107 ulong time0;
108 
109 static void
fillstat(Dir * d,uvlong path,ulong length,char * ext)110 fillstat(Dir *d, uvlong path, ulong length, char *ext)
111 {
112 	Tab *t;
113 	int type;
114 	char buf[32];
115 
116 	memset(d, 0, sizeof(*d));
117 	d->uid = estrdup("web");
118 	d->gid = estrdup("web");
119 	d->qid.path = path;
120 	d->atime = d->mtime = time0;
121 	d->length = length;
122 	type = TYPE(path);
123 	t = &tab[type];
124 	if(type == Qbodyext) {
125 		snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext);
126 		d->name = estrdup(buf);
127 	}
128 	else if(t->name)
129 		d->name = estrdup(t->name);
130 	else{	/* client directory */
131 		snprint(buf, sizeof buf, "%ud", NUM(path));
132 		d->name = estrdup(buf);
133 	}
134 	d->qid.type = t->mode>>24;
135 	d->mode = t->mode;
136 }
137 
138 static void
fsstat(Req * r)139 fsstat(Req *r)
140 {
141 	fillstat(&r->d, r->fid->qid.path, 0, nil);
142 	respond(r, nil);
143 }
144 
145 static int
rootgen(int i,Dir * d,void *)146 rootgen(int i, Dir *d, void*)
147 {
148 	char buf[32];
149 
150 	i += Qroot+1;
151 	if(i < Qclient){
152 		fillstat(d, i, 0, nil);
153 		return 0;
154 	}
155 	i -= Qclient;
156 	if(i < nclient){
157 		fillstat(d, PATH(Qclient, i), 0, nil);
158 		snprint(buf, sizeof buf, "%d", i);
159 		free(d->name);
160 		d->name = estrdup(buf);
161 		return 0;
162 	}
163 	return -1;
164 }
165 
166 static int
clientgen(int i,Dir * d,void * aux)167 clientgen(int i, Dir *d, void *aux)
168 {
169 	Client *c;
170 
171 	c = aux;
172 	i += Qclient+1;
173 	if(i <= Qparsed){
174 		fillstat(d, PATH(i, c->num), 0, c->ext);
175 		return 0;
176 	}
177 	return -1;
178 }
179 
180 static int
parsedgen(int i,Dir * d,void * aux)181 parsedgen(int i, Dir *d, void *aux)
182 {
183 	Client *c;
184 
185 	c = aux;
186 	i += Qparsed+1;
187 	if(i < Qend){
188 		fillstat(d, PATH(i, c->num), 0, nil);
189 		return 0;
190 	}
191 	return -1;
192 }
193 
194 static void
fsread(Req * r)195 fsread(Req *r)
196 {
197 	char *s;
198 	char e[ERRMAX];
199 	Client *c;
200 	ulong path;
201 
202 	path = r->fid->qid.path;
203 	switch(TYPE(path)){
204 	default:
205 		snprint(e, sizeof e, "bug in webfs path=%lux\n", path);
206 		respond(r, e);
207 		break;
208 
209 	case Qroot:
210 		dirread9p(r, rootgen, nil);
211 		respond(r, nil);
212 		break;
213 
214 	case Qrootctl:
215 		globalctlread(r);
216 		break;
217 
218 	case Qcookies:
219 		cookieread(r);
220 		break;
221 
222 	case Qclient:
223 		dirread9p(r, clientgen, client[NUM(path)]);
224 		respond(r, nil);
225 		break;
226 
227 	case Qctl:
228 		ctlread(r, client[NUM(path)]);
229 		break;
230 
231 	case Qcontenttype:
232 		c = client[NUM(path)];
233 		if(c->contenttype == nil)
234 			r->ofcall.count = 0;
235 		else
236 			readstr(r, c->contenttype);
237 		respond(r, nil);
238 		break;
239 
240 	case Qpostbody:
241 		c = client[NUM(path)];
242 		readbuf(r, c->postbody, c->npostbody);
243 		respond(r, nil);
244 		break;
245 
246 	case Qbody:
247 	case Qbodyext:
248 		c = client[NUM(path)];
249 		if(c->iobusy){
250 			respond(r, "already have i/o pending");
251 			break;
252 		}
253 		c->iobusy = 1;
254 		sendp(c->creq, r);
255 		break;
256 
257 	case Qparsed:
258 		dirread9p(r, parsedgen, client[NUM(path)]);
259 		respond(r, nil);
260 		break;
261 
262 	case Qurl:
263 	case Qscheme:
264 	case Qschemedata:
265 	case Quser:
266 	case Qpasswd:
267 	case Qhost:
268 	case Qport:
269 	case Qpath:
270 	case Qquery:
271 	case Qfragment:
272 	case Qftptype:
273 		c = client[NUM(path)];
274 		r->ofcall.count = 0;
275 		if(c->url != nil
276 		&& (s = *(char**)((uintptr)c->url+tab[TYPE(path)].offset)) != nil)
277 			readstr(r, s);
278 		respond(r, nil);
279 		break;
280 	}
281 }
282 
283 static void
fswrite(Req * r)284 fswrite(Req *r)
285 {
286 	int m;
287 	ulong path;
288 	char e[ERRMAX], *buf, *cmd, *arg;
289 	Client *c;
290 
291 	path = r->fid->qid.path;
292 	switch(TYPE(path)){
293 	default:
294 		snprint(e, sizeof e, "bug in webfs path=%lux\n", path);
295 		respond(r, e);
296 		break;
297 
298 	case Qcookies:
299 		cookiewrite(r);
300 		break;
301 
302 	case Qrootctl:
303 	case Qctl:
304 		if(r->ifcall.count >= 1024){
305 			respond(r, "ctl message too long");
306 			return;
307 		}
308 		buf = estredup(r->ifcall.data, (char*)r->ifcall.data+r->ifcall.count);
309 		cmd = buf;
310 		arg = strpbrk(cmd, "\t ");
311 		if(arg){
312 			*arg++ = '\0';
313 			arg += strspn(arg, "\t ");
314 		}else
315 			arg = "";
316 		r->ofcall.count = r->ifcall.count;
317 		if(TYPE(path)==Qrootctl){
318 			if(!ctlwrite(r, &globalctl, cmd, arg)
319 			&& !globalctlwrite(r, cmd, arg))
320 				respond(r, "unknown control command");
321 		}else{
322 			c = client[NUM(path)];
323 			if(!ctlwrite(r, &c->ctl, cmd, arg)
324 			&& !clientctlwrite(r, c, cmd, arg))
325 				respond(r, "unknown control command");
326 		}
327 		free(buf);
328 		break;
329 
330 	case Qpostbody:
331 		c = client[NUM(path)];
332 		if(c->bodyopened){
333 			respond(r, "cannot write postbody after opening body");
334 			break;
335 		}
336 		if(r->ifcall.offset >= 128*1024*1024){	/* >128MB is probably a mistake */
337 			respond(r, "offset too large");
338 			break;
339 		}
340 		m = r->ifcall.offset + r->ifcall.count;
341 		if(c->npostbody < m){
342 			c->postbody = erealloc(c->postbody, m);
343 			memset(c->postbody+c->npostbody, 0, m-c->npostbody);
344 			c->npostbody = m;
345 		}
346 		memmove(c->postbody+r->ifcall.offset, r->ifcall.data, r->ifcall.count);
347 		r->ofcall.count = r->ifcall.count;
348 		respond(r, nil);
349 		break;
350 	}
351 }
352 
353 static void
fsopen(Req * r)354 fsopen(Req *r)
355 {
356 	static int need[4] = { 4, 2, 6, 1 };
357 	ulong path;
358 	int n;
359 	Client *c;
360 	Tab *t;
361 
362 	/*
363 	 * lib9p already handles the blatantly obvious.
364 	 * we just have to enforce the permissions we have set.
365 	 */
366 	path = r->fid->qid.path;
367 	t = &tab[TYPE(path)];
368 	n = need[r->ifcall.mode&3];
369 	if((n&t->mode) != n){
370 		respond(r, "permission denied");
371 		return;
372 	}
373 
374 	switch(TYPE(path)){
375 	case Qcookies:
376 		cookieopen(r);
377 		break;
378 
379 	case Qpostbody:
380 		c = client[NUM(path)];
381 		c->havepostbody++;
382 		c->ref++;
383 		respond(r, nil);
384 		break;
385 
386 	case Qbody:
387 	case Qbodyext:
388 		c = client[NUM(path)];
389 		if(c->url == nil){
390 			respond(r, "url is not yet set");
391 			break;
392 		}
393 		c->bodyopened = 1;
394 		c->ref++;
395 		sendp(c->creq, r);
396 		break;
397 
398 	case Qclone:
399 		n = newclient(0);
400 		path = PATH(Qctl, n);
401 		r->fid->qid.path = path;
402 		r->ofcall.qid.path = path;
403 		if(fsdebug)
404 			fprint(2, "open clone => path=%lux\n", path);
405 		t = &tab[Qctl];
406 		/* fall through */
407 	default:
408 		if(t-tab >= Qclient)
409 			client[NUM(path)]->ref++;
410 		respond(r, nil);
411 		break;
412 	}
413 }
414 
415 static void
fsdestroyfid(Fid * fid)416 fsdestroyfid(Fid *fid)
417 {
418 	sendp(cclunk, fid);
419 	recvp(cclunkwait);
420 }
421 
422 static void
fsattach(Req * r)423 fsattach(Req *r)
424 {
425 	if(r->ifcall.aname && r->ifcall.aname[0]){
426 		respond(r, "invalid attach specifier");
427 		return;
428 	}
429 	r->fid->qid.path = PATH(Qroot, 0);
430 	r->fid->qid.type = QTDIR;
431 	r->fid->qid.vers = 0;
432 	r->ofcall.qid = r->fid->qid;
433 	respond(r, nil);
434 }
435 
436 static char*
fswalk1(Fid * fid,char * name,Qid * qid)437 fswalk1(Fid *fid, char *name, Qid *qid)
438 {
439 	int i, n;
440 	ulong path;
441 	char buf[32], *ext;
442 
443 	path = fid->qid.path;
444 	if(!(fid->qid.type&QTDIR))
445 		return "walk in non-directory";
446 
447 	if(strcmp(name, "..") == 0){
448 		switch(TYPE(path)){
449 		case Qparsed:
450 			qid->path = PATH(Qclient, NUM(path));
451 			qid->type = tab[Qclient].mode>>24;
452 			return nil;
453 		case Qclient:
454 		case Qroot:
455 			qid->path = PATH(Qroot, 0);
456 			qid->type = tab[Qroot].mode>>24;
457 			return nil;
458 		default:
459 			return "bug in fswalk1";
460 		}
461 	}
462 
463 	i = TYPE(path)+1;
464 	for(; i<nelem(tab); i++){
465 		if(i==Qclient){
466 			n = atoi(name);
467 			snprint(buf, sizeof buf, "%d", n);
468 			if(n < nclient && strcmp(buf, name) == 0){
469 				qid->path = PATH(i, n);
470 				qid->type = tab[i].mode>>24;
471 				return nil;
472 			}
473 			break;
474 		}
475 		if(i==Qbodyext){
476 			ext = client[NUM(path)]->ext;
477 			snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext);
478 			if(strcmp(buf, name) == 0){
479 				qid->path = PATH(i, NUM(path));
480 				qid->type = tab[i].mode>>24;
481 				return nil;
482 			}
483 		}
484 		else if(strcmp(name, tab[i].name) == 0){
485 			qid->path = PATH(i, NUM(path));
486 			qid->type = tab[i].mode>>24;
487 			return nil;
488 		}
489 		if(tab[i].mode&DMDIR)
490 			break;
491 	}
492 	return "directory entry not found";
493 }
494 
495 static void
fsflush(Req * r)496 fsflush(Req *r)
497 {
498 	Req *or;
499 	int t;
500 	Client *c;
501 	ulong path;
502 
503 	or=r;
504 	while(or->ifcall.type==Tflush)
505 		or = or->oldreq;
506 
507 	if(or->ifcall.type != Tread && or->ifcall.type != Topen)
508 		abort();
509 
510 	path = or->fid->qid.path;
511 	t = TYPE(path);
512 	if(t != Qbody && t != Qbodyext)
513 		abort();
514 
515 	c = client[NUM(path)];
516 	sendp(c->creq, r);
517 	iointerrupt(c->io);
518 }
519 
520 static void
fsthread(void *)521 fsthread(void*)
522 {
523 	ulong path;
524 	Alt a[3];
525 	Fid *fid;
526 	Req *r;
527 
528 	threadsetname("fsthread");
529 	plumbstart();
530 
531 	a[0].op = CHANRCV;
532 	a[0].c = cclunk;
533 	a[0].v = &fid;
534 	a[1].op = CHANRCV;
535 	a[1].c = creq;
536 	a[1].v = &r;
537 	a[2].op = CHANEND;
538 
539 	for(;;){
540 		switch(alt(a)){
541 		case 0:
542 			path = fid->qid.path;
543 			if(TYPE(path)==Qcookies)
544 				cookieclunk(fid);
545 			if(fid->omode != -1 && TYPE(path) >= Qclient)
546 				closeclient(client[NUM(path)]);
547 			sendp(cclunkwait, nil);
548 			break;
549 		case 1:
550 			switch(r->ifcall.type){
551 			case Tattach:
552 				fsattach(r);
553 				break;
554 			case Topen:
555 				fsopen(r);
556 				break;
557 			case Tread:
558 				fsread(r);
559 				break;
560 			case Twrite:
561 				fswrite(r);
562 				break;
563 			case Tstat:
564 				fsstat(r);
565 				break;
566 			case Tflush:
567 				fsflush(r);
568 				break;
569 			default:
570 				respond(r, "bug in fsthread");
571 				break;
572 			}
573 			sendp(creqwait, 0);
574 			break;
575 		}
576 	}
577 }
578 
579 static void
fssend(Req * r)580 fssend(Req *r)
581 {
582 	sendp(creq, r);
583 	recvp(creqwait);	/* avoids need to deal with spurious flushes */
584 }
585 
586 void
initfs(void)587 initfs(void)
588 {
589 	time0 = time(0);
590 	creq = chancreate(sizeof(void*), 0);
591 	creqwait = chancreate(sizeof(void*), 0);
592 	cclunk = chancreate(sizeof(void*), 0);
593 	cclunkwait = chancreate(sizeof(void*), 0);
594 	procrfork(fsthread, nil, STACK, RFNAMEG);
595 }
596 
597 void
takedown(Srv *)598 takedown(Srv*)
599 {
600 	closecookies();
601 	threadexitsall("done");
602 }
603 
604 Srv fs =
605 {
606 .attach=		fssend,
607 .destroyfid=	fsdestroyfid,
608 .walk1=		fswalk1,
609 .open=		fssend,
610 .read=		fssend,
611 .write=		fssend,
612 .stat=		fssend,
613 .flush=		fssend,
614 .end=		takedown,
615 };
616 
617