shithub: purgatorio

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

View raw version
implement Mkfs;

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;

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

LEN: con Sys->ATOMICIO;
HUNKS: con 128;

Kfs, Fs, Archive: con iota;	# types of destination file sytems

File: adt {
	new:	string;
	elem:	string;
	old:	string;
	uid:	string;
	gid:	string;
	mode:	int;
};

b: ref Iobuf;
bout: ref Iobuf;			# stdout when writing archive
newfile: string;
oldfile: string;
proto: string;
cputype: string;
users: string;
oldroot: string;
newroot: string;
prog := "mkfs";
lineno := 0;
buf: array of byte;
zbuf: array of byte;
buflen := 1024-8;
indent: int;
verb: int;
modes: int;
ream: int;
debug: int;
xflag: int;
sfd: ref Sys->FD;
fskind: int;	# Kfs, Fs, Archive
user: string;
stderr: ref Sys->FD;
usrid, grpid : string;
setuid: int;
	
init(nil: ref Draw->Context, args: list of string)
{
	sys = load Sys Sys->PATH;
	bufio = load Bufio Bufio->PATH;
	str = load String String->PATH;
	arg = load Arg Arg->PATH;

	sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil);

	stderr = sys->fildes(2);
	if(arg == nil)
		error(sys->sprint("can't load %q: %r", Arg->PATH));

	user = getuser();
	if(user == nil)
		user = "none";
	name := "";
	file := ref File;
	file.new = "";
	file.old = nil;
	file.mode = 0;
	oldroot = "";
	newroot = "/n/kfs";
	users = nil;
	fskind = Kfs;	# i suspect Inferno default should be different
	arg->init(args);
	arg->setusage("mkfs [-aprvxS] [-d root] [-n kfscmdname] [-s src-fs] [-u userfile] [-z n] [-G group] [-U user] proto ...");
	while((c := arg->opt()) != 0)
		case c {
		'a' =>
			fskind = Archive;
			newroot = "";
			bout = bufio->fopen(sys->fildes(1), Sys->OWRITE);
			if(bout == nil)
				error(sys->sprint("can't open standard output for archive: %r"));
		'd' =>
			fskind = Fs;
			newroot = arg->earg();
		'D' =>
			debug = 1;
		'n' =>
			name = arg->earg();
		'p' =>
			modes = 1;
		'q' =>
			;
		'r' =>
			ream = 1;
		's' =>
			oldroot = arg->earg();
		'u' =>
			users = arg->earg();
		'v' =>
			verb = 1;
		'x' =>
			xflag = 1;
		'z' =>
			(buflen, nil) = str->toint(arg->earg(), 10);
			buflen -= 8;	# qid.path and tag at end of each kfs block
		'U' => 
			usrid = arg->earg();
		'G' =>
			grpid = arg->earg();
		'S' =>
			setuid = 1;
		* =>
			arg->usage();
		}

	args = arg->argv();
	if(args == nil)
		arg->usage();

	buf = array [buflen] of byte;
	zbuf = array [buflen] of { * => byte 0 };

	if(name != nil)
		openkfscmd(name);
	kfscmd("allow");
	if(users != nil){
		proto = "users";	# for diagnostics
		setusers();
	}
	cputype = getenv("cputype");
	if(cputype == nil)
		cputype = "dis";

	errs := 0;
	for(; args != nil; args = tl args){
		proto = hd args;
		fprint(stderr, "processing %s\n", proto);

		b = bufio->open(proto, Sys->OREAD);
		if(b == nil){
			fprint(stderr, "%s: can't open %q: %r: skipping\n", prog, proto);
			errs++;
			continue;
		}

		lineno = 0;
		indent = 0;
		mkfs(file, -1);
		b.close();
	}
	fprint(stderr, "file system made\n");
	kfscmd("disallow");
	kfscmd("sync");
	if(errs)
		quit("skipped protos");
	if(fskind == Archive){
		bout.puts("end of archive\n");
		if(bout.flush() == Bufio->ERROR)
			error(sys->sprint("write error: %r"));
	}
}

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

mkfs(me: ref File, level: int)
{
	(child, fp) := getfile(me);
	if(child == nil)
		return;
	if(child.elem == "+" || child.elem == "*" || child.elem == "%"){
		rec := child.elem[0] == '+';
		filesonly := child.elem[0] == '%';
		child.new = me.new;
		setnames(child);
		mktree(child, rec, filesonly);
		(child, fp) = getfile(me);
	}
	while(child != nil && indent > level){
		if(mkfile(child))
			mkfs(child, indent);
		(child, fp) = getfile(me);
	}
	if(child != nil){
		b.seek(fp, 0);
		lineno--;
	}
}

