code: purgatorio

ref: 6042bb8adffe111049edfcb8b2eb1ad84814ed69
dir: /appl/lib/vac.b/

View raw version
implement Vac;

include "sys.m";
	sys: Sys;
	sprint: import sys;
include "venti.m";
	venti: Venti;
	Entrysize, Scoresize, Roottype, Dirtype, Pointertype0, Datatype: import venti;
	Root, Entry, Score, Session: import venti;
include "vac.m";


dflag = 0;

BIT8SZ:	con 1;
BIT16SZ:        con 2;
BIT32SZ:        con 4;
BIT48SZ:        con 6;
BIT64SZ:	con 8;

Rootnamelen:	con 128;
Rootversion:	con 2;
Direntrymagic:	con 16r1c4d9072;
Metablockmagic:	con 16r5656fc79;
Maxstringsize: con 1000;

blankroot: Root;
blankentry: Entry;
blankdirentry: Direntry;
blankmetablock: Metablock;
blankmetaentry: Metaentry;

init()
{
	sys = load Sys Sys->PATH;
	venti = load Venti Venti->PATH;
	venti->init();
}

Direntry.new(): ref Direntry
{
	return ref Direntry(9, "", 0, 0, 0, 0, big 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, big 0, big 0);
}

Direntry.mk(d: Sys->Dir): ref Direntry
{
	atime := 0; # d.atime;
	mode := d.mode&Modeperm;
	if(d.mode&sys->DMAPPEND)
		mode |= Modeappend;
	if(d.mode&sys->DMEXCL)
		mode |= Modeexcl;
	if(d.mode&sys->DMDIR)
		mode |= Modedir;
	if(d.mode&sys->DMTMP)
		mode |= Modetemp;
	return ref Direntry(9, d.name, 0, 0, 0, 0, d.qid.path, d.uid, d.gid, d.muid, d.mtime, 0, 0, atime, mode, d.mode, 0, big 0, big 0);
}

Direntry.mkdir(de: self ref Direntry): ref Sys->Dir
{
        d := ref sys->nulldir;
        d.name = de.elem;
        d.uid = de.uid;
        d.gid = de.gid;
        d.muid = de.mid;
        d.qid.path = de.qid;
        d.qid.vers = 0;
        d.qid.qtype = de.emode>>24;
        d.mode = de.emode;
        d.atime = de.atime;
        d.mtime = de.mtime;
        d.length = big 0;
        return d;
}

strlen(s: string): int
{
	return 2+len array of byte s;
}

Direntry.pack(de: self ref Direntry): array of byte
{
	if(de.version != 9) {
		sys->werrstr("only version 9 supported");
		return nil;
	}
		
	length := 4+2+strlen(de.elem)+4+4+4+4+8+strlen(de.uid)+strlen(de.gid)+strlen(de.mid)+4+4+4+4+4;
	if(de.qidspace)
		length += 1+2+8+8;

	d := array[length] of byte;
	i := 0;
	i = p32(d, i, Direntrymagic);
	i = p16(d, i, de.version);
	i = pstring(d, i, de.elem);
	i = p32(d, i, de.entry);
	if(de.version == 9) {
		i = p32(d, i, de.gen);
		i = p32(d, i, de.mentry);
		i = p32(d, i, de.mgen);
	}
	i = p64(d, i, de.qid);
	i = pstring(d, i, de.uid);
	i = pstring(d, i, de.gid);
	i = pstring(d, i, de.mid);
	i = p32(d, i, de.mtime);
	i = p32(d, i, de.mcount);
	i = p32(d, i, de.ctime);
	i = p32(d, i, de.atime);
	i = p32(d, i, de.mode);
	if(de.qidspace) {
		d[i++] = byte DirQidspace;
		i = p16(d, i, 16);
		i = p64(d, i, de.qidoff);
		i = p64(d, i, de.qidmax);
	}
	if(i != len d) {
		sys->werrstr(sprint("bad length for direntry (expected %d, have %d)", len d, i));
		return nil;
	}
	return d;
}

