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