mktree(me: ref File, rec: int, filesonly: int)
{
	fd := sys->open(oldfile, Sys->OREAD);
	if(fd == nil){
		warn(sys->sprint("can't open %q: %r", oldfile));
		return;
	}

	child := ref *me;
	r := ref Rec(nil, 0);
	for(;;){
		(n, d) := sys->dirread(fd);
		if(n <= 0)
			break;
		for(i := 0; i < n; i++)
		  	if (!recall(d[i].name, r)) {
				if(filesonly && d[i].mode & Sys->DMDIR)
					continue;
				child.new = mkpath(me.new, d[i].name);
				if(me.old != nil)
					child.old = mkpath(me.old, d[i].name);
				child.elem = d[i].name;
				setnames(child);
				if(copyfile(child, ref d[i], 1) && rec)
					mktree(child, rec, filesonly);
		  	}
	}
}

# Recall namespace fix
# -- remove duplicates (could use Readdir->init(,Readdir->COMPACT))
# obc

Rec: adt
{
	ad: array of string;
	l: int;
};

AL : con HUNKS;
recall(e : string, r : ref Rec) : int
{
	if (r.ad == nil) r.ad = array[AL] of string;
	# double array
	if (r.l >= len r.ad) {
		nar := array[2*(len r.ad)] of string;
		nar[0:] = r.ad;
		r.ad = nar;
	}
	for(i := 0; i < r.l; i++)
		if (r.ad[i] == e) return 1;
	r.ad[r.l++] = e;
	return 0;
}

mkfile(f: ref File): int
{
	(i, dir) := sys->stat(oldfile);
	if(i < 0){
		warn(sys->sprint("can't stat file %q: %r", oldfile));
		skipdir();
		return 0;
	}
	return copyfile(f, ref dir, 0);
}

copyfile(f: ref File, d: ref Dir, permonly: int): int
{
	mode: int;

	if(xflag && bout != nil){
		bout.puts(sys->sprint("%q\t%d\t%bd\n", f.new, d.mtime, d.length));
		return (d.mode & Sys->DMDIR) != 0;
	}
	d.name = f.elem;
	if(d.dtype != 'M' && d.dtype != 'U'){		# hmm... Indeed!
		d.uid = "inferno";
		d.gid = "inferno";
		mode = (d.mode >> 6) & 7;
		d.mode |= mode | (mode << 3);
	}
	if(f.uid != "-")
		d.uid = f.uid;
	if(f.gid != "-")
		d.gid = f.gid;
	if(fskind == Fs && !setuid){	# new system: set to nil
		d.uid = user;
		d.gid = user;
	}
	if (usrid != nil)
		d.uid = usrid;
	if (grpid != nil)
		d.gid = grpid;
	if(f.mode != ~0){
		if(permonly)
			d.mode = (d.mode & ~8r666) | (f.mode & 8r666);
		else if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR))
			warn(sys->sprint("inconsistent mode for %s", f.new));
		else
			d.mode = f.mode;
	}
	if(!uptodate(d, newfile)){
		if(d.mode & Sys->DMDIR)
			mkdir(d);
		else {
			if(verb)
				fprint(stderr, "%q\n", f.new);
			copy(d);
		}
	}else if(modes){
		nd := sys->nulldir;
		nd.mode = d.mode;
		nd.mtime = d.mtime;
		nd.gid = d.gid;
		if(sys->wstat(newfile, nd) < 0)
			warn(sys->sprint("can't set modes for %q: %r", f.new));
		# do the uid separately since different file systems object
		nd = sys->nulldir;
		nd.uid = d.uid;
		sys->wstat(newfile, nd);
	}
	return (d.mode & Sys->DMDIR) != 0;
}


# check if file to is up to date with
# respect to the file represented by df

uptodate(df: ref Dir, newf: string): int
{
	if(fskind == Archive || ream)
		return 0;
	(i, dt) := sys->stat(newf);
	if(i < 0)
		return 0;
	return dt.mtime >= df.mtime;
}

