code: purgatorio

ref: 82b046f36f8084a22bbb5d71edd0edd9179561eb
dir: /appl/cmd/zip/zipstream.b/

View raw version
# 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;
}