]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/webfs/fs.c
cwfs: back to previous version
[plan9front.git] / sys / src / cmd / webfs / fs.c
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                 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
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
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
416 fsdestroyfid(Fid *fid)
417 {
418         sendp(cclunk, fid);
419         recvp(cclunkwait);
420 }
421
422 static void
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*
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
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
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
580 fssend(Req *r)
581 {
582         sendp(creq, r);
583         recvp(creqwait);        /* avoids need to deal with spurious flushes */
584 }
585
586 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
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