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