]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/plumb/fsys.c
grep: error if sbrk fails
[plan9front.git] / sys / src / cmd / plumb / fsys.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <regexp.h>
5 #include <thread.h>
6 #include <auth.h>
7 #include <fcall.h>
8 #include <plumb.h>
9 #include "plumber.h"
10
11 enum
12 {
13         Stack = 16*1024
14 };
15
16 typedef struct Dirtab Dirtab;
17 typedef struct Fid Fid;
18 typedef struct Holdq Holdq;
19 typedef struct Readreq Readreq;
20 typedef struct Sendreq Sendreq;
21
22 struct Dirtab
23 {
24         char            *name;
25         uchar   type;
26         uint            qid;
27         uint            perm;
28         int             nopen;          /* #fids open on this port */
29         Fid             *fopen;
30         Holdq   *holdq;
31         Readreq *readq;
32         Sendreq *sendq;
33 };
34
35 struct Fid
36 {
37         int             fid;
38         int             busy;
39         int             open;
40         int             mode;
41         Qid             qid;
42         Dirtab  *dir;
43         long            offset;         /* zeroed at beginning of each message, read or write */
44         char            *writebuf;              /* partial message written so far; offset tells how much */
45         Fid             *next;
46         Fid             *nextopen;
47 };
48
49 struct Readreq
50 {
51         Fid             *fid;
52         Fcall           *fcall;
53         uchar   *buf;
54         Readreq *next;
55 };
56
57 struct Sendreq
58 {
59         int                     nfid;           /* number of fids that should receive this message */
60         int                     nleft;          /* number left that haven't received it */
61         Fid                     **fid;  /* fid[nfid] */
62         Plumbmsg        *msg;
63         char                    *pack;  /* plumbpack()ed message */
64         int                     npack;  /* length of pack */
65         Sendreq         *next;
66 };
67
68 struct Holdq
69 {
70         Plumbmsg        *msg;
71         Holdq           *next;
72 };
73
74 struct  /* needed because incref() doesn't return value */
75 {
76         Lock;
77         int                     ref;
78 } rulesref;
79
80 enum
81 {
82         DEBUG   = 0,
83         NDIR    = 50,
84         Nhash   = 16,
85
86         Qdir            = 0,
87         Qrules  = 1,
88         Qsend   = 2,
89         Qport   = 3,
90         NQID    = Qport
91 };
92
93 static Dirtab dir[NDIR] =
94 {
95         { ".",                  QTDIR,  Qdir,                   0500|DMDIR },
96         { "rules",              QTFILE, Qrules,         0600 },
97         { "send",               QTFILE, Qsend,          0200 },
98 };
99 static int      ndir = NQID;
100
101 static int              srvfd;
102 static int              srvclosefd;                     /* rock for end of pipe to close */
103 static int              clockfd;
104 static int              clock;
105 static Fid              *fids[Nhash];
106 static QLock    readlock;
107 static QLock    queue;
108 static char     srvfile[128];
109 static int              messagesize = 8192+IOHDRSZ;     /* good start */
110
111 static void     fsysproc(void*);
112 static void fsysrespond(Fcall*, uchar*, char*);
113 static Fid*     newfid(int);
114
115 static Fcall* fsysflush(Fcall*, uchar*, Fid*);
116 static Fcall* fsysversion(Fcall*, uchar*, Fid*);
117 static Fcall* fsysauth(Fcall*, uchar*, Fid*);
118 static Fcall* fsysattach(Fcall*, uchar*, Fid*);
119 static Fcall* fsyswalk(Fcall*, uchar*, Fid*);
120 static Fcall* fsysopen(Fcall*, uchar*, Fid*);
121 static Fcall* fsyscreate(Fcall*, uchar*, Fid*);
122 static Fcall* fsysread(Fcall*, uchar*, Fid*);
123 static Fcall* fsyswrite(Fcall*, uchar*, Fid*);
124 static Fcall* fsysclunk(Fcall*, uchar*, Fid*);
125 static Fcall* fsysremove(Fcall*, uchar*, Fid*);
126 static Fcall* fsysstat(Fcall*, uchar*, Fid*);
127 static Fcall* fsyswstat(Fcall*, uchar*, Fid*);
128
129 Fcall*  (*fcall[Tmax])(Fcall*, uchar*, Fid*) =
130 {
131         [Tflush]        = fsysflush,
132         [Tversion]      = fsysversion,
133         [Tauth] = fsysauth,
134         [Tattach]       = fsysattach,
135         [Twalk] = fsyswalk,
136         [Topen] = fsysopen,
137         [Tcreate]       = fsyscreate,
138         [Tread] = fsysread,
139         [Twrite]        = fsyswrite,
140         [Tclunk]        = fsysclunk,
141         [Tremove]= fsysremove,
142         [Tstat] = fsysstat,
143         [Twstat]        = fsyswstat,
144 };
145
146 char    Ebadfcall[] =   "bad fcall type";
147 char    Eperm[] =       "permission denied";
148 char    Enomem[] =      "malloc failed for buffer";
149 char    Enotdir[] =     "not a directory";
150 char    Enoexist[] =    "plumb file does not exist";
151 char    Eisdir[] =              "file is a directory";
152 char    Ebadmsg[] =     "bad plumb message format";
153 char Enosuchport[] ="no such plumb port";
154 char Enoport[] =        "couldn't find destination for message";
155 char    Einuse[] =      "file already open";
156
157 /*
158  * Add new port.  A no-op if port already exists or is the null string
159  */
160 void
161 addport(char *port)
162 {
163         int i;
164
165         if(port == nil)
166                 return;
167         for(i=NQID; i<ndir; i++)
168                 if(strcmp(port, dir[i].name) == 0)
169                         return;
170         if(i == NDIR){
171                 fprint(2, "plumb: too many ports; max %d\n", NDIR);
172                 return;
173         }
174         ndir++;
175         dir[i].name = estrdup(port);
176         dir[i].qid = i;
177         dir[i].perm = 0400;
178         nports++;
179         ports = erealloc(ports, nports*sizeof(char*));
180         ports[nports-1] = dir[i].name;
181 }
182
183 static ulong
184 getclock(void)
185 {
186         char buf[32];
187
188         seek(clockfd, 0, 0);
189         read(clockfd, buf, sizeof buf);
190         return atoi(buf);
191 }
192
193 void
194 startfsys(void)
195 {
196         int p[2], fd;
197
198         fmtinstall('F', fcallfmt);
199         clockfd = open("/dev/time", OREAD|OCEXEC);
200         clock = getclock();
201         if(pipe(p) < 0)
202                 error("can't create pipe: %r");
203         /* 0 will be server end, 1 will be client end */
204         srvfd = p[0];
205         srvclosefd = p[1];
206         sprint(srvfile, "/srv/plumb.%s.%d", user, getpid());
207         if(putenv("plumbsrv", srvfile) < 0)
208                 error("can't write $plumbsrv: %r");
209         fd = create(srvfile, OWRITE|OCEXEC|ORCLOSE, 0600);
210         if(fd < 0)
211                 error("can't create /srv file: %r");
212         if(fprint(fd, "%d", p[1]) <= 0)
213                 error("can't write /srv/file: %r");
214         /* leave fd open; ORCLOSE will take care of it */
215
216         procrfork(fsysproc, nil, Stack, RFFDG);
217
218         close(p[0]);
219         if(mount(p[1], -1, "/mnt/plumb", MREPL, "") < 0)
220                 error("can't mount /mnt/plumb: %r");
221         close(p[1]);
222 }
223
224 static void
225 fsysproc(void*)
226 {
227         int n;
228         Fcall *t;
229         Fid *f;
230         uchar *buf;
231
232         close(srvclosefd);
233         srvclosefd = -1;
234         t = nil;
235         for(;;){
236                 buf = malloc(messagesize);      /* avoid memset of emalloc */
237                 if(buf == nil)
238                         error("malloc failed: %r");
239                 qlock(&readlock);
240                 n = read9pmsg(srvfd, buf, messagesize);
241                 if(n == 0)
242                         threadexitsall("unmounted");
243                 if(n < 0)
244                         error("mount read: %r");
245                 if(readlock.head == nil)        /* no other processes waiting to read; start one */
246                         proccreate(fsysproc, nil, Stack);
247                 qunlock(&readlock);
248                 if(t == nil)
249                         t = emalloc(sizeof(Fcall));
250                 if(convM2S(buf, n, t) != n)
251                         error("convert error in convM2S");
252                 if(DEBUG)
253                         fprint(2, "<= %F\n", t);
254                 if(fcall[t->type] == nil)
255                         fsysrespond(t, buf, Ebadfcall);
256                 else{
257                         if(t->type==Tversion || t->type==Tauth)
258                                 f = nil;
259                         else
260                                 f = newfid(t->fid);
261                         t = (*fcall[t->type])(t, buf, f);
262                 }
263         }
264 }
265
266 static void
267 fsysrespond(Fcall *t, uchar *buf, char *err)
268 {
269         int n;
270
271         if(err){
272                 t->type = Rerror;
273                 t->ename = err;
274         }else
275                 t->type++;
276         if(buf == nil)
277                 buf = emalloc(messagesize);
278         n = convS2M(t, buf, messagesize);
279         if(n < 0)
280                 error("convert error in convS2M");
281         if(write(srvfd, buf, n) != n)
282                 error("write error in respond");
283         if(DEBUG)
284                 fprint(2, "=> %F\n", t);
285         free(buf);
286 }
287
288 static
289 Fid*
290 newfid(int fid)
291 {
292         Fid *f, *ff, **fh;
293
294         qlock(&queue);
295         ff = nil;
296         fh = &fids[fid&(Nhash-1)];
297         for(f=*fh; f; f=f->next)
298                 if(f->fid == fid)
299                         goto Return;
300                 else if(ff==nil && !f->busy)
301                         ff = f;
302         if(ff){
303                 ff->fid = fid;
304                 f = ff;
305                 goto Return;
306         }
307         f = emalloc(sizeof *f);
308         f->fid = fid;
309         f->next = *fh;
310         *fh = f;
311     Return:
312         qunlock(&queue);
313         return f;
314 }
315
316 static uint
317 dostat(Dirtab *dir, uchar *buf, uint nbuf, uint clock)
318 {
319         Dir d;
320
321         d.qid.type = dir->type;
322         d.qid.path = dir->qid;
323         d.qid.vers = 0;
324         d.mode = dir->perm;
325         d.length = 0;   /* would be nice to do better */
326         d.name = dir->name;
327         d.uid = user;
328         d.gid = user;
329         d.muid = user;
330         d.atime = clock;
331         d.mtime = clock;
332         return convD2M(&d, buf, nbuf);
333 }
334
335 static void
336 queuesend(Dirtab *d, Plumbmsg *m)
337 {
338         Sendreq *s, *t;
339         Fid *f;
340         int i;
341
342         s = emalloc(sizeof(Sendreq));
343         s->nfid = d->nopen;
344         s->nleft = s->nfid;
345         s->fid = emalloc(s->nfid*sizeof(Fid*));
346         i = 0;
347         /* build array of fids open on this channel */
348         for(f=d->fopen; f!=nil; f=f->nextopen)
349                 s->fid[i++] = f;
350         s->msg = m;
351         s->next = nil;
352         /* link to end of queue; drainqueue() searches in sender order so this implements a FIFO */
353         for(t=d->sendq; t!=nil; t=t->next)
354                 if(t->next == nil)
355                         break;
356         if(t == nil)
357                 d->sendq = s;
358         else
359                 t->next = s;
360 }
361
362 static void
363 queueread(Dirtab *d, Fcall *t, uchar *buf, Fid *f)
364 {
365         Readreq *r;
366
367         r = emalloc(sizeof(Readreq));
368         r->fcall = t;
369         r->buf = buf;
370         r->fid = f;
371         r->next = d->readq;
372         d->readq = r;
373 }
374
375 static void
376 drainqueue(Dirtab *d)
377 {
378         Readreq *r, *nextr, *prevr;
379         Sendreq *s, *nexts, *prevs;
380         int i, n;
381
382         prevs = nil;
383         for(s=d->sendq; s!=nil; s=nexts){
384                 nexts = s->next;
385                 for(i=0; i<s->nfid; i++){
386                         prevr = nil;
387                         for(r=d->readq; r!=nil; r=nextr){
388                                 nextr = r->next;
389                                 if(r->fid == s->fid[i]){
390                                         /* pack the message if necessary */
391                                         if(s->pack == nil)
392                                                 s->pack = plumbpack(s->msg, &s->npack);
393                                         /* exchange the stuff... */
394                                         r->fcall->data = s->pack+r->fid->offset;
395                                         n = s->npack - r->fid->offset;
396                                         if(n > messagesize-IOHDRSZ)
397                                                 n = messagesize-IOHDRSZ;
398                                         if(n > r->fcall->count)
399                                                 n = r->fcall->count;
400                                         r->fcall->count = n;
401                                         fsysrespond(r->fcall, r->buf, nil);
402                                         r->fid->offset += n;
403                                         if(r->fid->offset >= s->npack){
404                                                 /* message transferred; delete this fid from send queue */
405                                                 r->fid->offset = 0;
406                                                 s->fid[i] = nil;
407                                                 s->nleft--;
408                                         }
409                                         /* delete read request from queue */
410                                         if(prevr)
411                                                 prevr->next = r->next;
412                                         else
413                                                 d->readq = r->next;
414                                         free(r->fcall);
415                                         free(r);
416                                         break;
417                                 }else
418                                         prevr = r;
419                         }
420                 }
421                 /* if no fids left, delete this send from queue */
422                 if(s->nleft == 0){
423                         free(s->fid);
424                         plumbfree(s->msg);
425                         free(s->pack);
426                         if(prevs)
427                                 prevs->next = s->next;
428                         else
429                                 d->sendq = s->next;
430                         free(s);
431                 }else
432                         prevs = s;
433         }
434 }
435
436 /* can't flush a send because they are always answered synchronously */
437 static void
438 flushqueue(Dirtab *d, int oldtag)
439 {
440         Readreq *r, *prevr;
441
442         prevr = nil;
443         for(r=d->readq; r!=nil; r=r->next){
444                 if(oldtag == r->fcall->tag){
445                         /* delete read request from queue */
446                         if(prevr)
447                                 prevr->next = r->next;
448                         else
449                                 d->readq = r->next;
450                         free(r->fcall);
451                         free(r->buf);
452                         free(r);
453                         return;
454                 }
455                 prevr = r;
456         }
457 }
458
459 /* remove messages awaiting delivery to now-closing fid */
460 static void
461 removesenders(Dirtab *d, Fid *fid)
462 {
463         Sendreq *s, *nexts, *prevs;
464         int i;
465
466         prevs = nil;
467         for(s=d->sendq; s!=nil; s=nexts){
468                 nexts = s->next;
469                 for(i=0; i<s->nfid; i++)
470                         if(fid == s->fid[i]){
471                                 /* delete this fid from send queue */
472                                 s->fid[i] = nil;
473                                 s->nleft--;
474                                 break;
475                         }
476                 /* if no fids left, delete this send from queue */
477                 if(s->nleft == 0){
478                         free(s->fid);
479                         plumbfree(s->msg);
480                         free(s->pack);
481                         if(prevs)
482                                 prevs->next = s->next;
483                         else
484                                 d->sendq = s->next;
485                         free(s);
486                 }else
487                         prevs = s;
488         }
489 }
490
491 static void
492 hold(Plumbmsg *m, Dirtab *d)
493 {
494         Holdq *h, *q;
495
496         h = emalloc(sizeof(Holdq));
497         h->msg = m;
498         /* add to end of queue */
499         if(d->holdq == nil)
500                 d->holdq = h;
501         else{
502                 for(q=d->holdq; q->next!=nil; q=q->next)
503                         ;
504                 q->next = h;
505         }
506 }
507
508 static void
509 queueheld(Dirtab *d)
510 {
511         Holdq *h;
512
513         while(d->holdq != nil){
514                 h = d->holdq;
515                 d->holdq = h->next;
516                 queuesend(d, h->msg);
517                 /* no need to drain queue because we know no-one is reading yet */
518                 free(h);
519         }
520 }
521
522 static void
523 dispose(Fcall *t, uchar *buf, Plumbmsg *m, Ruleset *rs, Exec *e)
524 {
525         int i;
526         char *err;
527
528         qlock(&queue);
529         err = nil;
530         if(m->dst==nil || m->dst[0]=='\0'){
531                 err = Enoport;
532                 if(rs != nil)
533                         err = startup(rs, e);
534                 plumbfree(m);
535         }else
536                 for(i=NQID; i<ndir; i++)
537                         if(strcmp(m->dst, dir[i].name) == 0){
538                                 if(dir[i].nopen == 0){
539                                         err = startup(rs, e);
540                                         if(e!=nil && e->holdforclient)
541                                                 hold(m, &dir[i]);
542                                         else
543                                                 plumbfree(m);
544                                 }else{
545                                         queuesend(&dir[i], m);
546                                         drainqueue(&dir[i]);
547                                 }
548                                 break;
549                         }
550         freeexec(e);
551         qunlock(&queue);
552         fsysrespond(t, buf, err);
553         free(t);
554 }
555
556 static Fcall*
557 fsysversion(Fcall *t, uchar *buf, Fid*)
558 {
559         if(t->msize < 256){
560                 fsysrespond(t, buf, "version: message size too small");
561                 return t;
562         }
563         if(t->msize < messagesize)
564                 messagesize = t->msize;
565         t->msize = messagesize;
566         if(strncmp(t->version, "9P2000", 6) != 0){
567                 fsysrespond(t, buf, "unrecognized 9P version");
568                 return t;
569         }
570         t->version = "9P2000";
571         fsysrespond(t, buf, nil);
572         return t;
573 }
574
575 static Fcall*
576 fsysauth(Fcall *t, uchar *buf, Fid*)
577 {
578         fsysrespond(t, buf, "plumber: authentication not required");
579         return t;
580 }
581
582 static Fcall*
583 fsysattach(Fcall *t, uchar *buf, Fid *f)
584 {
585         Fcall out;
586
587         if(strcmp(t->uname, user) != 0){
588                 fsysrespond(&out, buf, Eperm);
589                 return t;
590         }
591         f->busy = 1;
592         f->open = 0;
593         f->qid.type = QTDIR;
594         f->qid.path = Qdir;
595         f->qid.vers = 0;
596         f->dir = dir;
597         memset(&out, 0, sizeof(Fcall));
598         out.type = t->type;
599         out.tag = t->tag;
600         out.fid = f->fid;
601         out.qid = f->qid;
602         fsysrespond(&out, buf, nil);
603         return t;
604 }
605
606 static Fcall*
607 fsysflush(Fcall *t, uchar *buf, Fid*)
608 {
609         int i;
610
611         qlock(&queue);
612         for(i=NQID; i<ndir; i++)
613                 flushqueue(&dir[i], t->oldtag);
614         qunlock(&queue);
615         fsysrespond(t, buf, nil);
616         return t;
617 }
618
619 static Fcall*
620 fsyswalk(Fcall *t, uchar *buf, Fid *f)
621 {
622         Fcall out;
623         Fid *nf;
624         ulong path;
625         Dirtab *d, *dir;
626         Qid q;
627         int i;
628         uchar type;
629         char *err;
630
631         if(f->open){
632                 fsysrespond(t, buf, "clone of an open fid");
633                 return t;
634         }
635
636         nf = nil;
637         if(t->fid  != t->newfid){
638                 nf = newfid(t->newfid);
639                 if(nf->busy){
640                         fsysrespond(t, buf, "clone to a busy fid");
641                         return t;
642                 }
643                 nf->busy = 1;
644                 nf->open = 0;
645                 nf->dir = f->dir;
646                 nf->qid = f->qid;
647                 f = nf; /* walk f */
648         }
649
650         out.nwqid = 0;
651         err = nil;
652         dir = f->dir;
653         q = f->qid;
654
655         if(t->nwname > 0){
656                 for(i=0; i<t->nwname; i++){
657                         if((q.type & QTDIR) == 0){
658                                 err = Enotdir;
659                                 break;
660                         }
661                         if(strcmp(t->wname[i], "..") == 0){
662                                 type = QTDIR;
663                                 path = Qdir;
664         Accept:
665                                 q.type = type;
666                                 q.vers = 0;
667                                 q.path = path;
668                                 out.wqid[out.nwqid++] = q;
669                                 continue;
670                         }
671                         d = dir;
672                         d++;    /* skip '.' */
673                         for(; d->name; d++)
674                                 if(strcmp(t->wname[i], d->name) == 0){
675                                         type = d->type;
676                                         path = d->qid;
677                                         dir = d;
678                                         goto Accept;
679                                 }
680                         err = Enoexist;
681                         break;
682                 }
683         }
684
685         out.type = t->type;
686         out.tag = t->tag;
687         if(err!=nil || out.nwqid<t->nwname){
688                 if(nf)
689                         nf->busy = 0;
690         }else if(out.nwqid == t->nwname){
691                 f->qid = q;
692                 f->dir = dir;
693         }
694
695         fsysrespond(&out, buf, err);
696         return t;
697 }
698
699 static Fcall*
700 fsysopen(Fcall *t, uchar *buf, Fid *f)
701 {
702         int m, clearrules, mode;
703
704         clearrules = 0;
705         if(t->mode & OTRUNC){
706                 if(f->qid.path != Qrules)
707                         goto Deny;
708                 clearrules = 1;
709         }
710         /* can't truncate anything, so just disregard */
711         mode = t->mode & ~(OTRUNC|OCEXEC);
712         /* can't execute or remove anything */
713         if(mode==OEXEC || (mode&ORCLOSE))
714                 goto Deny;
715         switch(mode){
716         default:
717                 goto Deny;
718         case OREAD:
719                 m = 0400;
720                 break;
721         case OWRITE:
722                 m = 0200;
723                 break;
724         case ORDWR:
725                 m = 0600;
726                 break;
727         }
728         if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
729                 goto Deny;
730         if(f->qid.path==Qrules && (mode==OWRITE || mode==ORDWR)){
731                 lock(&rulesref);
732                 if(rulesref.ref++ != 0){
733                         rulesref.ref--;
734                         unlock(&rulesref);
735                         fsysrespond(t, buf, Einuse);
736                         return t;
737                 }
738                 unlock(&rulesref);
739         }
740         if(clearrules){
741                 writerules(nil, 0);
742                 for(m=0; rules[m]; m++){
743                         freeruleset(rules[m]);
744                         rules[m] = nil;
745                 }
746         }
747         t->qid = f->qid;
748         t->iounit = 0;
749         qlock(&queue);
750         f->mode = mode;
751         f->open = 1;
752         f->dir->nopen++;
753         f->nextopen = f->dir->fopen;
754         f->dir->fopen = f;
755         queueheld(f->dir);
756         qunlock(&queue);
757         fsysrespond(t, buf, nil);
758         return t;
759
760     Deny:
761         fsysrespond(t, buf, Eperm);
762         return t;
763 }
764
765 static Fcall*
766 fsyscreate(Fcall *t, uchar *buf, Fid*)
767 {
768         fsysrespond(t, buf, Eperm);
769         return t;
770 }
771
772 static Fcall*
773 fsysreadrules(Fcall *t, uchar *buf)
774 {
775         char *p;
776         int n;
777
778         p = printrules();
779         n = strlen(p);
780         t->data = p;
781         if(t->offset >= n)
782                 t->count = 0;
783         else{
784                 t->data = p+t->offset;
785                 if(t->offset+t->count > n)
786                         t->count = n-t->offset;
787         }
788         fsysrespond(t, buf, nil);
789         free(p);
790         return t;
791 }
792
793 static Fcall*
794 fsysread(Fcall *t, uchar *buf, Fid *f)
795 {
796         uchar *b;
797         int i, n, o, e;
798         uint len;
799         Dirtab *d;
800         uint clock;
801
802         if(f->qid.path != Qdir){
803                 if(f->qid.path == Qrules)
804                         return fsysreadrules(t, buf);
805                 /* read from port */
806                 if(f->qid.path < NQID){
807                         fsysrespond(t, buf, "internal error: unknown read port");
808                         return t;
809                 }
810                 qlock(&queue);
811                 queueread(f->dir, t, buf, f);
812                 drainqueue(f->dir);
813                 qunlock(&queue);
814                 return nil;
815         }
816         o = t->offset;
817         e = t->offset+t->count;
818         clock = getclock();
819         b = malloc(messagesize-IOHDRSZ);
820         if(b == nil){
821                 fsysrespond(t, buf, Enomem);
822                 return t;
823         }
824         n = 0;
825         d = dir;
826         d++;    /* first entry is '.' */
827         for(i=0; d->name!=nil && i<e; i+=len){
828                 len = dostat(d, b+n, messagesize-IOHDRSZ-n, clock);
829                 if(len <= BIT16SZ)
830                         break;
831                 if(i >= o)
832                         n += len;
833                 d++;
834         }
835         t->data = (char*)b;
836         t->count = n;
837         fsysrespond(t, buf, nil);
838         free(b);
839         return t;
840 }
841
842 static Fcall*
843 fsyswrite(Fcall *t, uchar *buf, Fid *f)
844 {
845         Plumbmsg *m;
846         int i, n;
847         long count;
848         char *data;
849         Exec *e;
850
851         switch((int)f->qid.path){
852         case Qdir:
853                 fsysrespond(t, buf, Eisdir);
854                 return t;
855         case Qrules:
856                 clock = getclock();
857                 fsysrespond(t, buf, writerules(t->data, t->count));
858                 return t;
859         case Qsend:
860                 if(f->offset == 0){
861                         data = t->data;
862                         count = t->count;
863                 }else{
864                         /* partial message already assembled */
865                         f->writebuf = erealloc(f->writebuf, f->offset + t->count);
866                         memmove(f->writebuf+f->offset, t->data, t->count);
867                         data = f->writebuf;
868                         count = f->offset+t->count;
869                 }
870                 m = plumbunpackpartial(data, count, &n);
871                 if(m == nil){
872                         if(n == 0){
873                                 f->offset = 0;
874                                 free(f->writebuf);
875                                 f->writebuf = nil;
876                                 fsysrespond(t, buf, Ebadmsg);
877                                 return t;
878                         }
879                         /* can read more... */
880                         if(f->offset == 0){
881                                 f->writebuf = emalloc(t->count);
882                                 memmove(f->writebuf, t->data, t->count);
883                         }
884                         /* else buffer has already been grown */
885                         f->offset += t->count;
886                         fsysrespond(t, buf, nil);
887                         return t;
888                 }
889                 /* release partial buffer */
890                 f->offset = 0;
891                 free(f->writebuf);
892                 f->writebuf = nil;
893                 for(i=0; rules[i]; i++)
894                         if((e=matchruleset(m, rules[i])) != nil){
895                                 dispose(t, buf, m, rules[i], e);
896                                 return nil;
897                         }
898                 if(m->dst != nil){
899                         dispose(t, buf, m, nil, nil);
900                         return nil;
901                 }
902                 fsysrespond(t, buf, "no matching plumb rule");
903                 return t;
904         }
905         fsysrespond(t, buf, "internal error: write to unknown file");
906         return t;
907 }
908
909 static Fcall*
910 fsysstat(Fcall *t, uchar *buf, Fid *f)
911 {
912         t->stat = emalloc(messagesize-IOHDRSZ);
913         t->nstat = dostat(f->dir, t->stat, messagesize-IOHDRSZ, clock);
914         fsysrespond(t, buf, nil);
915         free(t->stat);
916         t->stat = nil;
917         return t;
918 }
919
920 static Fcall*
921 fsyswstat(Fcall *t, uchar *buf, Fid*)
922 {
923         fsysrespond(t, buf, Eperm);
924         return t;
925 }
926
927 static Fcall*
928 fsysremove(Fcall *t, uchar *buf, Fid*)
929 {
930         fsysrespond(t, buf, Eperm);
931         return t;
932 }
933
934 static Fcall*
935 fsysclunk(Fcall *t, uchar *buf, Fid *f)
936 {
937         Fid *prev, *p;
938         Dirtab *d;
939
940         qlock(&queue);
941         if(f->open){
942                 d = f->dir;
943                 d->nopen--;
944                 if(d->qid==Qrules && (f->mode==OWRITE || f->mode==ORDWR)){
945                         /*
946                          * just to be sure last rule is parsed; error messages will be lost, though,
947                          * unless last write ended with a blank line
948                          */
949                         writerules(nil, 0);
950                         lock(&rulesref);
951                         rulesref.ref--;
952                         unlock(&rulesref);
953                 }
954                 prev = nil;
955                 for(p=d->fopen; p; p=p->nextopen){
956                         if(p == f){
957                                 if(prev)
958                                         prev->nextopen = f->nextopen;
959                                 else
960                                         d->fopen = f->nextopen;
961                                 removesenders(d, f);
962                                 break;
963                         }
964                         prev = p;
965                 }
966         }
967         f->busy = 0;
968         f->open = 0;
969         f->offset = 0;
970         if(f->writebuf != nil){
971                 free(f->writebuf);
972                 f->writebuf = nil;
973         }
974         qunlock(&queue);
975         fsysrespond(t, buf, nil);
976         return t;
977 }