Direntry.unpack(d: array of byte): ref Direntry
{
	{
		de := ref blankdirentry;
		i := 0;
		magic: int;
		(magic, i) = eg32(d, i);
		if(magic != Direntrymagic) {
			sys->werrstr(sprint("bad magic (%x, want %x)", magic, Direntrymagic));
			return nil;
		}
		(de.version, i) = eg16(d, i);
		if(de.version != 8 && de.version != 9) {
			sys->werrstr(sprint("bad version (%d)", de.version));
			return nil;
		}
		(de.elem, i) = egstring(d, i);
		(de.entry, i) = eg32(d, i);
		case de.version {
		8 =>
			de.gen = 0;
			de.mentry = de.entry+1;
			de.mgen = 0;
		9 =>
			(de.gen, i) = eg32(d, i);
			(de.mentry, i) = eg32(d, i);
			(de.mgen, i) = eg32(d, i);
		}
		(de.qid, i) = eg64(d, i);
		(de.uid, i) = egstring(d, i);
		(de.gid, i) = egstring(d, i);
		(de.mid, i) = egstring(d, i);
		(de.mtime, i) = eg32(d, i);
		(de.mcount, i) = eg32(d, i);
		(de.ctime, i) = eg32(d, i);
		(de.atime, i) = eg32(d, i);
		(de.mode, i) = eg32(d, i);
		de.emode = de.mode&Modeperm;
		if(de.mode&Modeappend)
			de.emode |= sys->DMAPPEND;
		if(de.mode&Modeexcl)
			de.emode |= sys->DMEXCL;
		if(de.mode&Modedir)
			de.emode |= sys->DMDIR;
		if(de.mode&Modetemp)
			de.emode |= sys->DMTMP;
		while(i < len d) {
			t := int d[i++];
			n: int;
			(n, i) = eg16(d, i);
			case t {
			DirQidspace =>
				if(n != 16) {
					sys->werrstr(sprint("invalid qidspace length %d", n));
					return nil;
				}
				de.qidspace = 1;
				(de.qidoff, i) = eg64(d, i);
				(de.qidmax, i) = eg64(d, i);
			* =>
				# ignore other optional fields
				i += n;
			}
		}
		return de;
	} exception e {
	"too small:*" =>
		sys->werrstr("direntry "+e);
		return nil;
	* =>
		raise e;
	}
}


Metablock.new(): ref Metablock
{
	return ref Metablock(0, 0, 0, 0);
}

Metablock.pack(mb: self ref Metablock, d: array of byte)
{
	i := 0;
	i = p32(d, i, Metablockmagic);
	i = p16(d, i, mb.size);
	i = p16(d, i, mb.free);
	i = p16(d, i, mb.maxindex);
	i = p16(d, i, mb.nindex);
}

Metablock.unpack(d: array of byte): ref Metablock
{
	if(len d < Metablocksize) {
		sys->werrstr(sprint("bad length for metablock (%d, want %d)", len d, Metablocksize));
		return nil;
	}
	i := 0;
	magic := g32(d, i);
	if(magic != Metablockmagic && magic != Metablockmagic+1) {
		sys->werrstr(sprint("bad magic for metablock (%x, need %x)", magic, Metablockmagic));
		return nil;
	}
	i += BIT32SZ;

	mb := ref blankmetablock;
	mb.size = g16(d, i);
	i += BIT16SZ;
	mb.free = g16(d, i);
	i += BIT16SZ;
	mb.maxindex = g16(d, i);
	i += BIT16SZ;
	mb.nindex = g16(d, i);
	i += BIT16SZ;
	if(mb.nindex == 0) {
		sys->werrstr("bad metablock, nindex=0");
		return nil;
	}
	return mb;
}

Metaentry.pack(me: self ref Metaentry, d: array of byte)
{
	i := 0;
	i = p16(d, i, me.offset);
	i = p16(d, i, me.size);
}

Metaentry.unpack(d: array of byte, i: int): ref Metaentry
{
	o := Metablocksize+i*Metaentrysize;
	if(o+Metaentrysize > len d) {
		sys->werrstr(sprint("meta entry lies outside meta block, i=%d", i));
		return nil;
	}

	me := ref blankmetaentry;
	me.offset = g16(d, o);
	o += BIT16SZ;
	me.size = g16(d, o);
	o += BIT16SZ;
	if(me.offset+me.size > len d) {
		sys->werrstr(sprint("meta entry points outside meta block, i=%d", i));
		return nil;
	}
	return me;
}


Page.new(dsize: int): ref Page
{
	psize := (dsize/Scoresize)*Scoresize;
	return ref Page(array[psize] of byte, 0);
}

