/*
* 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<<j)) == 0){
fs->map[i] = x|(1<<j);
return i*8 + j;
}
}
return Notabno;
}
static Mdir*
validdir(Tfs *fs, uchar *p)
{
Mdir *md;
ulong x;
if(checksum(p) != 0)
return 0;
if(p[0] != Tagdir)
return 0;
md = (Mdir*)p;
x = GETS(md->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,
};