code: purgatorio

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

View raw version
# zipfs design
# 
# at startup we read the entire central directory,
# set up a styxservers nametree and serve from that.
# bit 62 of the qid denotes the directory bit.
#
# if a file is not compressed and -p was set, we read directly through
# the zipfile's fd (no crc protection).
# otherwise, on first read of a file, we open a fd for that file.
# sequential or higher-than-current offset reads reuse the fd.
# random reads to before the current offset reopen the file and
# seek to the desired location by reading & discarding data.

implement Zipfs;

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 "daytime.m";
	daytime: Daytime;
include "styx.m";
	styx: Styx;
	Tmsg, Rmsg: import Styx;
include "styxservers.m";
	styxservers: Styxservers;
	Styxserver, Fid, Navigator, Navop: import styxservers;
	nametree: Nametree;
	Tree: import nametree;
include "tables.m";
	tables: Tables;
	Table, Strhash: import tables;
include "zip.m";
	zip: Zip;
	Fhdr, CDFhdr, Endofcdir: import zip;

Zipfs: module {
	init:	fn(nil: ref Draw->Context, args: list of string);
};


# directories for styx nametree
Zdir: adt {
	name:	string;
	qid:	big;
	pqid:	big;
};

# opened file from zip archive
Zfile: adt {
	fid:	int;
	fd:	ref Sys->FD;
	off:	big;
	cdf:	ref CDFhdr;
	f:	ref Fhdr;
};

Qiddir: con big 1<<62;

zipfd: ref Sys->FD;
srv: ref Styxserver;
files: array of ref Fhdr;
zdirs: ref Strhash[ref Zdir]; # indexed by full path without trailing slash
rootzdir: ref Zdir;
dirgen: int;

cdirfhdrs: array of ref CDFhdr;

zfiles: ref Table[ref Zfile]; # indexed by fid

Dflag: int;
dflag: int;
pflag: int;
now: 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;
	daytime = load Daytime Daytime->PATH;
	styx = load Styx Styx->PATH;
	styx->init();
	styxservers = load Styxservers Styxservers->PATH;
	styxservers->init(styx);
	nametree = load Nametree Nametree->PATH;
	nametree->init();
	tables = load Tables Tables->PATH;
	zip = load Zip Zip->PATH;
	zip->init();

	# for zip library
	sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
	if(sys->bind("#s", "/chan", Sys->MREPL) < 0)
		fail(sprint("bind /chan: %r"));

	arg->init(args);
	arg->setusage(arg->progname()+" [-Ddp] zipfile");
	while((c := arg->opt()) != 0)
		case c {
		'D' =>	styxservers->traceset(Dflag++);
		'd' =>	zip->dflag = dflag++;
		'p' =>	pflag++;
		* =>	arg->usage();
		}
	args = arg->argv();
	if(len args != 1)
		arg->usage();

	zdirs = zdirs.new(101, nil);

	zipfd = sys->open(hd args, Sys->OREAD);
	if(zipfd == nil)
		fail(sprint("open: %r"));

	err: string;
	(nil, cdirfhdrs, err) = zip->open(zipfd);
	if(err != nil)
		fail("parsing zip: "+err);

	now = daytime->now();
	(tree, treeop) := nametree->start();
	rzd := rootzdir = ref Zdir (".", big 0|Qiddir, big 0|Qiddir);
	tree.create(rzd.pqid, dir(rzd.name, rzd.qid, now, big 0));

	for(i := 0; i < len cdirfhdrs; i++) {
		f := cdirfhdrs[i];
		fs := f.filename;
		(path, name) := str->splitstrr(fs, "/");
		zd := ensuredir(tree, path);
		if(fs == nil || fs[len fs-1] == '/')
			continue;
		qid := big (i+1);
		tree.create(zd.qid, dir(name, qid, f.mtime, f.uncomprsize));
	}
	zdirs = nil;

	zfiles = zfiles.new(31, nil);

	msgc: chan of ref Tmsg;
	(msgc, srv) = Styxserver.new(sys->fildes(0), Navigator.new(treeop), big 0|Qiddir);
	spawn styxsrv(msgc);
}