Page.add(p: self ref Page, s: Score)
{
	for(i := 0; i < Scoresize; i++)
		p.d[p.o+i] = s.a[i];
	p.o += Scoresize;
}

Page.full(p: self ref Page): int
{
	return p.o+Scoresize > len p.d;
}

Page.data(p: self ref Page): array of byte
{
	for(i := p.o; i >= Scoresize; i -= Scoresize)
		if(!Score(p.d[i-Scoresize:i]).eq(Score.zero()))
			break;
	return p.d[:i];
}


File.new(s: ref Session, dtype, dsize: int): ref File
{
	p := array[1] of ref Page;
	p[0] = Page.new(dsize);
	return ref File(p, dtype, dsize, big 0, s);
}

fflush(f: ref File, last: int): (int, ref Entry)
{
	for(i := 0; i < len f.p; i++) {
		if(!last && !f.p[i].full())
			return (0, nil);
		if(last && f.p[i].o == Scoresize) {
			flags := venti->Entryactive;
			if(f.dtype == Dirtype)
				flags |= venti->Entrydir;
			flags |= i<<venti->Entrydepthshift;
			score := Score(f.p[i].data());
			if(len score.a == 0)
				score = Score.zero();
			return (0, ref Entry(0, len f.p[i].d, f.dsize, i, flags, f.size, score));
		}
		(ok, score) := f.s.write(Pointertype0+i, f.p[i].data());
		if(ok < 0)
			return (-1, nil);
		f.p[i] = Page.new(f.dsize);
		if(i+1 == len f.p) {
			newp := array[len f.p+1] of ref Page;
			newp[:] = f.p;
			newp[len newp-1] = Page.new(f.dsize);
			f.p = newp;
		}
		f.p[i+1].add(score);
	}
	sys->werrstr("internal error in fflush");
	return (-1, nil);
}

File.write(f: self ref File, d: array of byte): int
{
	(fok, nil) := fflush(f, 0);
	if(fok < 0)
		return -1;
	length := len d;
	for(i := len d; i > 0; i--)
		if(d[i-1] != byte 0)
			break;
	d = d[:i];
	(ok, score) := f.s.write(f.dtype, d);
	if(ok < 0)
		return -1;
	f.size += big length;
	f.p[0].add(score);
	return 0;
}

File.finish(f: self ref File): ref Entry
{
	(ok, e) := fflush(f, 1);
	if(ok < 0)
		return nil;
	return e;
}


Sink.new(s: ref Venti->Session, dsize: int): ref Sink
{
	dirdsize := (dsize/Entrysize)*Entrysize;
	return ref Sink(File.new(s, Dirtype, dsize), array[dirdsize] of byte, 0, 0);
}

Sink.add(m: self ref Sink, e: ref Entry): int
{
	ed := e.pack();
	if(ed == nil)
		return -1;
	n := len m.d - m.nd;
	if(n > len ed)
		n = len ed;
	m.d[m.nd:] = ed[:n];
	m.nd += n;
	if(n < len ed) {
		if(m.f.write(m.d) < 0)
			return -1;
		m.nd = len ed - n;
		m.d[:] = ed[n:];
	}
	return m.ne++;
}

Sink.finish(m: self ref Sink): ref Entry
{
	if(m.nd > 0)
		if(m.f.write(m.d[:m.nd]) < 0)
			return nil;
	e := m.f.finish();
	e.dsize = len m.d;
	return e;
}


elemcmp(a, b: array of byte, fossil: int): int
{
	for(i := 0; i < len a && i < len b; i++)
		if(a[i] != b[i])
			return (int a[i] - int b[i]);
	if(fossil)
		return len a - len b;
	return len b - len a;
}

Mentry.cmp(a, b: ref Mentry): int
{
	return elemcmp(array of byte a.elem, array of byte b.elem, 0);
}

MSink.new(s: ref Venti->Session, dsize: int): ref MSink
{
	return ref MSink(File.new(s, Datatype, dsize), array[dsize] of byte, 0, nil);
}

l2a[T](l: list of T): array of T
{
	a := array[len l] of T;
	i := 0;
	for(; l != nil; l = tl l)
		a[i++] = hd l;
	return a;
}

