code: purgatorio

ref: 82b046f36f8084a22bbb5d71edd0edd9179561eb
dir: /appl/cmd/disk/mkext.b/

View raw version
implement Mkext;

include "sys.m";
	sys: Sys;
	Dir, sprint, fprint: import sys;

include "draw.m";

include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

include "string.m";
	str: String;

include "arg.m";
	arg: Arg;

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

LEN: con Sys->ATOMICIO;
NFLDS: con 6;		# filename, modes, uid, gid, mtime, bytes

bin: ref Iobuf;
uflag := 0;
tflag := 0;
hflag := 0;
vflag := 0;
fflag := 0;
stderr: ref Sys->FD;
bout: ref Iobuf;
argv0 := "mkext";

init(nil: ref Draw->Context, args: list of string)
{
	sys = load Sys Sys->PATH;
	stderr = sys->fildes(2);
	bufio = load Bufio Bufio->PATH;
	if(bufio == nil)
		error(sys->sprint("cannot load %s: %r\n", Bufio->PATH));

	str = load String String->PATH;
	if(str == nil)
		error(sys->sprint("cannot load %s: %r\n", String->PATH));

	arg = load Arg Arg->PATH;
	if(arg == nil)
		error(sys->sprint("cannot load %s: %r\n", Arg->PATH));

	destdir := "";
	arg->init(args);
	arg->setusage("mkext [-h] [-d destdir] [-T] [-u] [-v] [file ...]");
	while((c := arg->opt()) != 0)
		case c {
		'd' =>
			destdir = arg->earg();
		'f' =>
			fflag = 1;

		'h' =>
			hflag = 1;
			bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
			if(bout == nil)
				error(sys->sprint("can't access standard output: %r"));
		'u' =>
			uflag = 1;
			tflag = 1;
		't' or 'T' =>
			tflag = 1;
		'v' =>
			vflag = 1;
		* =>
			arg->usage();
		}
	args = arg->argv();

	bin = bufio->fopen(sys->fildes(0), Sys->OREAD);
	if(bin == nil)
		error(sys->sprint("can't access standard input: %r"));
	while((p := bin.gets('\n')) != nil){
		if(p == "end of archive\n"){
			fprint(stderr, "done\n");
			quit(nil);
		}
		fields := str->unquoted(p);
		if(len fields != NFLDS){
			warn("too few fields in file header");
			continue;
		}
		name := hd fields;
		fields = tl fields;
		(mode, nil) := str->toint(hd fields, 8);
		fields = tl fields;
		uid := hd fields;
		fields = tl fields;
		gid := hd fields;
		fields = tl fields;
		(mtime, nil) := str->toint(hd fields, 10);
		fields = tl fields;
		(bytes, nil) := str->tobig(hd fields, 10);
		if(args != nil){
			if(!selected(name, args)){
				if(bytes != big 0)
					seekpast(bytes);
				continue;
			}
			mkdirs(destdir, name);
		}
		name = destdir+name;
		if(hflag){
			bout.puts(sys->sprint("%q %s %s %s %ud %bd\n",
				name, octal(mode), uid, gid, mtime, bytes));
			if(bytes != big 0)
				seekpast(bytes);
			continue;
		}
		if(mode & Sys->DMDIR)
			mkdir(name, mode, mtime, uid, gid);
		else
			extract(name, mode, mtime, uid, gid, bytes);
	}
	fprint(stderr, "premature end of archive\n");
	quit("eof");
}

quit(s: string)
{
	if(bout != nil)
		bout.flush();
	if(s != nil)
		raise "fail: "+s;
	exit;
}

fileprefix(prefix, s: string): int
{
	n := len prefix;
	m := len s;
	if(n > m || !str->prefix(prefix, s))
		return 0;
	if(m > n && s[n] != '/')
		return 0;
	return 1;
}

selected(s: string, args: list of string): int
{
	for(; args != nil; args = tl args)
		if(fileprefix(hd args, s))
			return 1;
	return 0;
}

mkdirs(basedir, name: string)
{
	(nil, names) := sys->tokenize(name, "/");
	while(names != nil) {
		#sys->print("mkdir %s\n", basedir);
		create(basedir, Sys->OREAD, 8r775|Sys->DMDIR);

		if(tl names == nil)
			break;
		basedir = basedir + "/" + hd names;
		names = tl names;
	}
}