# inefficient
# called with empty string (denoting the root directory), or path with trailing slash
ensuredir(tree: ref Tree, s: string): ref Zdir
{
	if(s == nil)
		return rootzdir;
	s = s[:len s-1];

	zd := zdirs.find(s);
	if(zd != nil)
		return zd;

	(ppath, name) := str->splitstrr(s, "/");
	pzd := ensuredir(tree, ppath);
	zd = ref Zdir (name, big ++dirgen|Qiddir, pzd.qid);
	tree.create(pzd.qid, dir(zd.name, zd.qid, now, big 0));
	zdirs.add(s, zd);
	return zd;
}

dir(name: string, qid: big, mtime: int, length: big): Sys->Dir
{
	d := sys->zerodir;
	d.name = name;
	d.uid = "zip";
	d.gid = "zip";
	d.qid.path = qid;
	if((qid & Qiddir) != big 0) {
		d.qid.qtype = Sys->QTDIR;
		d.mode = Sys->DMDIR|8r555;
	} else {
		d.qid.qtype = Sys->QTFILE;
		d.mode = 8r444;
	}
	d.mtime = d.atime = mtime;
	d.length = length;
	return d;
}

styxsrv(msgc: chan of ref Tmsg)
{
done:
	for(;;) alt {
	mm := <-msgc =>
		if(mm == nil)
			break done;
		pick m := mm {
		Readerror =>
			warn("read error: "+m.error);
			break done;
		}
		dostyx(mm);
	}
	killgrp(sys->pctl(0, nil));
}

openzfile(fid: int, qid: big): (ref Zfile, string)
{
	cdf := cdirfhdrs[int qid-1];
	zf := ref Zfile (fid, nil, big 0, cdf, nil);
	err: string;
	if(pflag && cdf.comprmethod == zip->Mplain)
		(zf.f, err) = zip->readfhdr(zipfd, cdf);
	else
		(zf.fd, nil, err) = zip->openfile(zipfd, cdf);
	if(err != nil)
		return (nil, err);
	if(Dflag) warn("zipfs: "+cdf.filename);
	zfiles.add(fid, zf);
	return (zf, nil);
}

zfileseek(zf: ref Zfile, off: big): string
{
	if(zf.off == off)
		return nil;

	if(off < zf.off) {
		(fd, nil, err) := zip->openfile(zipfd, zf.cdf);
		if(err != nil)
			return err;
		zf.fd = fd;
		zf.off = big 0;
	}

	n := int (off-zf.off);
	buf := array[Sys->ATOMICIO] of byte;
	while(n > 0) {
		nn := sys->read(zf.fd, buf, len buf);
		if(nn < 0)
			return sprint("seeking to requested offset: %r");
		if(nn == 0)
			break;  # not there yet, but subsequent reads on zf.fd will/should return eof too
		zf.off += big nn;
		n -= nn;
	}

	return nil;
}

dostyx(mm: ref Tmsg)
{
	pick m := mm {
	Clunk or
	Remove =>
		f := srv.getfid(m.fid);
		if(f != nil && f.isopen && (f.path & Qiddir) == big 0)
			zfiles.del(m.fid);
		srv.default(m);

	Read =>
		f := srv.getfid(m.fid);
		if(f.qtype & Sys->QTDIR)
			return srv.default(m);

		zf := zfiles.find(m.fid);
		if(zf == nil) {
			err: string;
			(zf, err) = openzfile(m.fid, f.path);
			if(err != nil)
				return replyerror(m, err);
		}

		if(zf.f == nil) {
			err := zfileseek(zf, m.offset);
			if(err != nil)
				return replyerror(m, err);
			n := sys->read(zf.fd, buf := array[m.count] of byte, len buf);
			if(n < 0)
				return replyerror(m, sprint("%r"));
			zf.off += big n;
			srv.reply(ref Rmsg.Read(m.tag, buf[:n]));
		} else {
			n := zip->pread(zipfd, zf.f, buf := array[m.count] of byte, len buf, m.offset);
			if(n < 0)
				return replyerror(m, sprint("%r"));
			srv.reply(ref Rmsg.Read(m.tag, buf[:n]));
		}

	* =>
		srv.default(mm);
	}
}

replyerror(m: ref Tmsg, s: string)
{
	srv.reply(ref Rmsg.Error(m.tag, s));
}

killgrp(pid: int)
{
	sys->fprint(sys->open(sprint("/prog/%d/ctl", pid), Sys->OWRITE), "killgrp");
}

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;
}