insertsort[T](a: array of T)
	for { T =>	cmp:	fn(a, b: T): int; }
{
	for(i := 1; i < len a; i++) {
		tmp := a[i];
		for(j := i; j > 0 && T.cmp(a[j-1], tmp) > 0; j--)
			a[j] = a[j-1];
		a[j] = tmp;
	}
}

mflush(m: ref MSink, last: int): int
{
	d := array[len m.de] of byte;

	me := l2a(m.l);
	insertsort(me);
	o := Metablocksize;
	deo := o+len m.l*Metaentrysize;
	for(i := 0; i < len me; i++) {
		me[i].me.offset += deo;
		me[i].me.pack(d[o:]);
		o += Metaentrysize;
	}
	d[o:] = m.de[:m.nde];
	o += m.nde;
	if(!last)
		while(o < len d)
			d[o++] = byte 0;

	mb := Metablock.new();
	mb.nindex = len m.l;
	mb.maxindex = mb.nindex;
	mb.free = 0;
	mb.size = o;
	mb.pack(d);

	if(m.f.write(d[:o]) < 0)
		return -1;
	m.nde = 0;
	m.l = nil;
	return 0;
}

MSink.add(m: self ref MSink, de: ref Direntry): int
{
	d := de.pack();
	if(d == nil)
		return -1;
if(dflag) say(sprint("msink: adding direntry, length %d", len d));
	if(Metablocksize+len m.l*Metaentrysize+m.nde + Metaentrysize+len d > len m.de)
		if(mflush(m, 0) < 0)
			return -1;
	m.de[m.nde:] = d;
	m.l = ref Mentry(de.elem, ref Metaentry(m.nde, len d))::m.l;
	m.nde += len d;
	return 0;
}

MSink.finish(m: self ref MSink): ref Entry
{
	if(m.nde > 0)
		mflush(m, 1);
	return m.f.finish();
}

Source.new(s: ref Session, e: ref Entry): ref Source
{
	dsize := e.dsize;
	if(e.flags&venti->Entrydir)
		dsize = Entrysize*(dsize/Entrysize);
	return ref Source(s, e, dsize);
}

power(b, e: int): big
{
	r := big 1;
	while(e-- > 0)
		r *= big b;
	return r;
}

blocksize(e: ref Entry): int
{
	if(e.psize > e.dsize)
		return e.psize;
	return e.dsize;
}

Source.get(s: self ref Source, i: big, d: array of byte): int
{
	npages := (s.e.size+big (s.dsize-1))/big s.dsize;
	if(i*big s.dsize >= s.e.size)
		return 0;

	want := s.dsize;
	if(i == npages-big 1)
		want = int (s.e.size - i*big s.dsize);
	last := s.e.score;
	bsize := blocksize(s.e);
	buf: array of byte;

	npp := s.e.psize/Scoresize;	# scores per pointer block
	np := power(npp, s.e.depth-1);	# blocks referenced by score at this depth
	for(depth := s.e.depth; depth >= 0; depth--) {
		dtype := Pointertype0+depth-1;
		if(depth == 0) {
			dtype = Datatype;
			if(s.e.flags & venti->Entrydir)
				dtype = Dirtype;
			bsize = want;
		}
		buf = s.session.read(last, dtype, bsize);
		if(buf == nil)
			return -1;
		if(depth > 0) {
			pi := int (i / np);
			i %= np;
			np /= big npp;
			o := (pi+1)*Scoresize;
			if(o <= len buf)
				last = Score(buf[o-Scoresize:o]);
			else
				last = Score.zero();
		}
	}
	for(j := len buf; j < want; j++)
		d[j] = byte 0;
	d[:] = buf;
	return want;
}


Vacfile.mk(s: ref Source): ref Vacfile
{
	return ref Vacfile(s, big 0);
}

Vacfile.new(s: ref Session, e: ref Entry): ref Vacfile
{
	return Vacfile.mk(Source.new(s, e));
}

Vacfile.seek(v: self ref Vacfile, offset: big): big
{
	v.o += offset;
	if(v.o > v.s.e.size)
		v.o = v.s.e.size;
	return v.o;
}

Vacfile.read(v: self ref Vacfile, d: array of byte, n: int): int
{
	have := v.pread(d, n, v.o);
	if(have > 0)
		v.o += big have;
	return have;
}

