Blob


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