ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/lib/zip.b/
implement Zip;
include "sys.m";
sys: Sys;
sprint: import sys;
include "draw.m";
include "arg.m";
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "daytime.m";
dt: Daytime;
include "encoding.m";
base16: Encoding;
include "filter.m";
inflate: Filter;
include "convcs.m";
convcs: Convcs;
cp437: Btos;
include "zip.m";
comprmethods := array[] of {
"uncompressed",
"shrunk",
"reduce, factor 1",
"reduce, factor 2",
"reduce, factor 3",
"reduce, factor 4",
"implode",
"tokenize",
"deflate",
"deflate64",
"pkware implode",
nil,
"bzip2",
nil,
"lzma",
Mibmterse =>
"ibm terse (new)",
Mlz77z =>
"ibm lz77 z",
Mwavpack =>
"wavpack",
Mppmdi1 =>
"PPMd version I, rev 1",
};
Crc32poly: con int 16redb88320; # reversed standard crc-32
crc32tab: array of int;
init(): string
{
sys = load Sys Sys->PATH;
bufio = load Bufio Bufio->PATH;
base16 = load Encoding Encoding->BASE16PATH;
dt = load Daytime Daytime->PATH;
inflate = load Filter Filter->INFLATEPATH;
inflate->init();
convcs = load Convcs Convcs->PATH;
err := convcs->init(nil);
if(err == nil)
(cp437, err) = convcs->getbtos("cp437");
if(err != nil)
err = "convcs: "+err;
crc32tab = mkcrctab(Crc32poly);
return err;
}
sanitizepath(s: string): string
{
endslash := s != nil && s[len s-1] == '/';
r: list of string;
for(l := sys->tokenize(s, "/").t1; l != nil; l = tl l)
case hd l {
"." => ;
".." => if(r != nil) r = tl r;
* => r = hd l::r;
}
rs := "";
for(r = rev(r); r != nil; r = tl r)
rs += "/"+hd r;
if(rs != nil)
rs = rs[1:];
if(endslash)
rs[len rs] = '/';
return rs;
}
Extra.parse(d: array of byte): (ref Extra, string)
{
e := ref Extra;
{
o := 0;
while(o < len d) {
id: int;
buf: array of byte;
(id, o) = g16(d, o);
(buf, o) = gstr(d, o);
e.l = ref (id, buf)::e.l;
}
} exception {
"get:*" =>
return (nil, sprint("bad extra"));
}
e.l = rev(e.l);
return (e, nil);
}
Extra.pack(e: self ref Extra): array of byte
{
n := 0;
if(e != nil)
l := e.l;
for(t := l; t != nil; t = tl t)
n += 2+2+len (hd t).t1;
buf := array[n] of byte;
o := 0;
for(; l != nil; l = tl l) {
(id, dat) := *hd l;
o = p16(buf, o, id);
o = p16(buf, o, len dat);
o = pbuf(buf, o, dat);
}
return buf;
}
Extra.text(e: self ref Extra): string
{
s: string;
for(l := e.l; l != nil; l = tl l)
s += sprint(", 0x%04ux=%s", (hd l).t0, base16->enc((hd l).t1));
if(s != nil)
s = s[2:];
return "Extra("+s+")";
}
Fhdr.mk(x: ref CDFhdr): ref Fhdr
{
return ref Fhdr (
x.versneeded,
x.flags,
x.comprmethod,
x.filemtime,
x.filemdate,
x.mtime,
x.crc32,
x.comprsize,
x.uncomprsize,
x.filename,
ref *x.extra,
big 0);
}
fhdrsig := array[] of {byte 'P', byte 'K', byte 3, byte 4};
Fhdr.parse(buf: array of byte, off: big): (ref Fhdr, string)
{
if(len buf < 4 || bufcmp(buf[:4], fhdrsig) != 0)
return (nil, sprint("not a local file header"));
o := 4;
f := ref Fhdr;
{
(f.versneeded, o) = g16(buf, o);
(f.flags, o) = g16(buf, o);
(f.comprmethod, o) = g16(buf, o);
(f.filemtime, o) = g16(buf, o);
(f.filemdate, o) = g16(buf, o);
f.mtime = mtimedos2unix(f.filemtime, f.filemdate);
(f.crc32, o) = g32(buf, o);
(f.comprsize, o) = g32(buf, o);
(f.uncomprsize, o) = g32(buf, o);
flen, extralen: int;
(flen, o) = g16(buf, o);
(extralen, o) = g16(buf, o);
(f.filename, o) = gbufstr(f.flags&Futf8, flen, buf, o);
f.filename = sanitizepath(f.filename);
extra: array of byte;
(extra, o) = gbuf(extralen, buf, o);
err: string;
(f.extra, err) = Extra.parse(extra);
if(err != nil)
return (nil, "bad extra for local file header: "+err);
f.dataoff = off+big o;
} exception {
"get:*" =>
return (nil, sprint("short buffer for local file header (o %d, len %d)", o, len buf));
}
return (f, nil);
}
Fhdr.read(fd: ref Sys->FD, off: big): (ref Fhdr, string)
{
minwidth: con 4+5*2+3*4+2*2;
n := preadn(fd, buf0 := array[minwidth] of byte, len buf0, off);
if(n < 0)
return (nil, sprint("read: %r"));
if(n != len buf0)
return (nil, "short read");
flen := g16(buf0, len buf0-4).t0;
extralen := g16(buf0, len buf0-2).t0;
buf1 := array[flen+extralen] of byte;
if(len buf1 > 0) {
n = preadn(fd, buf1, len buf1, off+big minwidth);
if(n < 0)
return (nil, sprint("read: %r"));
if(n != len buf1)
return (nil, "short read");
}
buf := array[len buf0+len buf1] of byte;
buf[:] = buf0;
buf[len buf0:] = buf1;
return Fhdr.parse(buf, off);
}
Fhdr.pack(f: self ref Fhdr): array of byte
{
filename := array of byte f.filename;
ebuf := f.extra.pack();
buf := array[4+5*2+3*4+2+len filename+2+len ebuf] of byte;
(f.filemtime, f.filemdate) = mtimeunix2dos(f.mtime);
o := 0;
o = pbuf(buf, o, fhdrsig);
o = p16(buf, o, f.versneeded);
o = p16(buf, o, f.flags);
o = p16(buf, o, f.comprmethod);
o = p16(buf, o, f.filemtime);
o = p16(buf, o, f.filemdate);
o = p32(buf, o, f.crc32);
o = p32(buf, o, f.comprsize);
o = p32(buf, o, f.uncomprsize);
o = p16(buf, o, len filename);
o = p16(buf, o, len ebuf);
o = pbuf(buf, o, filename);
o = pbuf(buf, o, ebuf);
return buf;
}
Fhdr.text(f: self ref Fhdr): string
{
return sprint("Fhdr(versneeded %d (%s), flags %ux, comprmethod %d/%s, mtime %d, crc32 %bd, comprsize %bd, uncomprsize %bd, filename %q, %s, dataoff %bd)",
f.versneeded, versstr(f.versneeded),
f.flags,
f.comprmethod, comprmethod(f.comprmethod),
f.mtime,
f.crc32,
f.comprsize, f.uncomprsize,
f.filename,
f.extra.text(),
f.dataoff);
}
mask(n: int): int
{
return (1<<n)-1;
}
mtimeunix2dos(m: int): (int, int)
{
tm := dt->local(m);
s := tm.sec | tm.min<<5 | tm.hour<<11;
d := tm.mday | (tm.mon+1)<<5 | (tm.year-80)<<9;
return (s, d);
}
zerotm: ref dt->Tm;
mtimedos2unix(s, d: int): int
{
if(zerotm == nil)
zerotm = dt->local(dt->now());
tm := ref *zerotm;
tm.sec = (s>>0) & mask(5);
tm.min = (s>>5) & mask(6);
tm.hour = (s>>11) & mask(5);
tm.mday = (d>>0) & mask(5);
tm.mon = ((d>>5) & mask(4))-1;
tm.year = ((d>>9) & mask(7))+80;
tm.wday = 0;
tm.yday = 0;
return dt->tm2epoch(tm);
}
CDFhdr.mk(f: ref Fhdr, off: big): ref CDFhdr
{
return ref CDFhdr (
f.versneeded, # we're not claiming to be unix, because then unzip sets 0 for permissions when absent
f.versneeded,
f.flags,
f.comprmethod,
f.filemtime,
f.filemdate,
f.mtime,
f.crc32,
f.comprsize,
f.uncomprsize,
f.filename,
f.extra,
nil,
0,
0,
big 0,
off);
}
cdirfhdrsig := array[] of {byte 'P', byte 'K', byte 1, byte 2};
CDFhdr.parse(buf: array of byte): (ref CDFhdr, string)
{
if(len buf < 4 || bufcmp(buf[:4], cdirfhdrsig) != 0)
return (nil, "not a central directory file header");
f := ref CDFhdr;
o := 4;
{
(f.versmadeby, o) = g16(buf, o);
(f.versneeded, o) = g16(buf, o);
(f.flags, o) = g16(buf, o);
if(f.flags & Fcdirencrypted)
return (nil, "central directory is encrypted, not supported");
(f.comprmethod, o) = g16(buf, o);
(f.filemtime, o) = g16(buf, o);
(f.filemdate, o) = g16(buf, o);
f.mtime = mtimedos2unix(f.filemtime, f.filemdate);
(f.crc32, o) = g32(buf, o);
(f.comprsize, o) = g32(buf, o);
(f.uncomprsize, o) = g32(buf, o);
flen, extralen, commentlen: int;
(flen, o) = g16(buf, o);
(extralen, o) = g16(buf, o);
(commentlen, o) = g16(buf, o);
(f.disknrstart, o) = g16(buf, o);
(f.intattr, o) = g16(buf, o);
(f.extattr, o) = g32(buf, o);
(f.reloffset, o) = g32(buf, o);
(f.filename, o) = gbufstr(f.flags&Futf8, flen, buf, o);
f.filename = sanitizepath(f.filename);
extra: array of byte;
(extra, o) = gbuf(extralen, buf, o);
err: string;
(f.extra, err) = Extra.parse(extra);
if(err != nil)
return (nil, sprint("bad extra for central directory file header"));
(f.comment, o) = gbufstr(f.flags&Futf8, commentlen, buf, o);
if(o != len buf)
say(sprint("%d trailing bytes after parsing central directory file header", len buf-o));
} exception {
"get:*" =>
return (nil, sprint("short buffer for central directory file header (o %d, len %d)", o, len buf));
}
return (f, nil);
}
CDFhdr.read(b: ref Bufio->Iobuf): (ref CDFhdr, string)
{
buf0 := array[4+6*2+3*4+5*2+2*4] of byte;
if(breadn(b, buf0, len buf0) != len buf0)
return (nil, sprint("short read on central directory file header"));
if(bufcmp(buf0[:4], cdirfhdrsig) != 0)
return (nil, sprint("not signature of central directory file header"));
lenoff := 4+6*2+3*4;
n := g16(buf0, lenoff).t0;
n += g16(buf0, lenoff+2).t0;
n += g16(buf0, lenoff+4).t0;
buf1 := array[n] of byte;
if(breadn(b, buf1, len buf1) != len buf1)
return (nil, sprint("short read on filename/extra/comment section of central directory file header"));
buf := array[len buf0+len buf1] of byte;
buf[:] = buf0;
buf[len buf0:] = buf1;
return CDFhdr.parse(buf);
}
CDFhdr.pack(f: self ref CDFhdr): array of byte
{
filename := array of byte f.filename;
comment := array of byte f.comment;
ebuf := f.extra.pack();
buf := array[4+6*2+3*4+2+len filename+2+len ebuf+2+len comment+2*2+2*4] of byte;
(f.filemtime, f.filemdate) = mtimeunix2dos(f.mtime);
o := 0;
o = pbuf(buf, o, cdirfhdrsig);
o = p16(buf, o, f.versmadeby);
o = p16(buf, o, f.versneeded);
o = p16(buf, o, f.flags);
o = p16(buf, o, f.comprmethod);
o = p16(buf, o, f.filemtime);
o = p16(buf, o, f.filemdate);
o = p32(buf, o, f.crc32);
o = p32(buf, o, f.comprsize);
o = p32(buf, o, f.uncomprsize);
o = p16(buf, o, len filename);
o = p16(buf, o, len ebuf);
o = p16(buf, o, len comment);
o = p16(buf, o, f.disknrstart);
o = p16(buf, o, f.intattr);
o = p32(buf, o, f.extattr);
o = p32(buf, o, f.reloffset);
o = pbuf(buf, o, filename);
o = pbuf(buf, o, ebuf);
o = pbuf(buf, o, comment);
return buf;
}
CDFhdr.text(f: self ref CDFhdr): string
{
return sprint("CDFhdr(version: madeby %d (%s), needed %d (%s); flags %02ux, comprmethod %d/%s, mtime %d, crc32 %bux, comprsize %bd, uncomprsize %bd, file %q, %s, comment %q, disknrstart %d, intattr %02x, extattr %04bux, reloffset %bd)",
f.versmadeby, versstr(f.versmadeby),
f.versneeded, versstr(f.versneeded),
f.flags, f.comprmethod, comprmethod(f.comprmethod),
f.mtime, f.crc32, f.comprsize, f.uncomprsize,
f.filename,
f.extra.text(),
f.comment,
f.disknrstart,
f.intattr, f.extattr, f.reloffset);
}
eocentraldirsig := array[] of {byte 'P', byte 'K', byte 5, byte 6};
Endofcdir.parse(buf: array of byte): (ref Endofcdir, string)
{
e := ref Endofcdir;
if(len buf < 4 || bufcmp(buf[:4], eocentraldirsig) != 0)
return (nil, "not end of central directory");
o := 4;
{
(e.disknr, o) = g16(buf, o);
(e.diskcdir, o) = g16(buf, o);
(e.diskcdirentries, o) = g16(buf, o);
(e.cdirentries, o) = g16(buf, o);
(e.cdirsize, o) = g32(buf, o);
(e.cdiroffset, o) = g32(buf, o);
(e.comment, o) = gstr(buf, o);
if(o != len buf)
say(sprint("%d trailing bytes after end of central directory", len buf-o));
} exception {
"get:*" =>
return (nil, sprint("short buffer for end of central directory buffer, (o %d, len %d)", o, len buf));
}
return (e, nil);
}
Endofcdir.pack(e: self ref Endofcdir): array of byte
{
buf := array[4+4*2+2*4+2+len e.comment] of byte;
o := 0;
o = pbuf(buf, o, eocentraldirsig);
o = p16(buf, o, e.disknr);
o = p16(buf, o, e.diskcdir);
o = p16(buf, o, e.diskcdirentries);
o = p16(buf, o, e.cdirentries);
o = p32(buf, o, e.cdirsize);
o = p32(buf, o, e.cdiroffset);
o = p16(buf, o, len e.comment);
o = pbuf(buf, o, e.comment);
return buf;
}
Endofcdir.text(e: self ref Endofcdir): string
{
return sprint("Endofcdir(disk: nr %d, cdir %d, cdirentries %d; cdir: entries %d, size %bd, offset %bd; comment %q)",
e.disknr, e.diskcdir, e.diskcdirentries, e.cdirentries, e.cdirsize, e.cdiroffset, string e.comment);
}
comprmethod(m: int): string
{
if(m < 0 || m >= len comprmethods || comprmethods[m] == nil)
return "unknown";
return comprmethods[m];
}
open(fd: ref Sys->FD): (ref Endofcdir, array of ref CDFhdr, string)
{
{
return open0(fd);
} exception e {
"open0:*" =>
return (nil, nil, e[len "open0:":]);
}
}
error(s: string)
{
raise "open0:"+s;
}
open0(fd: ref Sys->FD): (ref Endofcdir, array of ref CDFhdr, string)
{
(ok, dir) := sys->fstat(fd);
if(ok < 0)
error(sprint("stat: %r"));
size := dir.length;
off := size-big (8*1024);
if(off < big 0)
off = big 0;
n := preadn(fd, buf := array[int (size-off)] of byte, len buf, off);
if(n < 0)
error(sprint("read: %r"));
buf = buf[:n];
(o, eerr) := findeocdir(buf);
if(eerr != nil)
error("cannot parse file: "+eerr);
off += big o;
(eocdir, err) := Endofcdir.parse(buf[o:]);
if(err != nil)
error("parsing end of central directory: "+err);
if(eocdir.disknr != 0 || eocdir.diskcdirentries != eocdir.cdirentries)
error("split zip file, not supported");
b := bufio->fopen(fd, Bufio->OREAD);
if(b == nil)
error(sprint("fopen: %r"));
if(b.seek(eocdir.cdiroffset, Bufio->SEEKSTART) != eocdir.cdiroffset)
error(sprint("seek to central directory: %r"));
a := array[eocdir.cdirentries] of ref CDFhdr;
for(i := 0; i < len a; i++) {
(fhdr, ferr) := CDFhdr.read(b);
if(ferr != nil)
error("reading central directory file header: "+ferr);
a[i] = fhdr;
}
return (eocdir, a, nil);
}
findeocdir(buf: array of byte): (int, string)
{
for(o := len buf-(4+2+2+2+2+4+4+2); o >= 0; o--)
if(buf[o] == byte 'P' && bufcmp(buf[o:o+4], eocentraldirsig) == 0)
return (o, nil);
return (-1, "cannot find end of central directory");
}
supported(f: ref Fhdr): string
{
if((f.versneeded & 255) > Version)
return sprint("version too low for opening file, have %s, need %s", versstr(Version), versstr(f.versneeded & 255));
if(f.flags & Fcompressedpatched)
return "file is a patch, not supported";
if(f.flags & Fstrongcrypto)
return "file is new-style encrypted, not supported";
if(f.flags & Fencrypted)
return "file is encrypted, not supported";
return nil;
}
openfile(fd: ref Sys->FD, cdf: ref CDFhdr): (ref Sys->FD, ref Fhdr, string)
{
(f, err) := Fhdr.read(fd, cdf.reloffset);
if(err != nil)
return (nil, nil, err);
err = supported(f);
if(err != nil)
return (nil, nil, err);
zfd: ref Sys->FD;
case f.comprmethod {
Mplain =>
zfd = pushbuf(fd, f.dataoff, int f.uncomprsize, cdf.crc32);
Mdeflate =>
# xxx +1 is a hack to prevent "premature end of stream" from our inflate. is our inflate broken or is it info-zip 3.0?
zfd = pushfilter(fd, f.dataoff, 1+int f.comprsize, cdf.crc32);
* =>
return (nil, nil, sprint("compression method %q not supported", comprmethod(f.comprmethod)));
}
if(zfd == nil)
return (nil, nil, "opening file in zip failed");
return (zfd, f, nil);
}
readfhdr(fd: ref Sys->FD, cdf: ref CDFhdr): (ref Fhdr, string)
{
(f, err) := Fhdr.read(fd, cdf.reloffset);
if(err == nil)
err = supported(f);
return (f, err);
}
# no crc32 protection, assumes supportedness has been checked already.
pread(fd: ref Sys->FD, f: ref Fhdr, buf: array of byte, n: int, off: big): int
{
if(f.comprmethod != Mplain) {
sys->werrstr("file is not plain (uncompressed)");
return -1;
}
if(off > f.uncomprsize)
off = f.uncomprsize;
if(off+big n > f.uncomprsize)
n = int (f.uncomprsize-off);
return sys->pread(fd, buf, n, f.dataoff+off);
}
cvtstr(d: array of byte, isutf8: int): string
{
if(isutf8)
return string d;
return cp437->btos(Convcs->Startstate, d, -1).t1;
}
versstr(v: int): string
{
v &= 255;
return sprint("%d.%d", v/10, v%10);
}
filegen: int;
fileio(): (ref Sys->FD, ref Sys->FileIO)
{
f := sprint("f%d", filegen++);
fio := sys->file2chan("/chan", f);
if(fio != nil)
rfd := sys->open("/chan/"+f, Sys->OREAD);
return (rfd, fio);
}
# as long as reads are sequential, keep track of the crc and verify at eof
pushbuf0(fd: ref Sys->FD, off: big, n: int, fdc: chan of ref Sys->FD, hdrcrc: big)
{
(rfd, fio) := fileio();
fdc <-= rfd;
if(rfd == nil)
return;
prevoff := 0;
docrc := 1;
crc := ~0;
end := off+big n;
Fio:
for(;;) {
(roff0, count, nil, rc) := <-fio.read;
if(rc == nil)
return;
roff := off+big roff0;
rend := roff+big count;
if(roff < off)
roff = off;
if(rend > end)
rend = end;
nn := sys->pread(fd, buf := array[int (rend-roff)] of byte, len buf, roff);
if(nn < 0) {
rc <-= (nil, sprint("%r"));
continue;
}
docrc = docrc && prevoff == roff0;
if(docrc) {
crc = crc32(crc, buf[:nn]);
if(nn == 0 && ~crc != int hdrcrc) {
rc <-= (nil, sprint("crc mismatch, expected %bux, calculated %ux", hdrcrc, ~crc));
break Fio;
}
prevoff += nn;
}
rc <-= (buf[:nn], nil);
}
}
pushbuf(fd: ref Sys->FD, off: big, n: int, hdrcrc: big): ref Sys->FD
{
spawn pushbuf0(fd, off, n, fdc := chan of ref Sys->FD, hdrcrc);
return <-fdc;
}
pushfilter0(fd: ref Sys->FD, off: big, n: int, fdc: chan of ref Sys->FD, hdrcrc: big)
{
(rfd, fio) := fileio();
fdc <-= rfd;
if(rfd == nil)
return;
rqc := inflate->start("");
pid: int;
pick srq := <-rqc {
Start =>
pid = srq.pid;
* =>
return;
}
crc := ~0;
poff := 0; # previous offset read
buf := array[0] of byte;
Fio:
for(;;) {
(roff, count, nil, rc) := <-fio.read;
if(rc == nil)
break Fio;
if(roff != poff) {
rc <-= (nil, "random reads not allowed");
break Fio;
}
Filter:
while(len buf == 0)
pick rq := <-rqc {
Start =>
rc <-= (nil, "bogus start message from filter");
break Fio;
Fill =>
give := len rq.buf;
if(give > n)
give = n;
nn := sys->pread(fd, rq.buf, give, off);
rq.reply <-= nn;
if(nn < 0) {
rc <-= (nil, sprint("read: %r"));
break Fio;
}
off += big nn;
n -= nn;
Result =>
crc = crc32(crc, rq.buf);
buf = array[len rq.buf] of byte;
buf[:] = rq.buf;
rq.reply <-= 0;
break Filter;
Finished =>
if(len rq.buf != 0)
say(sprint("%d leftover bytes", len rq.buf));
crc = ~crc;
if(crc != int hdrcrc) {
rc <-= (nil, sprint("crc mismatch, expected %bux, calculated %ux", hdrcrc, crc));
break Fio;
}
rc <-= (array[0] of byte, nil);
break Fio;
Info =>
say("inflate: "+rq.msg);
Error =>
rc <-= (nil, rq.e);
break Fio;
}
give := count;
if(give > len buf)
give = len buf;
r := buf[:give];
buf = buf[give:];
poff += give;
rc <-= (r, nil);
}
kill(pid);
}
pushfilter(fd: ref Sys->FD, off: big, n: int, hdrcrc: big): ref Sys->FD
{
spawn pushfilter0(fd, off, n, fdc := chan of ref Sys->FD, hdrcrc);
return <-fdc;
}
bufcmp(a, b: array of byte): int
{
for(i := 0; i < len a; i++)
if(a[i] != b[i])
return int a[i]-int b[i];
return 0;
}
g16(d: array of byte, o: int): (int, int)
{
if(o+2 > len d)
raise "get:short buffer";
v := 0;
v |= int d[o++]<<0;
v |= int d[o++]<<8;
return (v, o);
}
g32(d: array of byte, o: int): (big, int)
{
if(o+2 > len d)
raise "get:short buffer";
v := big 0;
v |= big d[o++]<<0;
v |= big d[o++]<<8;
v |= big d[o++]<<16;
v |= big d[o++]<<24;
return (v, o);
}
gstr(d: array of byte, o: int): (array of byte, int)
{
n: int;
(n, o) = g16(d, o);
if(o+n > len d)
raise "get:short buffer for string";
buf := array[n] of byte;
buf[:] = d[o:o+n];
return (buf, o+n);
}
gbuf(n: int, d: array of byte, o: int): (array of byte, int)
{
if(o+n > len d)
raise "get:short buffer for buffer";
buf := array[n] of byte;
buf[:] = d[o:o+n];
return (buf, o+n);
}
gbufstr(isutf8: int, n: int, d: array of byte, o: int): (string, int)
{
buf: array of byte;
(buf, o) = gbuf(n, d, o);
return (cvtstr(buf, isutf8), o);
}
p16(d: array of byte, o: int, v: int): int
{
d[o++] = byte (v>>0);
d[o++] = byte (v>>8);
return o;
}
p32(d: array of byte, o: int, v: big): int
{
d[o++] = byte (v>>0);
d[o++] = byte (v>>8);
d[o++] = byte (v>>16);
d[o++] = byte (v>>24);
return o;
}
pbuf(d: array of byte, o: int, buf: array of byte): int
{
d[o:] = buf;
return o+len buf;
}
preadn(fd: ref Sys->FD, buf: array of byte, n: int, off: big): int
{
have := 0;
for(;;) {
nn := sys->pread(fd, buf[have:], n, off);
if(nn < 0)
return nn;
if(nn == 0)
break;
have += nn;
off += big nn;
n -= nn;
}
return have;
}
breadn(b: ref Iobuf, buf: array of byte, n: int): int
{
have := 0;
for(;;) {
nn := b.read(buf[have:], n-have);
if(nn < 0)
return nn;
if(nn == 0)
break;
have += nn;
}
return have;
}
mkcrcval(poly, c: int): int
{
for(j := 0; j < 8; j++)
if(c & 1)
c = poly ^ ((c>>1) & 16r7fffffff);
else
c = (c>>1) & 16r7fffffff;
return c;
}
mkcrctab(poly: int): array of int
{
tab := array[256] of int;
for(i := 0; i < 256; i++)
tab[i] = mkcrcval(poly, i);
return tab;
}
crc32(crc: int, buf: array of byte): int
{
n := len buf;
for(i := 0; i < n; i++)
crc = crc32tab[(crc ^ int buf[i]) & 255] ^ ((crc>>8) & 16rffffff);
return crc;
}
rev[T](l: list of T): list of T
{
r: list of T;
for(; l != nil; l = tl l)
r = hd l::r;
return r;
}
kill(pid: int)
{
sys->fprint(sys->open(sprint("/prog/%d/ctl", pid), Sys->OWRITE), "kill");
}
say(s: string)
{
if(dflag)
sys->fprint(sys->fildes(2), "zip: %s\n", s);
}