Vacfile.pread(v: self ref Vacfile, d: array of byte, n: int, offset: big): int
{
	dsize := v.s.dsize;
if(dflag) say(sprint("vf.preadn, len d %d, n %d, offset %bd", len d, n, offset));
	have := v.s.get(big (offset/big dsize), buf := array[dsize] of byte);
	if(have <= 0)
		return have;
if(dflag) say(sprint("vacfile.pread: have=%d dsize=%d", have, dsize));
	o := int (offset % big dsize);
	have -= o;
	if(have > n)
		have = n;
	if(have <= 0)
		return 0;
	d[:] = buf[o:o+have];
	return have;
}


Vacdir.mk(vf: ref Vacfile, ms: ref Source): ref Vacdir
{
	return ref Vacdir(vf, ms, big 0, 0);
}

Vacdir.new(session: ref Session, e, me: ref Entry): ref Vacdir
{
        vf := Vacfile.new(session, e);
        ms := Source.new(session, me);
        return Vacdir.mk(vf, ms);

}

mecmp(d: array of byte, i: int, elem: string, fromfossil: int): (int, int)
{
	me := Metaentry.unpack(d, i);
	if(me == nil)
		return (0, 1);
	o := me.offset+6;
	n := g16(d, o);
	o += BIT16SZ;
	if(o+n > len d) {
		sys->werrstr("bad elem in direntry");
		return (0, 1);
	}
	return (elemcmp(d[o:o+n], array of byte elem, fromfossil), 0);
}

finddirentry(d: array of byte, elem: string): (int, ref Direntry)
{
	mb := Metablock.unpack(d);
	if(mb == nil)
		return (-1, nil);
	fromfossil := g32(d, 0) == Metablockmagic+1;

        left := 0;
        right := mb.nindex;
	while(left+1 != right) {
                mid := (left+right)/2;
		(c, err) := mecmp(d, mid, elem, fromfossil);
		if(err)
			return (-1, nil);
		if(c <= 0)
			left = mid;
		else
			right = mid;
		if(c == 0)
			break;
        }
	de := readdirentry(d, left, 0);
	if(de != nil && de.elem == elem)
		return (1, de);
	return (0, nil);
}

Vacdir.walk(v: self ref Vacdir, elem: string): ref Direntry
{
	i := big 0;
	for(;;) {
		n := v.ms.get(i, buf := array[v.ms.e.dsize] of byte);
		if(n < 0)
			return nil;
		if(n == 0)
			break;
		(ok, de) := finddirentry(buf[:n], elem);
		if(ok < 0)
			return nil;
		if(de != nil)
			return de;
		i++;
	}
	sys->werrstr(sprint("no such file or directory"));
	return nil;
}

vfreadentry(vf: ref Vacfile, entry: int): ref Entry
{
if(dflag) say(sprint("vfreadentry: reading entry=%d", entry));
	ebuf := array[Entrysize] of byte;
	n := vf.pread(ebuf, len ebuf, big entry*big Entrysize);
	if(n < 0)
		return nil;
	if(n != len ebuf) {
		sys->werrstr(sprint("bad archive, entry=%d not present (read %d, wanted %d)", entry, n, len ebuf));
		return nil;
	}
	e := Entry.unpack(ebuf);
	if(e == nil)
		return nil;
	if(~e.flags&venti->Entryactive) {
		sys->werrstr("entry not active");
		return nil;
	}
	# p9p writes archives with Entrylocal set?
	if(0 && e.flags&venti->Entrylocal) {
		sys->werrstr("entry is local");
		return nil;
	}
if(dflag) say(sprint("vreadentry: have entry, score=%s", e.score.text()));
	return e;
}

Vacdir.open(vd: self ref Vacdir, de: ref Direntry): (ref Entry, ref Entry)
{
if(dflag) say(sprint("vacdir.open: opening entry=%d", de.entry));
	e := vfreadentry(vd.vf, de.entry);
	if(e == nil)
		return (nil, nil);
	isdir1 := de.mode & Modedir;
	isdir2 := e.flags & venti->Entrydir;
	if(isdir1 && !isdir2 || !isdir1 && isdir2) {
		sys->werrstr("direntry directory bit does not match entry directory bit");
		return (nil, nil);
	}
if(dflag) say(sprint("vacdir.open: have entry, score=%s size=%bd", e.score.text(), e.size));
	me: ref Entry;
	if(de.mode&Modedir) {
		me = vfreadentry(vd.vf, de.mentry);
		if(me == nil)
			return (nil, nil);
if(dflag) say(sprint("vacdir.open: have mentry, score=%s size=%bd", me.score.text(), e.size));
	}
	return (e, me);
}