mkdir(name: string, mode: int, mtime: int, uid: string, gid: string)
{
	d: Dir;
	i: int;

	fd := create(name, Sys->OREAD, mode);
	if(fd == nil){
		(i, d) = sys->stat(name);
		if(i < 0 || !(d.mode & Sys->DMDIR)){
			warn(sys->sprint("can't make directory %s: %r", name));
			return;
		}
	}else{
		(i, d) = sys->fstat(fd);
		if(i < 0)
			warn(sys->sprint("can't stat %s: %r", name));
		fd = nil;
	}

	d = sys->nulldir;
	(nil, p) := str->splitr(name, "/");
	if(p == nil)
		p = name;
	d.name = p;
	if(tflag)
		d.mtime = mtime;
	if(uflag){
		d.uid = uid;
		d.gid = gid;
	}
	d.mode = mode;
	if(sys->wstat(name, d) < 0)
		warn(sys->sprint("can't set modes for %s: %r", name));
	if(uflag){
		(i, d) = sys->stat(name);
		if(i < 0)
			warn(sys->sprint("can't reread modes for %s: %r", name));
		if(d.mtime != mtime)
			warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime));
		if(uid != d.uid)
			warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid));
		if(gid != d.gid)
			warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid));
	}
}

extract(name: string, mode: int, mtime: int, uid: string, gid: string, bytes: big)
{
	n: int;

	if(vflag)
		sys->print("x %s %bd bytes\n", name, bytes);

	sfd := create(name, Sys->OWRITE, mode);
	if(sfd == nil) {
		if(!fflag || sys->remove(name) == -1 ||
		    (sfd = create(name, Sys->OWRITE, mode)) == nil) {
			warn(sys->sprint("can't make file %s: %r", name));
			seekpast(bytes);
			return;
		}
	}
	b := bufio->fopen(sfd, Bufio->OWRITE);
	if (b == nil) {
		warn(sys->sprint("can't open file %s for bufio : %r", name));
		seekpast(bytes);
		return;
	}
	buf := array [LEN] of byte;
	for(tot := big 0; tot < bytes; tot += big n){
		n = len buf;
		if(tot + big n > bytes)
			n = int(bytes - tot);
		n = bin.read(buf, n);
		if(n <= 0)
			error(sys->sprint("premature eof reading %s", name));
		if(b.write(buf, n) != n)
			warn(sys->sprint("error writing %s: %r", name));
	}

	(i, nil) := sys->fstat(b.fd);
	if(i < 0)
		warn(sys->sprint("can't stat %s: %r", name));
	d := sys->nulldir;
	(nil, p) := str->splitr(name, "/");
	if(p == nil)
		p = name;
	d.name = p;
	if(tflag)
		d.mtime = mtime;
	if(uflag){
		d.uid = uid;
		d.gid = gid;
	}
	d.mode = mode;
	if(b.flush() == Bufio->ERROR)
		warn(sys->sprint("error writing %s: %r", name));
	if(sys->fwstat(b.fd, d) < 0)
		warn(sys->sprint("can't set modes for %s: %r", name));
	if(uflag){
		(i, d) = sys->fstat(b.fd);
		if(i < 0)
			warn(sys->sprint("can't reread modes for %s: %r", name));
		if(d.mtime != mtime)
			warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime));
		if(d.uid != uid)
			warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid));
		if(d.gid != gid)
			warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid));
	}
	b.close();
}

seekpast(bytes: big)
{
	n: int;

	buf := array [LEN] of byte;
	for(tot := big 0; tot < bytes; tot += big n){
		n = len buf;
		if(tot + big n > bytes)
			n = int(bytes - tot);
		n = bin.read(buf, n);
		if(n <= 0)
			error("premature eof");
	}
}

error(s: string)
{
	fprint(stderr, "%s: %s\n", argv0, s);
	quit("error");
}

warn(s: string)
{
	fprint(stderr, "%s: %s\n", argv0, s);
}

octal(i: int): string
{
	s := "";
	do {
		t: string;
		t[0] = '0' + (i&7);
		s = t+s;
	} while((i = (i>>3)&~(7<<29)) != 0);
	return s;
}

parent(name : string) : string
{
	slash := -1;
	for (i := 0; i < len name; i++)
		if (name[i] == '/')
			slash = i;
	if (slash > 0)
		return name[0:slash];
	return "/";
}

create(name : string, rw : int, mode : int) : ref Sys->FD
{
	fd := sys->create(name, rw, mode);
	if (fd == nil) {
		p := parent(name);
		(ok, d) := sys->stat(p);
		if (ok < 0)
			return nil;
		omode := d.mode;
		d = sys->nulldir;
		d.mode = omode | 8r222;		# ensure parent is writable
		if(sys->wstat(p, d) < 0) {
			warn(sys->sprint("can't set modes for %s: %r", p));
			return nil;
		}
		fd = sys->create(name, rw, mode);
		d.mode = omode;
		sys->wstat(p, d);
	}
	return fd;
}