1 /* 2 * a pity the code isn't also tiny... 3 */ 4 #include "u.h" 5 #include "../port/lib.h" 6 #include "../port/error.h" 7 #include "mem.h" 8 #include "dat.h" 9 #include "fns.h" 10 11 enum{ 12 Qdir, 13 Qmedium, 14 15 Maxfs= 10, /* max file systems */ 16 17 Blen= 48, /* block length */ 18 Nlen= 28, /* name length */ 19 Dlen= Blen - 4, 20 21 Tagdir= 'd', 22 Tagdata= 'D', 23 Tagend= 'e', 24 Tagfree= 'f', 25 26 Notapin= 0xffff, 27 Notabno= 0xffff, 28 29 Fcreating= 1, 30 Frmonclose= 2 31 }; 32 33 /* representation of a Tdir on medium */ 34 typedef struct Mdir Mdir; 35 struct Mdir { 36 uchar type; 37 uchar bno[2]; 38 uchar pin[2]; 39 char name[Nlen]; 40 char pad[Blen - Nlen - 6]; 41 uchar sum; 42 }; 43 44 /* representation of a Tdata/Tend on medium */ 45 typedef struct Mdata Mdata; 46 struct Mdata { 47 uchar type; 48 uchar bno[2]; 49 uchar data[Dlen]; 50 uchar sum; 51 }; 52 53 typedef struct Tfile Tfile; 54 struct Tfile { 55 int r; 56 char name[Nlen]; 57 ushort bno; 58 ushort dbno; 59 ushort pin; 60 uchar flag; 61 ulong length; 62 63 /* hint to avoid egregious reading */ 64 ushort fbno; 65 ulong finger; 66 }; 67 68 typedef struct Tfs Tfs; 69 struct Tfs { 70 QLock ql; 71 int r; 72 Chan *c; 73 uchar *map; 74 int nblocks; 75 Tfile *f; 76 int nf; 77 int fsize; 78 }; 79 80 static struct { 81 Tfs fs[Maxfs]; 82 } tinyfs; 83 84 #define GETS(x) ((x)[0]|((x)[1]<<8)) 85 #define PUTS(x, v) {(x)[0] = (v);(x)[1] = ((v)>>8);} 86 87 #define GETL(x) (GETS(x)|(GETS(x+2)<<16)) 88 #define PUTL(x, v) {PUTS(x, v);PUTS(x+2, (v)>>16)}; 89 90 static uchar 91 checksum(uchar *p) 92 { 93 uchar *e; 94 uchar s; 95 96 s = 0; 97 for(e = p + Blen; p < e; p++) 98 s += *p; 99 return s; 100 } 101 102 static void 103 mapclr(Tfs *fs, ulong bno) 104 { 105 fs->map[bno>>3] &= ~(1<<(bno&7)); 106 } 107 108 static void 109 mapset(Tfs *fs, ulong bno) 110 { 111 fs->map[bno>>3] |= 1<<(bno&7); 112 } 113 114 static int 115 isalloced(Tfs *fs, ulong bno) 116 { 117 return fs->map[bno>>3] & (1<<(bno&7)); 118 } 119 120 static int 121 mapalloc(Tfs *fs) 122 { 123 int i, j, lim; 124 uchar x; 125 126 lim = (fs->nblocks + 8 - 1)/8; 127 for(i = 0; i < lim; i++){ 128 x = fs->map[i]; 129 if(x == 0xff) 130 continue; 131 for(j = 0; j < 8; j++) 132 if((x & (1<<j)) == 0){ 133 fs->map[i] = x|(1<<j); 134 return i*8 + j; 135 } 136 } 137 138 return Notabno; 139 } 140 141 static Mdir* 142 validdir(Tfs *fs, uchar *p) 143 { 144 Mdir *md; 145 ulong x; 146 147 if(checksum(p) != 0) 148 return 0; 149 if(p[0] != Tagdir) 150 return 0; 151 md = (Mdir*)p; 152 x = GETS(md->bno); 153 if(x >= fs->nblocks) 154 return 0; 155 return md; 156 } 157 158 static Mdata* 159 validdata(Tfs *fs, uchar *p, int *lenp) 160 { 161 Mdata *md; 162 ulong x; 163 164 if(checksum(p) != 0) 165 return 0; 166 md = (Mdata*)p; 167 switch(md->type){ 168 case Tagdata: 169 x = GETS(md->bno); 170 if(x >= fs->nblocks) 171 return 0; 172 if(lenp) 173 *lenp = Dlen; 174 break; 175 case Tagend: 176 x = GETS(md->bno); 177 if(x > Dlen) 178 return 0; 179 if(lenp) 180 *lenp = x; 181 break; 182 default: 183 return 0; 184 } 185 return md; 186 } 187 188 static Mdata* 189 readdata(Tfs *fs, ulong bno, uchar *buf, int *lenp) 190 { 191 if(bno >= fs->nblocks) 192 return 0; 193 if(devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno) != Blen) 194 error(Eio); 195 return validdata(fs, buf, lenp); 196 } 197 198 static void 199 writedata(Tfs *fs, ulong bno, ulong next, uchar *buf, int len, int last) 200 { 201 Mdata md; 202 203 if(bno >= fs->nblocks) 204 error(Eio); 205 if(len > Dlen) 206 len = Dlen; 207 if(len < 0) 208 error(Eio); 209 memset(&md, 0, sizeof(md)); 210 if(last){ 211 md.type = Tagend; 212 PUTS(md.bno, len); 213 } else { 214 md.type = Tagdata; 215 PUTS(md.bno, next); 216 } 217 memmove(md.data, buf, len); 218 md.sum = 0 - checksum((uchar*)&md); 219 220 if(devtab[fs->c->type]->write(fs->c, &md, Blen, Blen*bno) != Blen) 221 error(Eio); 222 } 223 224 static void 225 writedir(Tfs *fs, Tfile *f) 226 { 227 Mdir *md; 228 uchar buf[Blen]; 229 230 if(f->bno == Notabno) 231 return; 232 233 md = (Mdir*)buf; 234 memset(buf, 0, Blen); 235 md->type = Tagdir; 236 strncpy(md->name, f->name, sizeof(md->name)-1); 237 PUTS(md->bno, f->dbno); 238 PUTS(md->pin, f->pin); 239 md->sum = 0 - checksum(buf); 240 241 if(devtab[fs->c->type]->write(fs->c, buf, Blen, Blen*f->bno) != Blen) 242 error(Eio); 243 } 244 245 static void 246 freeblocks(Tfs *fs, ulong bno, ulong bend) 247 { 248 uchar buf[Blen]; 249 Mdata *md; 250 251 if(waserror()) 252 return; 253 254 while(bno != bend && bno != Notabno){ 255 mapclr(fs, bno); 256 if(devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno) != Blen) 257 break; 258 md = validdata(fs, buf, 0); 259 if(md == 0) 260 break; 261 if(md->type == Tagend) 262 break; 263 bno = GETS(md->bno); 264 } 265 266 poperror(); 267 } 268 269 static void 270 freefile(Tfs *fs, Tfile *f, ulong bend) 271 { 272 uchar buf[Blen]; 273 274 /* remove blocks from map */ 275 freeblocks(fs, f->dbno, bend); 276 277 /* change file type to free on medium */ 278 if(f->bno != Notabno){ 279 memset(buf, 0x55, Blen); 280 devtab[fs->c->type]->write(fs->c, buf, Blen, Blen*f->bno); 281 mapclr(fs, f->bno); 282 } 283 284 /* forget we ever knew about it */ 285 memset(f, 0, sizeof(*f)); 286 } 287 288 static void 289 expand(Tfs *fs) 290 { 291 Tfile *f; 292 293 fs->fsize += 8; 294 f = malloc(fs->fsize*sizeof(*f)); 295 if(f == nil) 296 error(Enomem); 297 298 if(fs->f){ 299 memmove(f, fs->f, fs->nf*sizeof(*f)); 300 free(fs->f); 301 } 302 fs->f = f; 303 } 304 305 static Tfile* 306 newfile(Tfs *fs, char *name) 307 { 308 int i; 309 volatile struct { 310 Tfile *f; 311 Tfs *fs; 312 } rock; 313 314 /* find free entry in file table */ 315 rock.f = 0; 316 rock.fs = fs; 317 for(;;) { 318 for(i = 0; i < rock.fs->fsize; i++){ 319 rock.f = &rock.fs->f[i]; 320 if(rock.f->name[0] == 0){ 321 strncpy(rock.f->name, name, sizeof(rock.f->name)-1); 322 break; 323 } 324 } 325 326 if(i < rock.fs->fsize){ 327 if(i >= rock.fs->nf) 328 rock.fs->nf = i+1; 329 break; 330 } 331 332 expand(rock.fs); 333 } 334 335 rock.f->flag = Fcreating; 336 rock.f->dbno = Notabno; 337 rock.f->bno = mapalloc(rock.fs); 338 rock.f->fbno = Notabno; 339 rock.f->r = 1; 340 rock.f->pin = up->env->pgrp->pin; 341 342 /* write directory block */ 343 if(waserror()){ 344 freefile(rock.fs, rock.f, Notabno); 345 nexterror(); 346 } 347 if(rock.f->bno == Notabno) 348 error("out of space"); 349 writedir(rock.fs, rock.f); 350 poperror(); 351 352 return rock.f; 353 } 354 355 /* 356 * Read the whole medium and build a file table and used 357 * block bitmap. Inconsistent files are purged. The medium 358 * had better be small or this could take a while. 359 */ 360 static void 361 tfsinit(Tfs *fs) 362 { 363 uchar dbuf[STATFIXLEN+4*KNAMELEN]; 364 Dir d; 365 uchar buf[Blen]; 366 ulong x, bno; 367 int n, done; 368 Tfile *f; 369 Mdir *mdir; 370 Mdata *mdata; 371 372 n = devtab[fs->c->type]->stat(fs->c, dbuf, sizeof(dbuf)); 373 if(n == 0) 374 error(Eshortstat); 375 n = convM2D(dbuf, n, &d, nil); 376 if(n == 0) 377 error(Eshortstat); 378 fs->nblocks = d.length/Blen; 379 if(fs->nblocks < 3) 380 error("tinyfs medium too small"); 381 382 /* bitmap for block usage */ 383 x = (fs->nblocks + 8 - 1)/8; 384 fs->map = malloc(x); 385 if(fs->map == nil) 386 error(Enomem); 387 memset(fs->map, 0x0, x); 388 for(bno = fs->nblocks; bno < x*8; bno++) 389 mapset(fs, bno); 390 391 /* find files */ 392 for(bno = 0; bno < fs->nblocks; bno++){ 393 n = devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno); 394 if(n != Blen) 395 break; 396 397 mdir = validdir(fs, buf); 398 if(mdir == 0) 399 continue; 400 401 if(fs->nf >= fs->fsize) 402 expand(fs); 403 404 f = &fs->f[fs->nf++]; 405 406 x = GETS(mdir->bno); 407 mapset(fs, bno); 408 strncpy(f->name, mdir->name, sizeof(f->name)); 409 f->pin = GETS(mdir->pin); 410 f->bno = bno; 411 f->dbno = x; 412 f->fbno = Notabno; 413 } 414 415 /* follow files */ 416 for(f = fs->f; f < &(fs->f[fs->nf]); f++){ 417 bno = f->dbno; 418 for(done = 0; !done;) { 419 if(isalloced(fs, bno)){ 420 freefile(fs, f, bno); 421 break; 422 } 423 n = devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno); 424 if(n != Blen){ 425 freefile(fs, f, bno); 426 break; 427 } 428 mdata = validdata(fs, buf, 0); 429 if(mdata == 0){ 430 freefile(fs, f, bno); 431 break; 432 } 433 mapset(fs, bno); 434 switch(mdata->type){ 435 case Tagdata: 436 bno = GETS(mdata->bno); 437 f->length += Dlen; 438 break; 439 case Tagend: 440 f->length += GETS(mdata->bno); 441 done = 1; 442 break; 443 } 444 if(done) 445 f->flag &= ~Fcreating; 446 } 447 } 448 } 449 450 /* 451 * single directory 452 */ 453 static int 454 tinyfsgen(Chan *c, char*, Dirtab *tab, int ntab, int i, Dir *dp) 455 { 456 Tfs *fs; 457 Tfile *f; 458 Qid qid; 459 460 USED(ntab); 461 USED(tab); 462 463 fs = &tinyfs.fs[c->dev]; 464 if(i >= fs->nf) 465 return -1; 466 if(i == DEVDOTDOT){ 467 mkqid(&qid, Qdir, 0, QTDIR); 468 devdir(c, qid, ".", 0, eve, 0555, dp); 469 return 1; 470 } 471 f = &fs->f[i]; 472 if(f->name[0] == 0) 473 return 0; 474 mkqid(&qid, i, 0, QTFILE); 475 devdir(c, qid, f->name, f->length, eve, 0664, dp); 476 return 1; 477 } 478 479 static void 480 tinyfsinit(void) 481 { 482 if(Nlen > KNAMELEN) 483 panic("tinyfsinit"); 484 } 485 486 /* 487 * specifier is the name of a device in /dev 488 */ 489 static Chan* 490 tinyfsattach(char *spec) 491 { 492 Tfs *fs; 493 Chan *c; 494 volatile struct { Chan *cc; } rock; 495 int i; 496 char buf[KNAMELEN*3]; 497 498 if(*spec == 0) 499 error("bad specifier"); 500 501 snprint(buf, sizeof(buf), "/dev/%s", spec); 502 rock.cc = namec(buf, Aopen, ORDWR, 0); 503 if(waserror()){ 504 cclose(rock.cc); 505 nexterror(); 506 } 507 508 fs = 0; 509 for(i = 0; i < Maxfs; i++){ 510 fs = &tinyfs.fs[i]; 511 qlock(&fs->ql); 512 if(fs->r && eqchan(rock.cc, fs->c, 1)) 513 break; 514 qunlock(&fs->ql); 515 } 516 if(i < Maxfs){ 517 fs->r++; 518 qunlock(&fs->ql); 519 cclose(rock.cc); 520 } else { 521 for(fs = tinyfs.fs; fs < &tinyfs.fs[Maxfs]; fs++){ 522 qlock(&fs->ql); 523 if(fs->r == 0) 524 break; 525 qunlock(&fs->ql); 526 } 527 if(fs == &tinyfs.fs[Maxfs]) 528 error("too many tinyfs's"); 529 fs->c = rock.cc; 530 fs->r = 1; 531 fs->f = 0; 532 fs->nf = 0; 533 fs->fsize = 0; 534 tfsinit(fs); 535 qunlock(&fs->ql); 536 } 537 poperror(); 538 539 c = devattach('F', spec); 540 c->dev = fs - tinyfs.fs; 541 c->qid.path = Qdir; 542 c->qid.type = QTDIR; 543 c->qid.vers = 0; 544 545 return c; 546 } 547 548 static Walkqid* 549 tinyfswalk(Chan *c, Chan *nc, char **name, int nname) 550 { 551 Tfs *fs; 552 Walkqid *w; 553 554 fs = &tinyfs.fs[c->dev]; 555 556 qlock(&fs->ql); 557 if(waserror()){ 558 qunlock(&fs->ql); 559 nexterror(); 560 } 561 w = devwalk(c, nc, name, nname, 0, 0, tinyfsgen); 562 if(w != nil && w->clone!=nil && w->clone->qid.type != QTDIR){ 563 fs = &tinyfs.fs[w->clone->dev]; 564 fs->r++; 565 fs->f[(ulong)w->clone->qid.path].r++; 566 } 567 poperror(); 568 qunlock(&fs->ql); 569 return w; 570 } 571 572 static int 573 tinyfsstat(Chan *c, uchar *db, int n) 574 { 575 return devstat(c, db, n, 0, 0, tinyfsgen); 576 } 577 578 static Chan* 579 tinyfsopen(Chan *c, int omode) 580 { 581 Tfile *f; 582 volatile struct { Tfs *fs; } rock; 583 584 rock.fs = &tinyfs.fs[c->dev]; 585 586 if(c->qid.type & QTDIR){ 587 if(omode != OREAD) 588 error(Eperm); 589 } else { 590 qlock(&rock.fs->ql); 591 if(waserror()){ 592 qunlock(&rock.fs->ql); 593 nexterror(); 594 } 595 switch(omode){ 596 case OTRUNC|ORDWR: 597 case OTRUNC|OWRITE: 598 f = newfile(rock.fs, rock.fs->f[c->qid.path].name); 599 rock.fs->f[c->qid.path].r--; 600 c->qid.path = f - rock.fs->f; 601 break; 602 case OREAD: 603 break; 604 default: 605 error(Eperm); 606 } 607 qunlock(&rock.fs->ql); 608 poperror(); 609 } 610 611 return devopen(c, omode, 0, 0, tinyfsgen); 612 } 613 614 static void 615 tinyfscreate(Chan *c, char *name, int omode, ulong perm) 616 { 617 volatile struct { Tfs *fs; } rock; 618 Tfile *f; 619 620 USED(perm); 621 622 rock.fs = &tinyfs.fs[c->dev]; 623 624 qlock(&rock.fs->ql); 625 if(waserror()){ 626 qunlock(&rock.fs->ql); 627 nexterror(); 628 } 629 f = newfile(rock.fs, name); 630 qunlock(&rock.fs->ql); 631 poperror(); 632 633 c->qid.path = f - rock.fs->f; 634 c->qid.vers = 0; 635 c->mode = openmode(omode); 636 } 637 638 static void 639 tinyfsremove(Chan *c) 640 { 641 Tfs *fs; 642 Tfile *f; 643 644 if(c->qid.type & QTDIR) 645 error(Eperm); 646 fs = &tinyfs.fs[c->dev]; 647 f = &fs->f[c->qid.path]; 648 qlock(&fs->ql); 649 freefile(fs, f, Notabno); 650 qunlock(&fs->ql); 651 } 652 653 static void 654 tinyfsclose(Chan *c) 655 { 656 volatile struct { Tfs *fs; } rock; 657 Tfile *f, *nf; 658 int i; 659 660 rock.fs = &tinyfs.fs[c->dev]; 661 662 qlock(&rock.fs->ql); 663 664 /* dereference file and remove old versions */ 665 if(!waserror()){ 666 if(c->qid.path != Qdir){ 667 f = &rock.fs->f[c->qid.path]; 668 f->r--; 669 if(f->r == 0){ 670 if(f->flag & Frmonclose) 671 freefile(rock.fs, f, Notabno); 672 else if(f->flag & Fcreating){ 673 /* remove all other files with this name */ 674 for(i = 0; i < rock.fs->fsize; i++){ 675 nf = &rock.fs->f[i]; 676 if(f == nf) 677 continue; 678 if(strcmp(nf->name, f->name) == 0){ 679 if(nf->r) 680 nf->flag |= Frmonclose; 681 else 682 freefile(rock.fs, nf, Notabno); 683 } 684 } 685 f->flag &= ~Fcreating; 686 } 687 } 688 } 689 poperror(); 690 } 691 692 /* dereference rock.fs and remove on zero refs */ 693 rock.fs->r--; 694 if(rock.fs->r == 0){ 695 if(rock.fs->f) 696 free(rock.fs->f); 697 rock.fs->f = 0; 698 rock.fs->nf = 0; 699 rock.fs->fsize = 0; 700 if(rock.fs->map) 701 free(rock.fs->map); 702 rock.fs->map = 0; 703 cclose(rock.fs->c); 704 rock.fs->c = 0; 705 } 706 qunlock(&rock.fs->ql); 707 } 708 709 static long 710 tinyfsread(Chan *c, void *a, long n, vlong offset) 711 { 712 volatile struct { Tfs *fs; } rock; 713 Tfile *f; 714 int sofar, i, off; 715 ulong bno; 716 Mdata *md; 717 uchar buf[Blen]; 718 uchar *p; 719 720 if(c->qid.type & QTDIR) 721 return devdirread(c, a, n, 0, 0, tinyfsgen); 722 723 p = a; 724 rock.fs = &tinyfs.fs[c->dev]; 725 f = &rock.fs->f[c->qid.path]; 726 if(offset >= f->length) 727 return 0; 728 729 qlock(&rock.fs->ql); 730 if(waserror()){ 731 qunlock(&rock.fs->ql); 732 nexterror(); 733 } 734 if(n + offset >= f->length) 735 n = f->length - offset; 736 737 /* walk to starting data block */ 738 if(0 && f->finger <= offset && f->fbno != Notabno){ 739 sofar = f->finger; 740 bno = f->fbno; 741 } else { 742 sofar = 0; 743 bno = f->dbno; 744 } 745 for(; sofar + Dlen <= offset; sofar += Dlen){ 746 md = readdata(rock.fs, bno, buf, 0); 747 if(md == 0) 748 error(Eio); 749 bno = GETS(md->bno); 750 } 751 752 /* read data */ 753 off = offset%Dlen; 754 offset -= off; 755 for(sofar = 0; sofar < n; sofar += i){ 756 md = readdata(rock.fs, bno, buf, &i); 757 if(md == 0) 758 error(Eio); 759 760 /* update finger for successful read */ 761 f->finger = offset; 762 f->fbno = bno; 763 offset += Dlen; 764 765 i -= off; 766 if(i > n - sofar) 767 i = n - sofar; 768 memmove(p, md->data+off, i); 769 p += i; 770 bno = GETS(md->bno); 771 off = 0; 772 } 773 qunlock(&rock.fs->ql); 774 poperror(); 775 776 return sofar; 777 } 778 779 /* 780 * if we get a write error in this routine, blocks will 781 * be lost. They should be recovered next fsinit. 782 */ 783 static long 784 tinyfswrite(Chan *c, void *a, long n, vlong offset) 785 { 786 Tfile *f; 787 int last, next, i, finger, off, used; 788 ulong bno, fbno; 789 Mdata *md; 790 uchar buf[Blen]; 791 uchar *p; 792 volatile struct { 793 Tfs *fs; 794 ulong dbno; 795 } rock; 796 797 if(c->qid.type & QTDIR) 798 error(Eperm); 799 800 if(n == 0) 801 return 0; 802 803 p = a; 804 rock.fs = &tinyfs.fs[c->dev]; 805 f = &rock.fs->f[c->qid.path]; 806 807 qlock(&rock.fs->ql); 808 rock.dbno = Notabno; 809 if(waserror()){ 810 freeblocks(rock.fs, rock.dbno, Notabno); 811 qunlock(&rock.fs->ql); 812 nexterror(); 813 } 814 815 /* files are append only, anything else is illegal */ 816 if(offset != f->length) 817 error("append only"); 818 819 /* write blocks backwards */ 820 p += n; 821 last = offset + n; 822 fbno = Notabno; 823 finger = 0; 824 off = offset; /* so we have something signed to compare against */ 825 for(next = ((last-1)/Dlen)*Dlen; next >= off; next -= Dlen){ 826 bno = mapalloc(rock.fs); 827 if(bno == Notabno) 828 error("out of space"); 829 i = last - next; 830 p -= i; 831 if(last == n+offset){ 832 writedata(rock.fs, bno, rock.dbno, p, i, 1); 833 finger = next; /* remember for later */ 834 fbno = bno; 835 } else { 836 writedata(rock.fs, bno, rock.dbno, p, i, 0); 837 } 838 rock.dbno = bno; 839 last = next; 840 } 841 842 /* walk to last data block */ 843 md = (Mdata*)buf; 844 if(0 && f->finger < offset && f->fbno != Notabno){ 845 next = f->finger; 846 bno = f->fbno; 847 } else { 848 next = 0; 849 bno = f->dbno; 850 } 851 852 used = 0; 853 while(bno != Notabno){ 854 md = readdata(rock.fs, bno, buf, &used); 855 if(md == 0) 856 error(Eio); 857 if(md->type == Tagend){ 858 if(next + Dlen < offset) 859 panic("devtinyfs1"); 860 break; 861 } 862 next += Dlen; 863 if(next > offset) 864 panic("devtinyfs1"); 865 bno = GETS(md->bno); 866 } 867 868 /* point to new blocks */ 869 if(offset == 0){ 870 /* first block in a file */ 871 f->dbno = rock.dbno; 872 writedir(rock.fs, f); 873 } else { 874 /* updating a current block */ 875 i = last - offset; 876 if(i > 0){ 877 p -= i; 878 memmove(md->data + used, p, i); 879 used += i; 880 } 881 writedata(rock.fs, bno, rock.dbno, md->data, used, last == n+offset); 882 } 883 f->length += n; 884 885 /* update finger */ 886 if(fbno != Notabno){ 887 f->finger = finger; 888 f->fbno = fbno; 889 } 890 poperror(); 891 qunlock(&rock.fs->ql); 892 893 return n; 894 } 895 896 Dev tinyfsdevtab = { 897 'F', 898 "tinyfs", 899 900 devreset, 901 tinyfsinit, 902 devshutdown, 903 tinyfsattach, 904 tinyfswalk, 905 tinyfsstat, 906 tinyfsopen, 907 tinyfscreate, 908 tinyfsclose, 909 tinyfsread, 910 devbread, 911 tinyfswrite, 912 devbwrite, 913 tinyfsremove, 914 devwstat, 915 }; 916