readdirentry(buf: array of byte, i: int, allowroot: int): ref Direntry
{
	me := Metaentry.unpack(buf, i);
	if(me == nil)
		return nil;
	o := me.offset;
	de := Direntry.unpack(buf[o:o+me.size]);
	if(de == nil)
		return nil;
	if(badelem(de.elem) && !(allowroot && de.elem == "/")) {
		sys->werrstr(sprint("bad direntry: %s", de.elem));
		return nil;
	}
	return de;
}
	
has(c: int, s: string): int
{
	for(i := 0; i < len s; i++)
		if(s[i] == c)
			return 1;
	return 0;
}

badelem(elem: string): int
{
	return elem == "" || elem == "." || elem == ".." || has('/', elem) || has(0, elem);
}

vdreaddir(vd: ref Vacdir, allowroot: int): (int, ref Direntry)
{
if(dflag) say(sprint("vdreaddir: ms.e.size=%bd vd.p=%bd vd.i=%d", vd.ms.e.size, vd.p, vd.i));
	dsize := vd.ms.dsize;
	n := vd.ms.get(vd.p, buf := array[dsize] of byte);
	if(n <= 0)
		return (n, nil);
if(dflag) say(sprint("vdreaddir: have buf, length=%d e.size=%bd", n, vd.ms.e.size));
	mb := Metablock.unpack(buf);
	if(mb == nil)
		return (-1, nil);
	de := readdirentry(buf, vd.i, allowroot);
	if(de == nil)
		return (-1, nil);
	vd.i++;
	if(vd.i >= mb.nindex) {
		vd.p++;
		vd.i = 0;
	}
if(dflag) say("vdreaddir: have entry");
	return (1, de);
}

Vacdir.readdir(vd: self ref Vacdir): (int, ref Direntry)
{
	return vdreaddir(vd, 0);
}


Vacdir.rewind(vd: self ref Vacdir)
{
	vd.p = big 0;
	vd.i = 0;
}


vdroot(session: ref Session, score: Venti->Score): (ref Vacdir, ref Direntry, string)
{
	d := session.read(score, venti->Roottype, venti->Rootsize);
	if(d == nil)
		return (nil, nil, sprint("reading vac score: %r"));
	r := Root.unpack(d);
	if(r == nil)
		return (nil, nil, sprint("bad vac root block: %r"));
	topscore := r.score;

	d = session.read(topscore, Dirtype, 3*Entrysize);
	if(d == nil)
		return (nil, nil, sprint("reading rootdir score: %r"));
	if(len d != 3*Entrysize) {
		if(len d % Entrysize != 0 && len d == 2*Entrysize != 0)	# what's in the second 40 bytes?  looks like 2nd 20 bytes of it is zero score
			return (nil, nil, sprint("bad fossil rootdir, have %d bytes, need %d or %d", len d, Entrysize, 2*Entrysize));
		e := Entry.unpack(d[:Entrysize]);
		if(e == nil)
			return (nil, nil, sprint("unpacking fossil top-level entry: %r"));
		topscore = e.score;
		d = session.read(topscore, Dirtype, 3*Entrysize);
		if(d == nil)
			return (nil, nil, sprint("reading fossil rootdir block: %r"));
	}

	e := array[3] of ref Entry;
	j := 0;
	for(i := 0; i+Entrysize <= len d; i += Entrysize) {
		e[j] = Entry.unpack(d[i:i+Entrysize]);
		if(e[j] == nil)
			return (nil, nil, sprint("reading root entry %d: %r", j));
		j++;
	}
if(dflag) say("top entries unpacked");

	mroot := Vacdir.mk(nil, Source.new(session, e[2]));
	(ok, de) := vdreaddir(mroot, 1);
	if(ok <= 0)
		return (nil, nil, sprint("reading root meta entry: %r"));

	return (Vacdir.new(session, e[0], e[1]), de, nil);
}


