ref: d0ab3a0dd8f6355b3603d0fb04043a9ae867639b
dir: /appl/lib/vac.b/
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);
}