ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/zip/zipstream.b/
# rewrite zip file, putting central directory at the front of the file.
implement Zipstream;
include "sys.m";
sys: Sys;
sprint: import sys;
include "draw.m";
include "arg.m";
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "string.m";
str: String;
include "filter.m";
deflate: Filter;
include "zip.m";
zip: Zip;
Fhdr, CDFhdr, Endofcdir: import zip;
Zipstream: module {
init: fn(nil: ref Draw->Context, args: list of string);
};
dflag: int;
oflag: int;
init(nil: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
arg := load Arg Arg->PATH;
bufio = load Bufio Bufio->PATH;
str = load String String->PATH;
deflate = load Filter Filter->DEFLATEPATH;
deflate->init();
zip = load Zip Zip->PATH;
zip->init();
arg->init(args);
arg->setusage(arg->progname()+" [-d] [-o] zipfile");
while((c := arg->opt()) != 0)
case c {
'd' => zip->dflag = dflag++;
'o' => oflag++;
}
args = arg->argv();
if(len args != 1)
arg->usage();
fd := sys->open(hd args, Sys->OREAD);
if(fd == nil)
fail(sprint("open: %r"));
(eocd, cdfhdrs, err) := zip->open(fd);
if(err != nil)
fail("parsing zip: "+err);
if(oflag) {
files := readfiles();
n := array[len cdfhdrs] of ref CDFhdr;
for(i := 0; i < len files; i++) {
j := find(cdfhdrs, files[i]);
if(j < 0)
fail(sprint("%#q not in zip file or specified twice", files[i]));
n[i] = cdfhdrs[j];
cdfhdrs[j] = nil;
}
for(j := 0; j < len cdfhdrs; j++)
if(cdfhdrs[j] != nil)
n[i++] = cdfhdrs[j];
cdfhdrs = n;
}
zb := bufio->fopen(sys->fildes(1), bufio->OWRITE);
if(zb == nil)
fail(sprint("fopen: %r"));
# calculate size of cdfhdrs
o := big 0;
for(i := 0; i < len cdfhdrs; i++)
o += big len cdfhdrs[i].pack();
cdsz := o;
eocd.cdirsize = cdsz;
eocd.cdiroffset = big 0;
o += big len eocd.pack();
# fix the offsets in the cdfhdrs & fhdrs, store original offsets
origoff := array[len cdfhdrs] of big;
fhdrs := array[len cdfhdrs] of ref Fhdr;
for(i = 0; i < len fhdrs; i++) {
cdf := cdfhdrs[i];
f: ref Fhdr;
(f, err) = Fhdr.read(fd, cdf.reloffset);
if(err != nil)
fail("reading local file header: "+err);
origoff[i] = f.dataoff;
cdf.reloffset = o;
fhdrs[i] = Fhdr.mk(cdf);
o += big len fhdrs[i].pack()+fhdrs[i].comprsize;
}
# write the cdfhdrs
for(i = 0; i < len cdfhdrs; i++) {
if(zb.write(buf := cdfhdrs[i].pack(), len buf) != len buf)
fail(sprint("write: %r"));
}
# a copy of the end of central directory structure (to keep other zip code happy)
if(zb.write(buf := eocd.pack(), len buf) != len buf)
fail(sprint("write: %r"));
# write the fhdrs & file contents
for(i = 0; i < len fhdrs; i++) {
if(zb.write(buf = fhdrs[i].pack(), len buf) != len buf || copyrange(fd, origoff[i], fhdrs[i].comprsize, zb) < 0)
fail(sprint("write: %r"));
}
if(zb.offset() != o)
fail(sprint("inconsitent offset after rewriting central directory and files, expected offset %bd, saw %bd", o, zb.offset()));
# write the eocd, this one is typically found by code parsing the zip file
if(zb.write(buf = eocd.pack(), len buf) != len buf || zb.flush() == bufio->ERROR)
fail(sprint("write: %r"));
}
readfiles(): array of string
{
b := bufio->fopen(sys->fildes(0), bufio->OREAD);
if(b == nil)
fail(sprint("fopen: %r"));
l: list of string;
for(;;) {
s := b.gets('\n');
if(s == nil)
break;
if(s[len s-1] == '\n')
s = s[:len s-1];
l = s::l;
}
f := array[len l] of string;
i := len f-1;
for(; l != nil; l = tl l)
f[i--] = hd l;
return f;
}
find(h: array of ref CDFhdr, s: string): int
{
for(i := 0; i < len h; i++)
if(h[i] != nil && h[i].filename == s)
return i;
return -1;
}
copyrange(fd: ref Sys->FD, off: big, n: big, zb: ref Iobuf): int
{
buf := array[sys->ATOMICIO] of byte;
while(n > big 0) {
if(n > big len buf)
nn := len buf;
else
nn = int n;
r := preadn(fd, buf, nn, off);
if(r < 0)
return -1;
else if(r != nn) {
sys->werrstr("short read");
return -1;
}
if(zb.write(buf, r) != r)
return -1;
off += big r;
n -= big r;
}
return 0;
}
preadn(fd: ref Sys->FD, buf: array of byte, n: int, off: big): int
{
org := n;
while(n > 0) {
nn := sys->pread(fd, buf, n, off);
if(nn < 0)
return nn;
if(nn == 0)
break;
n -= nn;
off += big nn;
buf = buf[nn:];
}
return org-n;
}
warn(s: string)
{
sys->fprint(sys->fildes(2), "%s\n", s);
}
say(s: string)
{
if(dflag)
warn(s);
}
fail(s: string)
{
warn(s);
raise "fail:"+s;
}