checksize(n: int): int
{
	if(n < 256 || n > Venti->Maxlumpsize) {
		sys->werrstr("bad block size");
		return 0;
	}
	return 1;
}


gstring(a: array of byte, o: int): (string, int)
{
	if(o < 0 || o+BIT16SZ > len a)
		return (nil, -1);
	l := (int a[o] << 8) | int a[o+1];
	if(l > Maxstringsize)
		return (nil, -1);
	o += BIT16SZ;
	e := o+l;
	if(e > len a)
		return (nil, -1);
	return (string a[o:e], e);
}

gtstring(a: array of byte, o: int, n: int): string
{
	e := o + n;
	if(e > len a)
		return nil;
	for(i := o; i < e; i++)
		if(a[i] == byte 0)
			break;
	return string a[o:i];
}

gscore(f: array of byte, i: int): Score
{
	s := Score(array[Scoresize] of byte);
	s.a[0:] = f[i:i+Scoresize];
	return s;
}

g16(f: array of byte, i: int): int
{
	return (int f[i] << 8) | int f[i+1];
}

g32(f: array of byte, i: int): int
{
	return (((((int f[i+0] << 8) | int f[i+1]) << 8) | int f[i+2]) << 8) | int f[i+3];
}

g48(f: array of byte, i: int): big
{
	return big g16(f, i)<<32 | (big g32(f, i+2) & 16rFFFFFFFF);
}

g64(f: array of byte, i: int): big
{
	return big g32(f, i)<<32 | (big g32(f, i+4) & 16rFFFFFFFF);
}

p16(d: array of byte, i: int, v: int): int
{
	d[i+0] = byte (v>>8);
	d[i+1] = byte v;
	return i+BIT16SZ;
}

p32(d: array of byte, i: int, v: int): int
{
	p16(d, i+0, v>>16);
	p16(d, i+2, v);
	return i+BIT32SZ;
}

p48(d: array of byte, i: int, v: big): int
{
	p16(d, i+0, int (v>>32));
	p32(d, i+2, int v);
	return i+BIT48SZ;
}

p64(d: array of byte, i: int, v: big): int
{
	p32(d, i+0, int (v>>32));
	p32(d, i+4, int v);
	return i+BIT64SZ;
}

pstring(a: array of byte, o: int, s: string): int
{
	sa := array of byte s;	# could do conversion ourselves
	n := len sa;
	a[o] = byte (n >> 8);
	a[o+1] = byte n;
	a[o+2:] = sa;
	return o+BIT16SZ+n;
}

ptstring(d: array of byte, i: int, s: string, l: int): int
{
	a := array of byte s;
	if(len a > l) {
		sys->werrstr("string too long: "+s);
		return -1;
	}
	for(j := 0; j < len a; j++)
		d[i+j] = a[j];
	while(j < l)
		d[i+j++] = byte 0;
	return i+l;
}

pscore(d: array of byte, i: int, s: Score): int
{
	for(j := 0; j < Scoresize; j++)
		d[i+j] = s.a[j];
	return i+Scoresize;
}

echeck(f: array of byte, i: int, l: int)
{
	if(i+l > len f)
		raise sprint("too small: buffer length is %d, requested %d bytes starting at offset %d", len f, l, i);
}

egscore(f: array of byte, i: int): (Score, int)
{
	echeck(f, i, Scoresize);
	return (gscore(f, i), i+Scoresize);
}

egstring(a: array of byte, o: int): (string, int)
{
	(s, no) := gstring(a, o);
	if(no == -1)
		raise sprint("too small: string runs outside buffer (length %d)", len a);
	return (s, no);
}

eg16(f: array of byte, i: int): (int, int)
{
	echeck(f, i, BIT16SZ);
	return (g16(f, i), i+BIT16SZ);
}

eg32(f: array of byte, i: int): (int, int)
{
	echeck(f, i, BIT32SZ);
	return (g32(f, i), i+BIT32SZ);
}

eg48(f: array of byte, i: int): (big, int)
{
	echeck(f, i, BIT48SZ);
	return (g48(f, i), i+BIT48SZ);
}

eg64(f: array of byte, i: int): (big, int)
{
	echeck(f, i, BIT64SZ);
	return (g64(f, i), i+BIT64SZ);
}

say(s: string)
{
	if(dflag)
		sys->fprint(sys->fildes(2), "%s\n", s);
}