xref: /plan9-contrib/sys/src/cmd/webfs/fs.c (revision 9a747e4fd48b9f4522c70c07e8f882a15030f964)
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
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
139 fsstat(Req *r)
140 {
141 	fillstat(&r->d, r->fid->qid.path, 0, nil);
142 	respond(r, nil);
143 }
144 
145 static int
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
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
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
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 		s = *(char**)((ulong)c->url+tab[TYPE(path)].offset);
275 		if(s == nil)
276 			r->ofcall.count = 0;
277 		else
278 			readstr(r, s);
279 		respond(r, nil);
280 		break;
281 	}
282 }
283 
284 static void
285 fswrite(Req *r)
286 {
287 	int m;
288 	ulong path;
289 	char e[ERRMAX], *buf, *cmd, *arg;
290 	Client *c;
291 
292 	path = r->fid->qid.path;
293 	switch(TYPE(path)){
294 	default:
295 		snprint(e, sizeof e, "bug in webfs path=%lux\n", path);
296 		respond(r, e);
297 		break;
298 
299 	case Qcookies:
300 		cookiewrite(r);
301 		break;
302 
303 	case Qrootctl:
304 	case Qctl:
305 		if(r->ifcall.count >= 1024){
306 			respond(r, "ctl message too long");
307 			return;
308 		}
309 		buf = estredup(r->ifcall.data, (char*)r->ifcall.data+r->ifcall.count);
310 		cmd = buf;
311 		arg = strpbrk(cmd, "\t ");
312 		if(arg){
313 			*arg++ = '\0';
314 			arg += strspn(arg, "\t ");
315 		}else
316 			arg = "";
317 		r->ofcall.count = r->ifcall.count;
318 		if(TYPE(path)==Qrootctl){
319 			if(!ctlwrite(r, &globalctl, cmd, arg)
320 			&& !globalctlwrite(r, cmd, arg))
321 				respond(r, "unknown control command");
322 		}else{
323 			c = client[NUM(path)];
324 			if(!ctlwrite(r, &c->ctl, cmd, arg)
325 			&& !clientctlwrite(r, c, cmd, arg))
326 				respond(r, "unknown control command");
327 		}
328 		free(buf);
329 		break;
330 
331 	case Qpostbody:
332 		c = client[NUM(path)];
333 		if(c->bodyopened){
334 			respond(r, "cannot write postbody after opening body");
335 			break;
336 		}
337 		if(r->ifcall.offset >= 128*1024*1024){	/* >128MB is probably a mistake */
338 			respond(r, "offset too large");
339 			break;
340 		}
341 		m = r->ifcall.offset + r->ifcall.count;
342 		if(c->npostbody < m){
343 			c->postbody = erealloc(c->postbody, m);
344 			memset(c->postbody+c->npostbody, 0, m-c->npostbody);
345 			c->npostbody = m;
346 		}
347 		memmove(c->postbody+r->ifcall.offset, r->ifcall.data, r->ifcall.count);
348 		r->ofcall.count = r->ifcall.count;
349 		respond(r, nil);
350 		break;
351 	}
352 }
353 
354 static void
355 fsopen(Req *r)
356 {
357 	static int need[4] = { 4, 2, 6, 1 };
358 	ulong path;
359 	int n;
360 	Client *c;
361 	Tab *t;
362 
363 	/*
364 	 * lib9p already handles the blatantly obvious.
365 	 * we just have to enforce the permissions we have set.
366 	 */
367 	path = r->fid->qid.path;
368 	t = &tab[TYPE(path)];
369 	n = need[r->ifcall.mode&3];
370 	if((n&t->mode) != n){
371 		respond(r, "permission denied");
372 		return;
373 	}
374 
375 	switch(TYPE(path)){
376 	case Qcookies:
377 		cookieopen(r);
378 		break;
379 
380 	case Qpostbody:
381 		c = client[NUM(path)];
382 		c->havepostbody++;
383 		c->ref++;
384 		respond(r, nil);
385 		break;
386 
387 	case Qbody:
388 	case Qbodyext:
389 		c = client[NUM(path)];
390 		if(c->url == nil){
391 			respond(r, "url is not yet set");
392 			break;
393 		}
394 		c->bodyopened = 1;
395 		c->ref++;
396 		sendp(c->creq, r);
397 		break;
398 
399 	case Qclone:
400 		n = newclient(0);
401 		path = PATH(Qctl, n);
402 		r->fid->qid.path = path;
403 		r->ofcall.qid.path = path;
404 		if(fsdebug)
405 			fprint(2, "open clone => path=%lux\n", path);
406 		t = &tab[Qctl];
407 		/* fall through */
408 	default:
409 		if(t-tab >= Qclient)
410 			client[NUM(path)]->ref++;
411 		respond(r, nil);
412 		break;
413 	}
414 }
415 
416 static void
417 fsdestroyfid(Fid *fid)
418 {
419 	sendp(cclunk, fid);
420 	recvp(cclunkwait);
421 }
422 
423 static void
424 fsattach(Req *r)
425 {
426 	if(r->ifcall.aname && r->ifcall.aname[0]){
427 		respond(r, "invalid attach specifier");
428 		return;
429 	}
430 	r->fid->qid.path = PATH(Qroot, 0);
431 	r->fid->qid.type = QTDIR;
432 	r->fid->qid.vers = 0;
433 	r->ofcall.qid = r->fid->qid;
434 	respond(r, nil);
435 }
436 
437 static char*
438 fswalk1(Fid *fid, char *name, Qid *qid)
439 {
440 	int i, n;
441 	ulong path;
442 	char buf[32], *ext;
443 
444 	path = fid->qid.path;
445 	if(!(fid->qid.type&QTDIR))
446 		return "walk in non-directory";
447 
448 	if(strcmp(name, "..") == 0){
449 		switch(TYPE(path)){
450 		case Qparsed:
451 			qid->path = PATH(Qclient, NUM(path));
452 			qid->type = tab[Qclient].mode>>24;
453 			return nil;
454 		case Qclient:
455 		case Qroot:
456 			qid->path = PATH(Qroot, 0);
457 			qid->type = tab[Qroot].mode>>24;
458 			return nil;
459 		default:
460 			return "bug in fswalk1";
461 		}
462 	}
463 
464 	i = TYPE(path)+1;
465 	for(; i<nelem(tab); i++){
466 		if(i==Qclient){
467 			n = atoi(name);
468 			snprint(buf, sizeof buf, "%d", n);
469 			if(n < nclient && strcmp(buf, name) == 0){
470 				qid->path = PATH(i, n);
471 				qid->type = tab[i].mode>>24;
472 				return nil;
473 			}
474 			break;
475 		}
476 		if(i==Qbodyext){
477 			ext = client[NUM(path)]->ext;
478 			snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext);
479 			if(strcmp(buf, name) == 0){
480 				qid->path = PATH(i, NUM(path));
481 				qid->type = tab[i].mode>>24;
482 				return nil;
483 			}
484 		}
485 		else if(strcmp(name, tab[i].name) == 0){
486 			qid->path = PATH(i, NUM(path));
487 			qid->type = tab[i].mode>>24;
488 			return nil;
489 		}
490 		if(tab[i].mode&DMDIR)
491 			break;
492 	}
493 	return "directory entry not found";
494 }
495 
496 static void
497 fsflush(Req *r)
498 {
499 	Req *or;
500 	int t;
501 	Client *c;
502 	ulong path;
503 
504 	or=r;
505 	while(or->ifcall.type==Tflush)
506 		or = or->oldreq;
507 
508 	if(or->ifcall.type != Tread && or->ifcall.type != Topen)
509 		abort();
510 
511 	path = or->fid->qid.path;
512 	t = TYPE(path);
513 	if(t != Qbody && t != Qbodyext)
514 		abort();
515 
516 	c = client[NUM(path)];
517 	sendp(c->creq, r);
518 	c->io->interrupt(c->io);
519 }
520 
521 static void
522 fsthread(void*)
523 {
524 	ulong path;
525 	Alt a[3];
526 	Fid *fid;
527 	Req *r;
528 
529 	threadsetname("fsthread");
530 	plumbstart();
531 
532 	a[0].op = CHANRCV;
533 	a[0].c = cclunk;
534 	a[0].v = &fid;
535 	a[1].op = CHANRCV;
536 	a[1].c = creq;
537 	a[1].v = &r;
538 	a[2].op = CHANEND;
539 
540 	for(;;){
541 		switch(alt(a)){
542 		case 0:
543 			path = fid->qid.path;
544 			if(TYPE(path)==Qcookies)
545 				cookieclunk(fid);
546 			if(fid->omode != -1 && TYPE(path) >= Qclient)
547 				closeclient(client[NUM(path)]);
548 			sendp(cclunkwait, nil);
549 			break;
550 		case 1:
551 			switch(r->ifcall.type){
552 			case Tattach:
553 				fsattach(r);
554 				break;
555 			case Topen:
556 				fsopen(r);
557 				break;
558 			case Tread:
559 				fsread(r);
560 				break;
561 			case Twrite:
562 				fswrite(r);
563 				break;
564 			case Tstat:
565 				fsstat(r);
566 				break;
567 			case Tflush:
568 				fsflush(r);
569 				break;
570 			default:
571 				respond(r, "bug in fsthread");
572 				break;
573 			}
574 			sendp(creqwait, 0);
575 			break;
576 		}
577 	}
578 }
579 
580 static void
581 fssend(Req *r)
582 {
583 	sendp(creq, r);
584 	recvp(creqwait);	/* avoids need to deal with spurious flushes */
585 }
586 
587 void
588 initfs(void)
589 {
590 	time0 = time(0);
591 	creq = chancreate(sizeof(void*), 0);
592 	creqwait = chancreate(sizeof(void*), 0);
593 	cclunk = chancreate(sizeof(void*), 0);
594 	cclunkwait = chancreate(sizeof(void*), 0);
595 	procrfork(fsthread, nil, STACK, RFNAMEG);
596 }
597 
598 void
599 takedown(Srv*)
600 {
601 	closecookies();
602 	threadexitsall("done");
603 }
604 
605 Srv fs =
606 {
607 .attach=		fssend,
608 .destroyfid=	fsdestroyfid,
609 .walk1=		fswalk1,
610 .open=		fssend,
611 .read=		fssend,
612 .write=		fssend,
613 .stat=		fssend,
614 .flush=		fssend,
615 .end=		takedown,
616 };
617 
618