code: 9ferno

ref: b548687a8ed1d0a159c9d3f3f921d93bbb56908e
dir: /appl/cmd/install/inst.b/

View raw version
implement Inst;

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;
include "keyring.m";
	keyring : Keyring;
include "arch.m";
	arch : Arch;
include "wrap.m";
	wrap : Wrap;

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

LEN: con Sys->ATOMICIO;

tflag := 0;
uflag := 0;
hflag := 0;
vflag := 0;
fflag := 1;
stderr: ref Sys->FD;
bout: ref Iobuf;
argv0 := "inst";
oldw, w : ref Wrap->Wrapped;
root := "/";
force := 0;
stoponerr := 1;

# membogus(argv: list of string)
# {
#
# }

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));
	keyring = load Keyring Keyring->PATH;
	if(keyring == nil)
		error(sys->sprint("cannot load %s: %r\n", Keyring->PATH));
	arch = load Arch Arch->PATH;
	if(arch == nil)
		error(sys->sprint("cannot load %s: %r\n", Arch->PATH));
	arch->init(bufio);
	wrap = load Wrap Wrap->PATH;
	if(wrap == nil)
		error(sys->sprint("cannot load %s: %r\n", Wrap->PATH));
	wrap->init(bufio);
	arg->init(args);
	while((c := arg->opt()) != 0)
		case c {
		'f' =>
			fflag = 0;
		'h' =>
			hflag = 1;
			bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
			if(bout == nil)
				error(sys->sprint("can't access standard output: %r"));
		't' =>
			tflag = 1;
		'u' =>
			uflag = 1;
		'v' =>
			vflag = 1;
		'r' =>
			root = arg->arg();
			if (root == nil)
				fatal("root missing");
		'F' =>
			force = 1;
		'c' =>
			stoponerr = 0;
		* =>
			usage();
		}
	args = arg->argv();
	if (args == nil)
		usage();
	ar := arch->openarch(hd args);
	if(ar == nil || ar.b == nil)
		error(sys->sprint("can't access %s: %r", hd args));
	w = wrap->openwraphdr(hd args, root, nil, 0);
	if (w == nil)
		fatal("no such package found");
	if(w.nu != 1)
		fatal("strange package: more than one piece");
	if (force == 0)
		oldw = wrap->openwrap(w.name, root, 0);
	if (force == 0 && w.u[0].utime && (oldw == nil || oldw.tfull < w.u[0].utime)){
		tfull: int;
		if(oldw == nil)
			tfull = -1;
		else
			tfull = oldw.tfull;
		fatal(sys->sprint("need %s version of %s already installed (pkg %d)", wrap->now2string(w.u[0].utime, 0), w.name, tfull));
	}
	args = tl args;
	digest := array[Keyring->MD5dlen] of byte;
	digest0 := array[Keyring->MD5dlen] of byte;
	digest1 := array[Keyring->MD5dlen] of byte;

	while ((a := arch->gethdr(ar)) != nil) {
		why := "";
		docopy := 0;
		if(force)
			docopy = 1;
		else if(a.d.mode & Sys->DMDIR)
			docopy = 1;
		else if(wrap->md5file(root+a.name, digest) < 0)
			docopy = 1;
		else{
			wrap->md5filea(root+a.name, digest1);
			(ok, t) := wrap->getfileinfo(oldw, a.name, digest, nil, digest1);
			if (ok >= 0) {
				if(t > w.u[0].time){
					docopy = 0;
					why = "version from newer package exists";
				}
				else
					docopy = 1;
			}
			else {
				(ok, t) = wrap->getfileinfo(oldw, a.name, nil, nil, nil);
				if(ok >= 0){
					docopy = 0;
					why = "locally modified";
				}
				else{
					docopy = 0;
					why = "locally created";
				}
			}
		}
		if(!docopy){
			wrap->md5sum(ar.b, digest0, int a.d.length);
			if(wrap->memcmp(digest, digest0, Keyring->MD5dlen))
				skipfile(a.name, why);
			continue;
		}
		if(args != nil){
			if(!selected(a.name, args)){
				arch->drain(ar, int a.d.length);
				continue;
			}
			if (!hflag)
				mkdirs(root, a.name);
		}
		name := pathcat(root, a.name);
		if(hflag){
			bout.puts(sys->sprint("%s %uo %s %s %ud %d\n",
				name, a.d.mode, a.d.uid, a.d.gid, a.d.mtime, int a.d.length));
			arch->drain(ar, int a.d.length);
			continue;
		}
		if(a.d.mode & Sys->DMDIR)
			mkdir(name, a.d);
		else
			extract(ar, name, a.d);
	}
	arch->closearch(ar);
	if(ar.err == nil){
		# fprint(stderr, "done\n");
		quit(nil);
	}
	else {
		fprint(stderr, "%s\n", ar.err);
		quit("eof");
	}
}

skipfile(f : string, why : string)
{
	sys->fprint(stderr, "skipping %s: %s\n", f, why);
}

skiprmfile(f: string, why: string)
{
	sys->fprint(stderr, "not removing %s: %s\n", f, why);
}

doremove(s : string)
{
	p := pathcat(root, s);
	digest := array[Keyring->MD5dlen] of { * => byte 0 };
	digest1 := array[Keyring->MD5dlen] of { * => byte 0 };
	if(wrap->md5file(p, digest) < 0)
		;
	else{
		wrap->md5filea(p, digest1);
		(ok, nil) := wrap->getfileinfo(oldw, s, digest, nil, digest1);
		if(force == 0 && ok < 0)
			skiprmfile(p, "locally modified");
		else{
			if (vflag)
				sys->print("rm %s\n", p);
			remove(p);
		}
	}
}