copy(d: ref Dir)
{
	t: ref Sys->FD;
	n: int;

	f := sys->open(oldfile, Sys->OREAD);
	if(f == nil){
		warn(sys->sprint("can't open %q: %r", oldfile));
		return;
	}
	t = nil;
	if(fskind == Archive)
		arch(d);
	else{
		(dname, fname) := str->splitr(newfile, "/");
		if(fname == nil)
			error(sys->sprint("internal temporary file error (%s)", dname));
		cptmp := dname+"__mkfstmp";
		t = sys->create(cptmp, Sys->OWRITE, 8r666);
		if(t == nil){
			warn(sys->sprint("can't create %q: %r", newfile));
			return;
		}
	}

	for(tot := big 0;; tot += big n){
		n = sys->read(f, buf, buflen);
		if(n < 0){
			warn(sys->sprint("can't read %q: %r", oldfile));
			break;
		}
		if(n == 0)
			break;
		if(fskind == Archive){
			if(bout.write(buf, n) != n)
				error(sys->sprint("write error: %r"));
		}else if(buf[0:buflen] == zbuf[0:buflen]){
			if(sys->seek(t, big buflen, 1) < big 0)
				error(sys->sprint("can't write zeros to %q: %r", newfile));
		}else if(sys->write(t, buf, n) < n)
			error(sys->sprint("can't write %q: %r", newfile));
	}
	f = nil;
	if(tot != d.length){
		warn(sys->sprint("wrong number bytes written to %s (was %bd should be %bd)",
			newfile, tot, d.length));
		if(fskind == Archive){
			warn("seeking to proper position");
			bout.seek(d.length - tot, 1);
		}
	}
	if(fskind == Archive)
		return;
	sys->remove(newfile);
	nd := sys->nulldir;
	nd.name = d.name;
	nd.mode = d.mode;
	nd.mtime = d.mtime;
	if(sys->fwstat(t, nd) < 0)
		error(sys->sprint("can't move tmp file to %q: %r", newfile));
	nd = sys->nulldir;
	nd.gid = d.gid;
	if(sys->fwstat(t, nd) < 0)
		warn(sys->sprint("can't set group id of %q to %q: %r", newfile, d.gid));
	nd.gid = nil;
	nd.uid = d.uid;
	sys->fwstat(t, nd);
}

mkdir(d: ref Dir)
{
	if(fskind == Archive){
		arch(d);
		return;
	}
	fd := sys->create(newfile, Sys->OREAD, d.mode);
	nd := sys->nulldir;
	nd.mode = d.mode;
	nd.gid = d.gid;
	nd.mtime = d.mtime;
	if(fd == nil){
		(i, d1) := sys->stat(newfile);
		if(i < 0 || !(d1.mode & Sys->DMDIR))
			error(sys->sprint("can't create %q", newfile));
		if(sys->wstat(newfile, nd) < 0)
			warn(sys->sprint("can't set modes for %q: %r", newfile));
		nd = sys->nulldir;
		nd.uid = d.uid;
		sys->wstat(newfile, nd);
		return;
	}
	if(sys->fwstat(fd, nd) < 0)
		warn(sys->sprint("can't set modes for %q: %r", newfile));
	nd = sys->nulldir;
	nd.uid = d.uid;
	sys->fwstat(fd, nd);
}

arch(d: ref Dir)
{
	bout.puts(sys->sprint("%q %uo %q %q %ud %bd\n",
		newfile, d.mode, d.uid, d.gid, d.mtime, d.length));
}

mkpath(prefix, elem: string): string
{
	return sys->sprint("%s/%s", prefix, elem);
}

setnames(f: ref File)
{
	newfile = newroot+f.new;
	if(f.old != nil){
		if(f.old[0] == '/')
			oldfile = oldroot+f.old;
		else
			oldfile = f.old;
	}else
		oldfile = oldroot+f.new;
}

#
# skip all files in the proto that
# could be in the current dir
#
skipdir()
{
	if(indent < 0)
		return;
	level := indent;
	for(;;){
		indent = 0;
		fp := b.offset();
		p := b.gets('\n');
		lineno++;
		if(p == nil){
			indent = -1;
			return;
		}
		for(j := 0; (c := p[j++]) != '\n';)
			if(c == ' ')
				indent++;
			else if(c == '\t')
				indent += 8;
			else
				break;
		if(indent <= level){
			b.seek(fp, 0);
			lineno--;
			return;
		}
	}
}

getfile(old: ref File): (ref File, big)
{
	f: ref File;
	p, elem: string;
	c: int;

	if(indent < 0)
		return (nil, big 0);
	fp := b.offset();
	do {
		indent = 0;
		p = b.gets('\n');
		lineno++;
		if(p == nil){
			indent = -1;
			return (nil, big 0);
		}
		for(; (c = p[0]) != '\n'; p = p[1:])
			if(c == ' ')
				indent++;
			else if(c == '\t')
				indent += 8;
			else
				break;
	} while(c == '\n' || c == '#');
	f = ref File;
	(elem, p) = getname(p);
	if(debug)
		fprint(stderr, "getfile: %q root %q\n", elem, old.new);
	f.new = mkpath(old.new, elem);
	(nil, f.elem) = str->splitr(f.new, "/");
	if(f.elem == nil)
		error(sys->sprint("can't find file name component of %q", f.new));
	(f.mode, p) = getmode(p);
	(f.uid, p) = getname(p);
	if(f.uid == nil)
		f.uid = "-";
	(f.gid, p) = getname(p);
	if(f.gid == nil)
		f.gid = "-";
	f.old = getpath(p);
	if(f.old == "-")
		f.old = nil;
	setnames(f);

	if(debug)
		printfile(f);

	return (f, fp);
}

