]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/exportfs/exportfs.c
exportfs/import: cleanup
[plan9front.git] / sys / src / cmd / exportfs / exportfs.c
1 /*
2  * exportfs - Export a plan 9 name space across a network
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <auth.h>
7 #include <fcall.h>
8 #include <libsec.h>
9 #define Extern
10 #include "exportfs.h"
11
12 #define QIDPATH ((1LL<<48)-1)
13 vlong newqid = 0;
14
15 enum {
16         Encnone,
17         Encssl,
18         Enctls,
19 };
20
21 void (*fcalls[])(Fsrpc*) =
22 {
23         [Tversion]      Xversion,
24         [Tauth] Xauth,
25         [Tflush]        Xflush,
26         [Tattach]       Xattach,
27         [Twalk]         Xwalk,
28         [Topen]         slave,
29         [Tcreate]       Xcreate,
30         [Tclunk]        Xclunk,
31         [Tread]         slave,
32         [Twrite]        slave,
33         [Tremove]       Xremove,
34         [Tstat]         Xstat,
35         [Twstat]        Xwstat,
36 };
37
38 /* accounting and debugging counters */
39 int     filecnt;
40 int     freecnt;
41 int     qidcnt;
42 int     qfreecnt;
43 int     ncollision;
44
45 int     netfd;                          /* initially stdin */
46 int     srvfd = -1;
47 int     nonone = 1;
48 char    *filterp;
49 char    *ealgs = "rc4_256 sha1";
50 char    *aanfilter = "/bin/aan";
51 int     encproto = Encnone;
52 int     readonly;
53
54 static void mksecret(char *, uchar *);
55 static int localread9pmsg(int, void *, uint, void *);
56 static char *anstring  = "tcp!*!0";
57
58 char *netdir = "", *local = "", *remote = "";
59
60 int     filter(int, char *);
61
62 void
63 usage(void)
64 {
65         fprint(2, "usage: %s [-adnsR] [-f dbgfile] [-m msize] [-r root] "
66                 "[-S srvfile] [-e 'crypt hash'] [-P exclusion-file] "
67                 "[-A announce-string] [-B address]\n", argv0);
68         fatal("usage");
69 }
70
71 static void
72 noteconn(int fd)
73 {
74         NetConnInfo *nci;
75
76         nci = getnetconninfo(nil, fd);
77         if (nci == nil)
78                 return;
79         netdir = strdup(nci->dir);
80         local = strdup(nci->lsys);
81         remote = strdup(nci->rsys);
82         freenetconninfo(nci);
83 }
84
85 void
86 main(int argc, char **argv)
87 {
88         char buf[ERRMAX], ebuf[ERRMAX], initial[4], *ini, *srvfdfile;
89         char *dbfile, *srv, *na, *nsfile, *keyspec;
90         int doauth, n, fd;
91         AuthInfo *ai;
92         Fsrpc *r;
93
94         dbfile = "/tmp/exportdb";
95         srv = nil;
96         srvfd = -1;
97         srvfdfile = nil;
98         na = nil;
99         nsfile = nil;
100         keyspec = "";
101         doauth = 0;
102
103         ai = nil;
104         ARGBEGIN{
105         case 'a':
106                 doauth = 1;
107                 break;
108
109         case 'd':
110                 dbg++;
111                 break;
112
113         case 'e':
114                 ealgs = EARGF(usage());
115                 if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
116                         ealgs = nil;
117                 break;
118
119         case 'f':
120                 dbfile = EARGF(usage());
121                 break;
122
123         case 'k':
124                 keyspec = EARGF(usage());
125                 break;
126
127         case 'm':
128                 messagesize = strtoul(EARGF(usage()), nil, 0);
129                 break;
130
131         case 'n':
132                 nonone = 0;
133                 break;
134
135         case 'r':
136                 srv = EARGF(usage());
137                 break;
138
139         case 's':
140                 srv = "/";
141                 break;
142
143         case 'A':
144                 anstring = EARGF(usage());
145                 break;
146
147         case 'B':
148                 na = EARGF(usage());
149                 break;
150
151         case 'F':
152                 /* accepted but ignored, for backwards compatibility */
153                 break;
154
155         case 'N':
156                 nsfile = EARGF(usage());
157                 break;
158
159         case 'P':
160                 patternfile = EARGF(usage());
161                 break;
162
163         case 'R':
164                 readonly = 1;
165                 break;
166
167         case 'S':
168                 if(srvfdfile)
169                         usage();
170                 srvfdfile = EARGF(usage());
171                 break;
172
173         default:
174                 usage();
175         }ARGEND
176         USED(argc, argv);
177
178         if(doauth){
179                 /*
180                  * We use p9any so we don't have to visit this code again, with the
181                  * cost that this code is incompatible with the old world, which
182                  * requires p9sk2. (The two differ in who talks first, so compatibility
183                  * is awkward.)
184                  */
185                 ai = auth_proxy(0, auth_getkey, "proto=p9any role=server %s", keyspec);
186                 if(ai == nil)
187                         fatal("auth_proxy: %r");
188                 if(nonone && strcmp(ai->cuid, "none") == 0)
189                         fatal("exportfs by none disallowed");
190                 if(auth_chuid(ai, nsfile) < 0)
191                         fatal("auth_chuid: %r");
192                 putenv("service", "exportfs");
193         }
194
195         if(srvfdfile){
196                 if((srvfd = open(srvfdfile, ORDWR)) < 0)
197                         fatal("open %s: %r", srvfdfile);
198         }
199
200         if(na){
201                 if(srv == nil)
202                         fatal("-B requires -s");
203
204                 local = "me";
205                 remote = na;
206                 if((fd = dial(netmkaddr(na, 0, "importfs"), 0, 0, 0)) < 0)
207                         fatal("can't dial %s: %r", na);
208         
209                 ai = auth_proxy(fd, auth_getkey, "proto=p9any role=client %s", keyspec);
210                 if(ai == nil)
211                         fatal("%r: %s", na);
212
213                 dup(fd, 0);
214                 dup(fd, 1);
215                 close(fd);
216         }
217
218         exclusions();
219
220         if(dbg) {
221                 n = create(dbfile, OWRITE|OTRUNC, 0666);
222                 dup(n, DFD);
223                 close(n);
224         }
225
226         if(srvfd >= 0 && srv){
227                 fprint(2, "exportfs: -S cannot be used with -r or -s\n");
228                 usage();
229         }
230
231         DEBUG(DFD, "exportfs: started\n");
232
233         rfork(RFNOTEG);
234
235         if(messagesize == 0){
236                 messagesize = iounit(netfd);
237                 if(messagesize == 0)
238                         messagesize = 8192+IOHDRSZ;
239         }
240
241         Workq = emallocz(sizeof(Fsrpc)*Nr_workbufs);
242         fhash = emallocz(sizeof(Fid*)*FHASHSIZE);
243
244         fmtinstall('F', fcallfmt);
245
246         /*
247          * Get tree to serve from network connection,
248          * check we can get there and ack the connection
249          */
250         if(srvfd != -1) {
251                 /* do nothing */
252         }
253         else if(srv) {
254                 chdir(srv);
255                 DEBUG(DFD, "invoked as server for %s", srv);
256                 strncpy(buf, srv, sizeof buf);
257         }
258         else {
259                 noteconn(netfd);
260                 buf[0] = 0;
261                 n = read(0, buf, sizeof(buf)-1);
262                 if(n < 0) {
263                         errstr(buf, sizeof buf);
264                         fprint(0, "read(0): %s", buf);
265                         DEBUG(DFD, "read(0): %s", buf);
266                         exits(buf);
267                 }
268                 buf[n] = 0;
269                 if(chdir(buf) < 0) {
270                         errstr(ebuf, sizeof ebuf);
271                         fprint(0, "chdir(%d:\"%s\"): %s", n, buf, ebuf);
272                         DEBUG(DFD, "chdir(%d:\"%s\"): %s", n, buf, ebuf);
273                         exits(ebuf);
274                 }
275         }
276
277         DEBUG(DFD, "\niniting root\n");
278         initroot();
279
280         DEBUG(DFD, "exportfs: %s\n", buf);
281
282         if(srv == nil && srvfd == -1 && write(0, "OK", 2) != 2)
283                 fatal("open ack write");
284
285         ini = initial;
286         if (readn(netfd, initial, sizeof(initial)) < sizeof(initial))
287                 fatal("can't read initial string: %r");
288
289         if (memcmp(ini, "impo", 4) == 0) {
290                 char buf[128], *p, *args[3];
291
292                 ini = nil;
293                 p = buf;
294                 for(;;){
295                         if ((n = read(netfd, p, 1)) < 0)
296                                 fatal("can't read impo arguments: %r");
297                         if (n == 0)
298                                 fatal("connection closed while reading arguments");
299                         if (*p == '\n') 
300                                 *p = '\0';
301                         if (*p++ == '\0')
302                                 break;
303                         if(p >= buf + sizeof(buf))
304                                 fatal("import parameters too long");
305                 }
306                 
307                 if (tokenize(buf, args, nelem(args)) != 2)
308                         fatal("impo arguments invalid: impo%s...", buf);
309
310                 if (strcmp(args[0], "aan") == 0)
311                         filterp = aanfilter;
312                 else if (strcmp(args[0], "nofilter") != 0)
313                         fatal("import filter argument unsupported: %s", args[0]);
314
315                 if (strcmp(args[1], "ssl") == 0)
316                         encproto = Encssl;
317                 else if (strcmp(args[1], "tls") == 0)
318                         encproto = Enctls;
319                 else if (strcmp(args[1], "clear") != 0)
320                         fatal("import encryption proto unsupported: %s", args[1]);
321
322                 if (encproto == Enctls)
323                         fatal("%s: tls has not yet been implemented", argv[0]);
324         }
325
326         if (encproto != Encnone && ealgs && ai) {
327                 uchar key[16], digest[SHA1dlen];
328                 char fromclientsecret[21];
329                 char fromserversecret[21];
330                 int i;
331
332                 assert(ai->nsecret <= sizeof(key)-4);
333                 memmove(key+4, ai->secret, ai->nsecret);
334
335                 /* exchange random numbers */
336                 srand(truerand());
337                 for(i = 0; i < 4; i++)
338                         key[i+12] = rand();
339
340                 if (ini) 
341                         fatal("Protocol botch: old import");
342                 if(readn(netfd, key, 4) != 4)
343                         fatal("can't read key part; %r");
344
345                 if(write(netfd, key+12, 4) != 4)
346                         fatal("can't write key part; %r");
347
348                 /* scramble into two secrets */
349                 sha1(key, sizeof(key), digest, nil);
350                 mksecret(fromclientsecret, digest);
351                 mksecret(fromserversecret, digest+10);
352
353                 if (filterp)
354                         netfd = filter(netfd, filterp);
355
356                 switch (encproto) {
357                 case Encssl:
358                         netfd = pushssl(netfd, ealgs, fromserversecret, 
359                                                 fromclientsecret, nil);
360                         break;
361                 case Enctls:
362                 default:
363                         fatal("Unsupported encryption protocol");
364                 }
365
366                 if(netfd < 0)
367                         fatal("can't establish ssl connection: %r");
368         }
369         else if (filterp) {
370                 if (ini)
371                         fatal("Protocol botch: don't know how to deal with this");
372                 netfd = filter(netfd, filterp);
373         }
374
375         if(ai)
376                 auth_freeAI(ai);
377
378         /*
379          * Start serving file requests from the network
380          */
381         for(;;) {
382                 r = getsbuf();
383                 if(r == 0)
384                         fatal("Out of service buffers");
385                         
386                 n = localread9pmsg(netfd, r->buf, messagesize, ini);
387                 if(n <= 0)
388                         fatal(nil);
389                 if(convM2S(r->buf, n, &r->work) == 0)
390                         fatal("convM2S format error");
391
392                 DEBUG(DFD, "%F\n", &r->work);
393                 (fcalls[r->work.type])(r);
394                 ini = nil;
395         }
396 }
397
398 /*
399  * WARNING: Replace this with the original version as soon as all 
400  * _old_ imports have been replaced with negotiating imports.  Also
401  * cpu relies on this (which needs to be fixed!) -- pb.
402  */
403 static int
404 localread9pmsg(int fd, void *abuf, uint n, void *ini)
405 {
406         int m, len;
407         uchar *buf;
408
409         buf = abuf;
410
411         /* read count */
412         if (ini)
413                 memcpy(buf, ini, BIT32SZ);
414         else {
415                 m = readn(fd, buf, BIT32SZ);
416                 if(m != BIT32SZ){
417                         if(m < 0)
418                                 return -1;
419                         return 0;
420                 }
421         }
422
423         len = GBIT32(buf);
424         if(len <= BIT32SZ || len > n){
425                 werrstr("bad length in 9P2000 message header");
426                 return -1;
427         }
428         len -= BIT32SZ;
429         m = readn(fd, buf+BIT32SZ, len);
430         if(m < len)
431                 return 0;
432         return BIT32SZ+m;
433 }
434 void
435 reply(Fcall *r, Fcall *t, char *err)
436 {
437         uchar *data;
438         int n;
439
440         t->tag = r->tag;
441         t->fid = r->fid;
442         if(err) {
443                 t->type = Rerror;
444                 t->ename = err;
445         }
446         else 
447                 t->type = r->type + 1;
448
449         DEBUG(DFD, "\t%F\n", t);
450
451         data = malloc(messagesize);     /* not mallocz; no need to clear */
452         if(data == nil)
453                 fatal(Enomem);
454         n = convS2M(t, data, messagesize);
455         if(write(netfd, data, n)!=n){
456                 syslog(0, "exportfs", "short write: %r");
457                 fatal("mount write");
458         }
459         free(data);
460 }
461
462 Fid *
463 getfid(int nr)
464 {
465         Fid *f;
466
467         for(f = fidhash(nr); f; f = f->next)
468                 if(f->nr == nr)
469                         return f;
470
471         return 0;
472 }
473
474 int
475 freefid(int nr)
476 {
477         Fid *f, **l;
478         char buf[128];
479
480         l = &fidhash(nr);
481         for(f = *l; f; f = f->next) {
482                 if(f->nr == nr) {
483                         if(f->mid) {
484                                 snprint(buf, sizeof(buf), "/mnt/exportfs/%d", f->mid);
485                                 unmount(0, buf);
486                                 psmap[f->mid] = 0;
487                         }
488                         if(f->f) {
489                                 freefile(f->f);
490                                 f->f = nil;
491                         }
492                         if(f->dir){
493                                 free(f->dir);
494                                 f->dir = nil;
495                         }
496                         *l = f->next;
497                         f->next = fidfree;
498                         fidfree = f;
499                         return 1;
500                 }
501                 l = &f->next;
502         }
503
504         return 0;       
505 }
506
507 Fid *
508 newfid(int nr)
509 {
510         Fid *new, **l;
511         int i;
512
513         l = &fidhash(nr);
514         for(new = *l; new; new = new->next)
515                 if(new->nr == nr)
516                         return 0;
517
518         if(fidfree == 0) {
519                 fidfree = emallocz(sizeof(Fid) * Fidchunk);
520
521                 for(i = 0; i < Fidchunk-1; i++)
522                         fidfree[i].next = &fidfree[i+1];
523
524                 fidfree[Fidchunk-1].next = 0;
525         }
526
527         new = fidfree;
528         fidfree = new->next;
529
530         memset(new, 0, sizeof(Fid));
531         new->next = *l;
532         *l = new;
533         new->nr = nr;
534         new->fid = -1;
535         new->mid = 0;
536
537         return new;     
538 }
539
540 Fsrpc *
541 getsbuf(void)
542 {
543         static int ap;
544         int look, rounds;
545         Fsrpc *wb;
546         int small_instead_of_fast = 1;
547
548         if(small_instead_of_fast)
549                 ap = 0; /* so we always start looking at the beginning and reuse buffers */
550
551         for(rounds = 0; rounds < 10; rounds++) {
552                 for(look = 0; look < Nr_workbufs; look++) {
553                         if(++ap == Nr_workbufs)
554                                 ap = 0;
555                         if(Workq[ap].busy == 0)
556                                 break;
557                 }
558
559                 if(look == Nr_workbufs){
560                         sleep(10 * rounds);
561                         continue;
562                 }
563
564                 wb = &Workq[ap];
565                 wb->pid = 0;
566                 wb->canint = 0;
567                 wb->flushtag = NOTAG;
568                 wb->busy = 1;
569                 if(wb->buf == nil)      /* allocate buffers dynamically to keep size down */
570                         wb->buf = emallocz(messagesize);
571                 return wb;
572         }
573         fatal("No more work buffers");
574         return nil;
575 }
576
577 void
578 freefile(File *f)
579 {
580         File *parent, *child;
581
582 Loop:
583         f->ref--;
584         if(f->ref > 0)
585                 return;
586         freecnt++;
587         if(f->ref < 0) abort();
588         DEBUG(DFD, "free %s\n", f->name);
589         /* delete from parent */
590         parent = f->parent;
591         if(parent->child == f)
592                 parent->child = f->childlist;
593         else{
594                 for(child=parent->child; child->childlist!=f; child=child->childlist)
595                         if(child->childlist == nil)
596                                 fatal("bad child list");
597                 child->childlist = f->childlist;
598         }
599         freeqid(f->qidt);
600         free(f->name);
601         f->name = nil;
602         free(f);
603         f = parent;
604         if(f != nil)
605                 goto Loop;
606 }
607
608 File *
609 file(File *parent, char *name)
610 {
611         Dir *dir;
612         char *path;
613         File *f;
614
615         DEBUG(DFD, "\tfile: 0x%p %s name %s\n", parent, parent->name, name);
616
617         path = makepath(parent, name);
618         if(patternfile != nil && excludefile(path)){
619                 free(path);
620                 return nil;
621         }
622         dir = dirstat(path);
623         free(path);
624         if(dir == nil)
625                 return nil;
626
627         for(f = parent->child; f; f = f->childlist)
628                 if(strcmp(name, f->name) == 0)
629                         break;
630
631         if(f == nil){
632                 f = emallocz(sizeof(File));
633                 f->name = estrdup(name);
634
635                 f->parent = parent;
636                 f->childlist = parent->child;
637                 parent->child = f;
638                 parent->ref++;
639                 f->ref = 0;
640                 filecnt++;
641         }
642         f->ref++;
643         f->qid.type = dir->qid.type;
644         f->qid.vers = dir->qid.vers;
645         f->qidt = uniqueqid(dir);
646         f->qid.path = f->qidt->uniqpath;
647
648         f->inval = 0;
649
650         free(dir);
651
652         return f;
653 }
654
655 void
656 initroot(void)
657 {
658         Dir *dir;
659
660         root = emallocz(sizeof(File));
661         root->name = estrdup(".");
662
663         dir = dirstat(root->name);
664         if(dir == nil)
665                 fatal("root stat");
666
667         root->ref = 1;
668         root->qid.vers = dir->qid.vers;
669         root->qidt = uniqueqid(dir);
670         root->qid.path = root->qidt->uniqpath;
671         root->qid.type = QTDIR;
672         free(dir);
673
674         psmpt = emallocz(sizeof(File));
675         psmpt->name = estrdup("/");
676
677         dir = dirstat(psmpt->name);
678         if(dir == nil)
679                 return;
680
681         psmpt->ref = 1;
682         psmpt->qid.vers = dir->qid.vers;
683         psmpt->qidt = uniqueqid(dir);
684         psmpt->qid.path = psmpt->qidt->uniqpath;
685         free(dir);
686
687         psmpt = file(psmpt, "mnt");
688         if(psmpt == 0)
689                 return;
690         psmpt = file(psmpt, "exportfs");
691 }
692
693 char*
694 makepath(File *p, char *name)
695 {
696         int i, n;
697         char *c, *s, *path, *seg[256];
698
699         seg[0] = name;
700         n = strlen(name)+2;
701         for(i = 1; i < 256 && p; i++, p = p->parent){
702                 seg[i] = p->name;
703                 n += strlen(p->name)+1;
704         }
705         path = malloc(n);
706         if(path == nil)
707                 fatal("out of memory");
708         s = path;
709
710         while(i--) {
711                 for(c = seg[i]; *c; c++)
712                         *s++ = *c;
713                 *s++ = '/';
714         }
715         while(s[-1] == '/')
716                 s--;
717         *s = '\0';
718
719         return path;
720 }
721
722 int
723 qidhash(vlong path)
724 {
725         int h, n;
726
727         h = 0;
728         for(n=0; n<64; n+=Nqidbits){
729                 h ^= path;
730                 path >>= Nqidbits;
731         }
732         return h & (Nqidtab-1);
733 }
734
735 void
736 freeqid(Qidtab *q)
737 {
738         ulong h;
739         Qidtab *l;
740
741         q->ref--;
742         if(q->ref > 0)
743                 return;
744         qfreecnt++;
745         h = qidhash(q->path);
746         if(qidtab[h] == q)
747                 qidtab[h] = q->next;
748         else{
749                 for(l=qidtab[h]; l->next!=q; l=l->next)
750                         if(l->next == nil)
751                                 fatal("bad qid list");
752                 l->next = q->next;
753         }
754         free(q);
755 }
756
757 Qidtab*
758 qidlookup(Dir *d)
759 {
760         ulong h;
761         Qidtab *q;
762
763         h = qidhash(d->qid.path);
764         for(q=qidtab[h]; q!=nil; q=q->next)
765                 if(q->type==d->type && q->dev==d->dev && q->path==d->qid.path)
766                         return q;
767         return nil;
768 }
769
770 int
771 qidexists(vlong path)
772 {
773         int h;
774         Qidtab *q;
775
776         for(h=0; h<Nqidtab; h++)
777                 for(q=qidtab[h]; q!=nil; q=q->next)
778                         if(q->uniqpath == path)
779                                 return 1;
780         return 0;
781 }
782
783 Qidtab*
784 uniqueqid(Dir *d)
785 {
786         ulong h;
787         vlong path;
788         Qidtab *q;
789
790         q = qidlookup(d);
791         if(q != nil){
792                 q->ref++;
793                 return q;
794         }
795         path = d->qid.path;
796         while(qidexists(path)){
797                 DEBUG(DFD, "collision on %s\n", d->name);
798                 /* collision: find a new one */
799                 ncollision++;
800                 path &= QIDPATH;
801                 ++newqid;
802                 if(newqid >= (1<<16)){
803                         DEBUG(DFD, "collision wraparound\n");
804                         newqid = 1;
805                 }
806                 path |= newqid<<48;
807                 DEBUG(DFD, "assign qid %.16llux\n", path);
808         }
809         q = mallocz(sizeof(Qidtab), 1);
810         if(q == nil)
811                 fatal("no memory for qid table");
812         qidcnt++;
813         q->ref = 1;
814         q->type = d->type;
815         q->dev = d->dev;
816         q->path = d->qid.path;
817         q->uniqpath = path;
818         h = qidhash(d->qid.path);
819         q->next = qidtab[h];
820         qidtab[h] = q;
821         return q;
822 }
823
824 void
825 fatal(char *s, ...)
826 {
827         char buf[ERRMAX];
828         va_list arg;
829         Proc *m;
830
831         if (s) {
832                 va_start(arg, s);
833                 vsnprint(buf, ERRMAX, s, arg);
834                 va_end(arg);
835         }
836
837         /* Clear away the slave children */
838         for(m = Proclist; m; m = m->next)
839                 postnote(PNPROC, m->pid, "kill");
840
841         DEBUG(DFD, "%s\n", buf);
842         if (s) 
843                 sysfatal("%s", buf);    /* caution: buf could contain '%' */
844         else
845                 exits(nil);
846 }
847
848 void*
849 emallocz(uint n)
850 {
851         void *p;
852
853         p = mallocz(n, 1);
854         if(p == nil)
855                 fatal(Enomem);
856         setmalloctag(p, getcallerpc(&n));
857         return p;
858 }
859
860 char*
861 estrdup(char *s)
862 {
863         char *t;
864
865         t = strdup(s);
866         if(t == nil)
867                 fatal(Enomem);
868         setmalloctag(t, getcallerpc(&s));
869         return t;
870 }
871
872 /* Network on fd1, mount driver on fd0 */
873 int
874 filter(int fd, char *cmd)
875 {
876         char buf[128], devdir[40], *s, *file, *argv[16];
877         int p[2], lfd, len, argc;
878
879         /* Get a free port and post it to the client. */
880         if (announce(anstring, devdir) < 0)
881                 fatal("filter: Cannot announce %s: %r", anstring);
882
883         snprint(buf, sizeof(buf), "%s/local", devdir);
884         if ((lfd = open(buf, OREAD)) < 0)
885                 fatal("filter: Cannot open %s: %r", buf);
886         if ((len = read(lfd, buf, sizeof buf - 1)) < 0)
887                 fatal("filter: Cannot read %s: %r", buf);
888         close(lfd);
889         buf[len] = '\0';
890         if ((s = strchr(buf, '\n')) != nil)
891                 len = s - buf;
892         if (write(fd, buf, len) != len) 
893                 fatal("filter: cannot write port; %r");
894
895         snprint(buf, sizeof(buf), "%s", cmd);
896         argc = tokenize(buf, argv, nelem(argv)-2);
897         if (argc == 0)
898                 fatal("filter: empty command");
899         argv[argc++] = devdir;
900         argv[argc] = nil;
901         file = argv[0];
902         if (s = strrchr(argv[0], '/'))
903                 argv[0] = s+1;
904
905         if(pipe(p) < 0)
906                 fatal("filter: pipe; %r");
907
908         switch(rfork(RFNOWAIT|RFPROC|RFMEM|RFFDG)) {
909         case -1:
910                 fatal("filter: rfork; %r\n");
911         case 0:
912                 if (dup(p[0], 1) < 0)
913                         fatal("filter: Cannot dup to 1; %r");
914                 if (dup(p[0], 0) < 0)
915                         fatal("filter: Cannot dup to 0; %r");
916                 close(p[0]);
917                 close(p[1]);
918                 exec(file, argv);
919                 fatal("filter: exec; %r");
920         default:
921                 close(fd);
922                 close(p[0]);
923         }
924         return p[1];    
925 }
926
927 static void
928 mksecret(char *t, uchar *f)
929 {
930         sprint(t, "%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
931                 f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9]);
932 }