quit(s: string)
{
	if (s == nil) {
		p := w.u[0].dir + "/remove";
		if ((b := bufio->open(p, Bufio->OREAD)) != nil) {
			while ((t := b.gets('\n')) != nil) {
				lt := len t;
				if (t[lt-1] == '\n')
					t = t[0:lt-1];
				doremove(t);
			}
		}
	}
	if(bout != nil)
		bout.flush();
	if(wrap != nil)
		wrap->end();
	if(s != nil)
		raise "fail: "+s;
	else
		fprint(stderr, "done\n");
	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) {
		create(basedir, Sys->OREAD, 8r775|Sys->DMDIR);
		if(tl names == nil)
			break;
		basedir = basedir + "/" + hd names;
		names = tl names;
	}
}

mkdir(name: string, dir : ref Sys->Dir)
{
	d: Dir;
	i: int;

	if(vflag) {
		MTPT : con "/n/remote";
		s := name;
		if (len name >= len MTPT && name[0:len MTPT] == MTPT)
			s = name[len MTPT:];
		sys->print("installing directory %s\n", s);
	}
	fd := create(name, Sys->OREAD, dir.mode);
	if(fd == nil) {
		err := sys->sprint("%r");
		(i, d) = sys->stat(name);
		if(i < 0 || !(d.mode & Sys->DMDIR)){
			werr(sys->sprint("can't make directory %s: %s", name, err));
			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;
	d.mode = dir.mode;
	if(tflag || uflag)
		d.mtime = dir.mtime;
	if(uflag){
		d.uid = dir.uid;
		d.gid = dir.gid;
	}
	fd = nil;
	if(sys->wstat(name, d) < 0){
		e := sys->sprint("%r");
		if(wstat(name, d) < 0)
			warn(sys->sprint("can't set modes for %s: %s", name, e));
	}
	if(uflag){
		(i, d) = sys->stat(name);
		if(i < 0)
			warn(sys->sprint("can't reread modes for %s: %r", name));
		if(dir.uid != d.uid)
			warn(sys->sprint("%s: uid mismatch %s %s", name, dir.uid, d.uid));
		if(dir.gid != d.gid)
			warn(sys->sprint("%s: gid mismatch %s %s", name, dir.gid, d.gid));
	}
}

extract(ar : ref Arch->Archive, name: string, dir : ref Sys->Dir)
{
	sfd := create(name, Sys->OWRITE, dir.mode);
	if(sfd == nil) {
		if(!fflag || remove(name) == -1 ||
		    (sfd = create(name, Sys->OWRITE, dir.mode)) == nil) {
			werr(sys->sprint("can't make file %s: %r", name));
			arch->drain(ar, int dir.length);
			return;
		}
	}
	b := bufio->fopen(sfd, Bufio->OWRITE);
	if (b == nil) {
		warn(sys->sprint("can't open file %s for bufio : %r", name));
		arch->drain(ar, int dir.length);
		return;
	}
	err := arch->getfile(ar, b, int dir.length);
	if (err != nil) {
		if (len err >= 9 && err[0:9] == "premature")
			fatal(err);
		else
			warn(err);
	}
	(i, d) := 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;
	d.mode = dir.mode;
	if(tflag || uflag)
		d.mtime = dir.mtime;
	if(uflag){
		d.uid = dir.uid;
		d.gid = dir.gid;
	}
	if(b.flush() == Bufio->ERROR)
		werr(sys->sprint("error writing %s: %r", name));
	b.close();
	sfd = nil;
	if(sys->wstat(name, d) < 0){
		e := sys->sprint("%r");
		if(wstat(name, d) < 0)
			warn(sys->sprint("can't set modes for %s: %s", name, e));
	}
	if(uflag){
		(i, d) = sys->stat(name);
		if(i < 0)
			warn(sys->sprint("can't reread modes for %s: %r", name));
		if(d.uid != dir.uid)
			warn(sys->sprint("%s: uid mismatch %s %s", name, dir.uid, d.uid));
		if(d.gid != dir.gid)
			warn(sys->sprint("%s: gid mismatch %s %s", name, dir.gid, d.gid));
	}
}

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

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

usage()
{
	fprint(stderr, "Usage: inst [-h] [-u] [-v] [-f] [-c] [-F] [-r dest-root] [file ...]\n");
	raise "fail: usage";
}

fatal(s : string)
{
	sys->fprint(stderr, "inst: %s\n", s);
	if(wrap != nil)
		wrap->end();
	exit;
}

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
		sys->wstat(p, d);
		fd = sys->create(name, rw, mode);
		d.mode = omode;
		sys->wstat(p, d);
	}
	return fd;
}

remove(name : string) : int
{
	if (sys->remove(name) < 0) {
		(ok, d) := sys->stat(name);
		if (ok < 0)
			return -1;
		omode := d.mode;
		d.mode |= 8r222;
		sys->wstat(name, d);
		if (sys->remove(name) >= 0)
			return 0;
		d.mode = omode;
		sys->wstat(name, d);
		return -1;
	}
	return 0;
}

wstat(name : string, d : Dir) : int
{
	(ok, dir) := sys->stat(name);
	if (ok < 0)
		return -1;
	omode := dir.mode;
	dir.mode |= 8r222;
	sys->wstat(name, dir);
	if (sys->wstat(name, d) >= 0)
		return 0;
	dir.mode = omode;
	sys->wstat(name, dir);
	return -1;
}

pathcat(s : string, t : string) : string
{
	if (s == nil) return t;
	if (t == nil) return s;
	slashs := s[len s - 1] == '/';
	slasht := t[0] == '/';
	if (slashs && slasht)
		return s + t[1:];
	if (!slashs && !slasht)
		return s + "/" + t;
	return s + t;
}