/* * a pity the code isn't also tiny... */ #include "u.h" #include "../port/lib.h" #include "../port/error.h" #include "mem.h" #include "dat.h" #include "fns.h" enum{ Qdir, Qmedium, Maxfs= 10, /* max file systems */ Blen= 48, /* block length */ Nlen= 28, /* name length */ Dlen= Blen - 4, Tagdir= 'd', Tagdata= 'D', Tagend= 'e', Tagfree= 'f', Notapin= 0xffff, Notabno= 0xffff, Fcreating= 1, Frmonclose= 2 }; /* representation of a Tdir on medium */ typedef struct Mdir Mdir; struct Mdir { uchar type; uchar bno[2]; uchar pin[2]; char name[Nlen]; char pad[Blen - Nlen - 6]; uchar sum; }; /* representation of a Tdata/Tend on medium */ typedef struct Mdata Mdata; struct Mdata { uchar type; uchar bno[2]; uchar data[Dlen]; uchar sum; }; typedef struct Tfile Tfile; struct Tfile { int r; char name[NAMELEN]; ushort bno; ushort dbno; ushort pin; uchar flag; ulong length; /* hint to avoid egregious reading */ ushort fbno; ulong finger; }; typedef struct Tfs Tfs; struct Tfs { QLock ql; int r; Chan *c; uchar *map; int nblocks; Tfile *f; int nf; int fsize; }; struct { Tfs fs[Maxfs]; } tinyfs; #define GETS(x) ((x)[0]|((x)[1]<<8)) #define PUTS(x, v) {(x)[0] = (v);(x)[1] = ((v)>>8);} #define GETL(x) (GETS(x)|(GETS(x+2)<<16)) #define PUTL(x, v) {PUTS(x, v);PUTS(x+2, (v)>>16)}; static uchar checksum(uchar *p) { uchar *e; uchar s; s = 0; for(e = p + Blen; p < e; p++) s += *p; return s; } static void mapclr(Tfs *fs, ulong bno) { fs->map[bno>>3] &= ~(1<<(bno&7)); } static void mapset(Tfs *fs, ulong bno) { fs->map[bno>>3] |= 1<<(bno&7); } static int isalloced(Tfs *fs, ulong bno) { return fs->map[bno>>3] & (1<<(bno&7)); } static int mapalloc(Tfs *fs) { int i, j, lim; uchar x; lim = (fs->nblocks + 8 - 1)/8; for(i = 0; i < lim; i++){ x = fs->map[i]; if(x == 0xff) continue; for(j = 0; j < 8; j++) if((x & (1<map[i] = x|(1<bno); if(x >= fs->nblocks) return 0; return md; } static Mdata* validdata(Tfs *fs, uchar *p, int *lenp) { Mdata *md; ulong x; if(checksum(p) != 0) return 0; md = (Mdata*)p; switch(md->type){ case Tagdata: x = GETS(md->bno); if(x >= fs->nblocks) return 0; if(lenp) *lenp = Dlen; break; case Tagend: x = GETS(md->bno); if(x > Dlen) return 0; if(lenp) *lenp = x; break; default: return 0; } return md; } static Mdata* readdata(Tfs *fs, ulong bno, uchar *buf, int *lenp) { if(bno >= fs->nblocks) return 0; if(devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno) != Blen) error(Eio); return validdata(fs, buf, lenp); } static void writedata(Tfs *fs, ulong bno, ulong next, uchar *buf, int len, int last) { Mdata md; if(bno >= fs->nblocks) error(Eio); if(len > Dlen) len = Dlen; if(len < 0) error(Eio); memset(&md, 0, sizeof(md)); if(last){ md.type = Tagend; PUTS(md.bno, len); } else { md.type = Tagdata; PUTS(md.bno, next); } memmove(md.data, buf, len); md.sum = 0 - checksum((uchar*)&md); if(devtab[fs->c->type]->write(fs->c, &md, Blen, Blen*bno) != Blen) error(Eio); } static void writedir(Tfs *fs, Tfile *f) { Mdir *md; uchar buf[Blen]; if(f->bno == Notabno) return; md = (Mdir*)buf; memset(buf, 0, Blen); md->type = Tagdir; strncpy(md->name, f->name, sizeof(md->name)-1); PUTS(md->bno, f->dbno); PUTS(md->pin, f->pin); md->sum = 0 - checksum(buf); if(devtab[fs->c->type]->write(fs->c, buf, Blen, Blen*f->bno) != Blen) error(Eio); } static void freeblocks(Tfs *fs, ulong bno, ulong bend) { uchar buf[Blen]; Mdata *md; if(waserror()) return; while(bno != bend && bno != Notabno){ mapclr(fs, bno); if(devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno) != Blen) break; md = validdata(fs, buf, 0); if(md == 0) break; if(md->type == Tagend) break; bno = GETS(md->bno); } poperror(); } static void freefile(Tfs *fs, Tfile *f, ulong bend) { uchar buf[Blen]; /* remove blocks from map */ freeblocks(fs, f->dbno, bend); /* change file type to free on medium */ if(f->bno != Notabno){ memset(buf, 0x55, Blen); devtab[fs->c->type]->write(fs->c, buf, Blen, Blen*f->bno); mapclr(fs, f->bno); } /* forget we ever knew about it */ memset(f, 0, sizeof(*f)); } static void expand(Tfs *fs) { Tfile *f; fs->fsize += 8; f = malloc(fs->fsize*sizeof(*f)); if(fs->f){ memmove(f, fs->f, fs->nf*sizeof(*f)); free(fs->f); } fs->f = f; } static Tfile* newfile(Tfs *fs, char *name) { int i; volatile struct { Tfile *f; Tfs *fs; } rock; /* find free entry in file table */ rock.f = 0; rock.fs = fs; for(;;) { for(i = 0; i < rock.fs->fsize; i++){ rock.f = &rock.fs->f[i]; if(rock.f->name[0] == 0){ strncpy(rock.f->name, name, sizeof(rock.f->name)-1); break; } } if(i < rock.fs->fsize){ if(i >= rock.fs->nf) rock.fs->nf = i+1; break; } expand(rock.fs); } rock.f->flag = Fcreating; rock.f->dbno = Notabno; rock.f->bno = mapalloc(rock.fs); rock.f->fbno = Notabno; rock.f->r = 1; rock.f->pin = Notapin; // what is a pin?? /* write directory block */ if(waserror()){ freefile(rock.fs, rock.f, Notabno); nexterror(); } if(rock.f->bno == Notabno) error("out of space"); writedir(rock.fs, rock.f); poperror(); return rock.f; } /* * Read the whole medium and build a file table and used * block bitmap. Inconsistent files are purged. The medium * had better be small or this could take a while. */ static void tfsinit(Tfs *fs) { char dbuf[DIRLEN]; Dir d; uchar buf[Blen]; ulong x, bno; int n, done; Tfile *f; Mdir *mdir; Mdata *mdata; devtab[fs->c->type]->stat(fs->c, dbuf); convM2D(dbuf, &d); fs->nblocks = d.length/Blen; if(fs->nblocks < 3) error("tinyfs medium too small"); /* bitmap for block usage */ x = (fs->nblocks + 8 - 1)/8; fs->map = malloc(x); memset(fs->map, 0x0, x); for(bno = fs->nblocks; bno < x*8; bno++) mapset(fs, bno); /* find files */ for(bno = 0; bno < fs->nblocks; bno++){ n = devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno); if(n != Blen) break; mdir = validdir(fs, buf); if(mdir == 0) continue; if(fs->nf >= fs->fsize) expand(fs); f = &fs->f[fs->nf++]; x = GETS(mdir->bno); mapset(fs, bno); strncpy(f->name, mdir->name, sizeof(f->name)); f->pin = GETS(mdir->pin); f->bno = bno; f->dbno = x; f->fbno = Notabno; } /* follow files */ for(f = fs->f; f < &(fs->f[fs->nf]); f++){ bno = f->dbno; for(done = 0; !done;) { if(isalloced(fs, bno)){ freefile(fs, f, bno); break; } n = devtab[fs->c->type]->read(fs->c, buf, Blen, Blen*bno); if(n != Blen){ freefile(fs, f, bno); break; } mdata = validdata(fs, buf, 0); if(mdata == 0){ freefile(fs, f, bno); break; } mapset(fs, bno); switch(mdata->type){ case Tagdata: bno = GETS(mdata->bno); f->length += Dlen; break; case Tagend: f->length += GETS(mdata->bno); done = 1; break; } if(done) f->flag &= ~Fcreating; } } } /* * single directory */ static int tinyfsgen(Chan *c, Dirtab *tab, int ntab, int i, Dir *dp) { Tfs *fs; Tfile *f; Qid qid; USED(ntab); USED(tab); qid.vers = 0; fs = &tinyfs.fs[c->dev]; if(i >= fs->nf) return -1; if(i == DEVDOTDOT){ qid.path = CHDIR; devdir(c, qid, ".", 0, eve, 0555, dp); return 1; } f = &fs->f[i]; if(f->name[0] == 0) return 0; qid.path = i; devdir(c, qid, f->name, f->length, eve, 0775, dp); return 1; } static void tinyfsinit(void) { if(Nlen > NAMELEN) panic("tinyfsinit"); } /* * specifier is an open file descriptor */ static Chan* tinyfsattach(char *spec) { Tfs *fs; Chan *c; volatile struct { Chan *cc; } rock; int i; char buf[NAMELEN*2]; snprint(buf, sizeof(buf), "/dev/%s", spec); rock.cc = namec(buf, Aopen, ORDWR, 0); if(waserror()){ cclose(rock.cc); nexterror(); } fs = 0; for(i = 0; i < Maxfs; i++){ fs = &tinyfs.fs[i]; qlock(&fs->ql); if(fs->r && eqchan(rock.cc, fs->c, 1)) break; qunlock(&fs->ql); } if(i < Maxfs){ fs->r++; qunlock(&fs->ql); cclose(rock.cc); } else { for(fs = tinyfs.fs; fs < &tinyfs.fs[Maxfs]; fs++){ qlock(&fs->ql); if(fs->r == 0) break; qunlock(&fs->ql); } if(fs == &tinyfs.fs[Maxfs]) error("too many tinyfs's"); fs->c = rock.cc; fs->r = 1; fs->f = 0; fs->nf = 0; fs->fsize = 0; tfsinit(fs); qunlock(&fs->ql); } poperror(); c = devattach('F', spec); c->dev = fs - tinyfs.fs; c->qid.path = CHDIR; c->qid.vers = 0; return c; } static Chan* tinyfsclone(Chan *c, Chan *nc) { Tfs *fs; fs = &tinyfs.fs[c->dev]; qlock(&fs->ql); fs->r++; qunlock(&fs->ql); return devclone(c, nc); } static int tinyfswalk(Chan *c, char *name) { int n; Tfs *fs; fs = &tinyfs.fs[c->dev]; qlock(&fs->ql); n = devwalk(c, name, 0, 0, tinyfsgen); if(n != 0 && c->qid.path != CHDIR){ fs = &tinyfs.fs[c->dev]; fs->f[c->qid.path].r++; } qunlock(&fs->ql); return n; } static void tinyfsstat(Chan *c, char *db) { devstat(c, db, 0, 0, tinyfsgen); } static Chan* tinyfsopen(Chan *c, int omode) { Tfile *f; volatile struct { Tfs *fs; } rock; rock.fs = &tinyfs.fs[c->dev]; if(c->qid.path & CHDIR){ if(omode != OREAD) error(Eperm); } else { qlock(&rock.fs->ql); if(waserror()){ qunlock(&rock.fs->ql); nexterror(); } switch(omode){ case OTRUNC|ORDWR: case OTRUNC|OWRITE: f = newfile(rock.fs, rock.fs->f[c->qid.path].name); rock.fs->f[c->qid.path].r--; c->qid.path = f - rock.fs->f; break; case OREAD: case OEXEC: break; default: error(Eperm); } qunlock(&rock.fs->ql); poperror(); } return devopen(c, omode, 0, 0, tinyfsgen); } static void tinyfscreate(Chan *c, char *name, int omode, ulong perm) { volatile struct { Tfs *fs; } rock; Tfile *f; USED(perm); rock.fs = &tinyfs.fs[c->dev]; qlock(&rock.fs->ql); if(waserror()){ qunlock(&rock.fs->ql); nexterror(); } f = newfile(rock.fs, name); qunlock(&rock.fs->ql); poperror(); c->qid.path = f - rock.fs->f; c->qid.vers = 0; c->mode = openmode(omode); } static void tinyfsremove(Chan *c) { Tfs *fs; Tfile *f; if(c->qid.path == CHDIR) error(Eperm); fs = &tinyfs.fs[c->dev]; f = &fs->f[c->qid.path]; qlock(&fs->ql); freefile(fs, f, Notabno); qunlock(&fs->ql); } static void tinyfsclose(Chan *c) { volatile struct { Tfs *fs; } rock; Tfile *f, *nf; int i; rock.fs = &tinyfs.fs[c->dev]; qlock(&rock.fs->ql); /* dereference file and remove old versions */ if(!waserror()){ if(c->qid.path != CHDIR){ f = &rock.fs->f[c->qid.path]; f->r--; if(f->r == 0){ if(f->flag & Frmonclose) freefile(rock.fs, f, Notabno); else if(f->flag & Fcreating){ /* remove all other files with this name */ for(i = 0; i < rock.fs->fsize; i++){ nf = &rock.fs->f[i]; if(f == nf) continue; if(strcmp(nf->name, f->name) == 0){ if(nf->r) nf->flag |= Frmonclose; else freefile(rock.fs, nf, Notabno); } } f->flag &= ~Fcreating; } } } poperror(); } /* dereference rock.fs and remove on zero refs */ rock.fs->r--; if(rock.fs->r == 0){ if(rock.fs->f) free(rock.fs->f); rock.fs->f = 0; rock.fs->nf = 0; rock.fs->fsize = 0; if(rock.fs->map) free(rock.fs->map); rock.fs->map = 0; cclose(rock.fs->c); rock.fs->c = 0; } qunlock(&rock.fs->ql); } static long tinyfsread(Chan *c, void *a, long n, vlong offset) { volatile struct { Tfs *fs; } rock; Tfile *f; int sofar, i, off; ulong bno; Mdata *md; uchar buf[Blen]; uchar *p; if(c->qid.path & CHDIR) return devdirread(c, a, n, 0, 0, tinyfsgen); p = a; rock.fs = &tinyfs.fs[c->dev]; f = &rock.fs->f[c->qid.path]; if(offset >= f->length) return 0; qlock(&rock.fs->ql); if(waserror()){ qunlock(&rock.fs->ql); nexterror(); } if(n + offset >= f->length) n = f->length - offset; /* walk to starting data block */ if(0 && f->finger <= offset && f->fbno != Notabno){ sofar = f->finger; bno = f->fbno; } else { sofar = 0; bno = f->dbno; } for(; sofar + Dlen <= offset; sofar += Dlen){ md = readdata(rock.fs, bno, buf, 0); if(md == 0) error(Eio); bno = GETS(md->bno); } /* read data */ off = offset%Dlen; offset -= off; for(sofar = 0; sofar < n; sofar += i){ md = readdata(rock.fs, bno, buf, &i); if(md == 0) error(Eio); /* update finger for successful read */ f->finger = offset; f->fbno = bno; offset += Dlen; i -= off; if(i > n - sofar) i = n - sofar; memmove(p, md->data+off, i); p += i; bno = GETS(md->bno); off = 0; } qunlock(&rock.fs->ql); poperror(); return sofar; } /* * if we get a write error in this routine, blocks will * be lost. They should be recovered next fsinit. */ static long tinyfswrite(Chan *c, void *a, long n, vlong offset) { Tfile *f; int last, next, i, finger, off, used; ulong bno, fbno; Mdata *md; uchar buf[Blen]; uchar *p; volatile struct { Tfs *fs; ulong dbno; } rock; if(c->qid.path & CHDIR) error(Eperm); if(n == 0) return 0; p = a; rock.fs = &tinyfs.fs[c->dev]; f = &rock.fs->f[c->qid.path]; qlock(&rock.fs->ql); rock.dbno = Notabno; if(waserror()){ freeblocks(rock.fs, rock.dbno, Notabno); qunlock(&rock.fs->ql); nexterror(); } /* files are append only, anything else is illegal */ if(offset != f->length) error("append only"); /* write blocks backwards */ p += n; last = offset + n; fbno = Notabno; finger = 0; off = offset; /* so we have something signed to compare against */ for(next = ((last-1)/Dlen)*Dlen; next >= off; next -= Dlen){ bno = mapalloc(rock.fs); if(bno == Notabno) error("out of space"); i = last - next; p -= i; if(last == n+offset){ writedata(rock.fs, bno, rock.dbno, p, i, 1); finger = next; /* remember for later */ fbno = bno; } else { writedata(rock.fs, bno, rock.dbno, p, i, 0); } rock.dbno = bno; last = next; } /* walk to last data block */ md = (Mdata*)buf; if(0 && f->finger < offset && f->fbno != Notabno){ next = f->finger; bno = f->fbno; } else { next = 0; bno = f->dbno; } used = 0; while(bno != Notabno){ md = readdata(rock.fs, bno, buf, &used); if(md == 0) error(Eio); if(md->type == Tagend){ if(next + Dlen < offset) panic("devtinyfs1"); break; } next += Dlen; if(next > offset) panic("devtinyfs1"); bno = GETS(md->bno); } /* point to new blocks */ if(offset == 0){ /* first block in a file */ f->dbno = rock.dbno; writedir(rock.fs, f); } else { /* updating a current block */ i = last - offset; if(i > 0){ p -= i; memmove(md->data + used, p, i); used += i; } writedata(rock.fs, bno, rock.dbno, md->data, used, last == n+offset); } f->length += n; /* update finger */ if(fbno != Notabno){ f->finger = finger; f->fbno = fbno; } poperror(); qunlock(&rock.fs->ql); return n; } Dev tinyfsdevtab = { 'F', "tinyfs", devreset, tinyfsinit, tinyfsattach, tinyfsclone, tinyfswalk, tinyfsstat, tinyfsopen, tinyfscreate, tinyfsclose, tinyfsread, devbread, tinyfswrite, devbwrite, tinyfsremove, devwstat, };