getpath(p: string): string
{
	for(i := 0; i < len p && (p[i] == ' ' || p[i] == '\t'); i++)
		;
	for(n := i; n < len p && (c := p[n]) != '\n' && c != ' ' && c != '\t'; n++)
		;
	return p[i:n];
}

getname(p: string): (string, string)
{
	for(i := 0; i < len p && (p[0] == ' ' || p[0] == '\t'); i++)
		;
	s := "";
	quoted := 0;
	for(; i < len p && (c := p[i]) != '\n' && (c != ' ' && c != '\t' || quoted); i++){
		if(c == '\''){
			if(i+1 >= len p || p[i+1] != '\''){
				quoted = !quoted;
				continue;
			}
			i++;
		}
		s[len s] = c;
	}
	if(len s > 0 && s[0] == '$'){
		s = getenv(s[1:]);
		if(s == nil)
			error(sys->sprint("can't read environment variable %q", s));
	}
	return (s, p[i:]);
}

getenv(s: string): string
{
	if(s == "user")
		return getuser();
	return readfile("/env/"+s);
}

getuser(): string
{
	return readfile("/dev/user");
}

readfile(f: string): string
{
	fd := sys->open(f, Sys->OREAD);
	if(fd != nil){
		a := array[256] of byte;
		n := sys->read(fd, a, len a);
		if(n > 0)
			return string a[0:n];
	}
	return nil;
}

getmode(p: string): (int, string)
{
	s: string;

	(s, p) = getname(p);
	if(s == nil || s == "-")
		return (~0, p);
	os := s;
	m := 0;
	if(s[0] == 'd'){
		m |= Sys->DMDIR;
		s = s[1:];
	}
	if(s[0] == 'a'){
		m |= Sys->DMAPPEND;
		s = s[1:];
	}
	if(s[0] == 'l'){
		m |= Sys->DMEXCL;
		s = s[1:];
	}

	for(i:=0; i<len s || i < 3; i++)
		if(i >= len s || !(s[i]>='0' && s[i]<='7')){
			warn(sys->sprint("bad mode specification %s", os));
			return (~0, p);
		}
	(v, nil) := str->toint(s, 8);
	return (m|v, p);
}

setusers()
{
	if(fskind != Kfs)
		return;
	file := ref File;
	m := modes;
	modes = 1;
	file.uid = "adm";
	file.gid = "adm";
	file.mode = Sys->DMDIR|8r775;
	file.new = "/adm";
	file.elem = "adm";
	file.old = nil;
	setnames(file);
	mkfile(file);
	file.new = "/adm/users";
	file.old = users;
	file.elem = "users";
	file.mode = 8r664;
	setnames(file);
	mkfile(file);
	kfscmd("user");
	mkfile(file);
	file.mode = Sys->DMDIR|8r775;
	file.new = "/adm";
	file.old = "/adm";
	file.elem = "adm";
	setnames(file);
	mkfile(file);
	modes = m;
}

openkfscmd(name: string)
{
	if(fskind != Kfs)
		return;
	kname := sys->sprint("/chan/kfs.%s.cmd", name);
	sfd = sys->open(kname, Sys->ORDWR);
	if(sfd == nil){
		fprint(stderr, "%s: can't open %q: %r\n", prog, kname);
		quit("open kfscmd");
	}
}

kfscmd(cmd: string)
{
	if(fskind != Kfs || sfd == nil)
		return;
	a := array of byte cmd;
	if(sys->write(sfd, a, len a) != len a){
		fprint(stderr, "%s: error writing %s: %r", prog, cmd);
		return;
	}
	for(;;){
		reply := array[4*1024] of byte;
		n := sys->read(sfd, reply, len reply);
		if(n <= 0)
			return;
		s := string reply[0:n];
		if(s == "done" || s == "success")
			return;
		if(s == "unknown command"){
			fprint(stderr, "%s: command %s not recognized\n", prog, cmd);
			return;
		}
	}
}

error(s: string)
{
	fprint(stderr, "%s: %s:%d: %s\n", prog, proto, lineno, s);
	kfscmd("disallow");
	kfscmd("sync");
	quit("error");
}

warn(s: string)
{
	fprint(stderr, "%s: %s:%d: %s\n", prog, proto, lineno, s);
}

printfile(f: ref File)
{
	if(f.old != nil)
		fprint(stderr, "%q from %q %q %q %uo\n", f.new, f.old, f.uid, f.gid, f.mode);
	else
		fprint(stderr, "%q %q %q %uo\n", f.new, f.uid, f.gid, f.mode);
}