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