disk/mkfs, disk/mkext: add from Plan 9

R=rsc, rsc
http://codereview.appspot.com/6405057
diff --git a/bin/disk/.placeholder b/bin/disk/.placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/bin/disk/.placeholder
diff --git a/man/man8/mkfs.8 b/man/man8/mkfs.8
new file mode 100644
index 0000000..2eaba6c
--- /dev/null
+++ b/man/man8/mkfs.8
@@ -0,0 +1,187 @@
+.TH MKFS 8
+.SH NAME
+mkfs, mkext \- archive or update a file system
+.SH SYNOPSIS
+.B disk/mkfs
+.RB [ -aprvxU ]
+.RB [ -d
+.IR root ]
+.RB [ -n
+.IR name ]
+.RB [ -s
+.IR source ]
+.RB [ -u
+.IR users ]
+.RB [ -z
+.IR n ]
+.I proto ...
+.PP
+.B disk/mkext
+.RB [ -d
+.IR name ]
+.RB [ -u ]
+.RB [ -h ]
+.RB [ -v ]
+.RB [ -x ]
+.RB [ -T ]
+.I file ...
+.SH DESCRIPTION
+.I Mkfs
+copies files from the file tree
+.I source
+(default
+.BR / )
+to a
+.B kfs
+file system (see
+.IR kfs (4)).
+The kfs service is mounted on
+.I root
+(default
+.BR /n/kfs ),
+and
+.B /adm/users
+is copied to
+.IB root /adm/users\f1.
+The
+.I proto
+files are read
+(see
+.IR proto (2)
+for their format)
+and any files specified in them that are out of date are copied to
+.BR /n/kfs .
+.PP
+.I Mkfs
+copies only those files that are out of date.
+Such a file is first copied into a temporary
+file in the appropriate destination directory
+and then moved to the destination file.
+Files in the
+.I kfs
+file system that are not specified in the
+.I proto
+file
+are not updated and not removed.
+.PP
+The options to
+.I mkfs
+are:
+.TF "s source"
+.TP
+.B a
+Instead of writing to a
+.B kfs
+file system, write an archive file to standard output, suitable for
+.IR mkext .
+All files in
+.IR proto ,
+not just those out of date, are archived.
+.TP
+.B x
+For use with
+.BR -a ,
+this option writes a list of file names, dates, and sizes to standard output
+rather than producing an archive file.
+.TP
+.BI "d " root
+Copy files into the tree rooted at
+.I root 
+(default
+.BR /n/kfs ).
+This option suppresses setting the
+.B uid
+and
+.B gid
+fields when copying files.
+Use
+.B -U
+to reenable it. 
+.TP
+.BI "n " name
+Use
+.RI kfs. name
+as the name of the kfs service (default
+.BR kfs ).
+.TP
+.B p
+Update the permissions of a file even if it is up to date.
+.TP
+.B r
+Copy all files.
+.TP
+.BI "s " source
+Copy from files rooted at the tree
+.IR source .
+.TP
+.BI "u " users
+Copy file
+.I users
+into
+.B /adm/users
+in the new system.
+.TP
+.B v
+Print the names of all of the files as they are copied.
+.TP
+.BI "z " n
+Copy files assuming kfs block
+.I n
+(default 1024)
+bytes long.
+If a block contains only 0-valued bytes, it is not copied.
+.PD
+.PP
+.I Mkext
+unpacks archive files made by the
+.B -a
+option of
+.IR mkfs .
+Each file on the command line is unpacked in one pass through the archive.
+If the file is a directory,
+all files and subdirectories of that directory are also unpacked.
+When a file is unpacked, the entire path is created if it
+does not exist.
+If no files are specified, the entire archive is unpacked;
+in this case, missing intermediate directories are not created.
+The options are:
+.TP
+.B d
+specifies a directory (default
+.BR / )
+to serve as the root of the unpacked file system.
+.TP
+.B u
+sets the owners of the files created to correspond to
+those in the archive and restores the modification times of the files.
+.TP
+.B T
+restores only the modification times of the files.
+.TP
+.B v
+prints the names and sizes of files as they are extracted.
+.TP
+.B h
+prints headers for the files on standard output
+instead of unpacking the files.
+.PD
+.SH EXAMPLES
+.PP
+Make an archive to establish a new file system:
+.IP
+.EX
+disk/mkfs -a -u files/adm.users -s dist proto > arch
+.EE
+.PP
+Unpack that archive onto a new file system:
+.IP
+.EX
+disk/mkext -u -d /n/newfs < arch
+.EE
+.SH SOURCE
+.B \*9/src/cmd/disk/mkfs.c
+.br
+.B \*9/src/cmd/disk/mkext.c
+.SH "SEE ALSO"
+.IR prep (8),
+.IR tar (1)
diff --git a/src/cmd/disk/mkext.c b/src/cmd/disk/mkext.c
new file mode 100644
index 0000000..05190d0
--- /dev/null
+++ b/src/cmd/disk/mkext.c
@@ -0,0 +1,318 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+enum{
+	LEN	= 8*1024,
+	NFLDS	= 6,		/* filename, modes, uid, gid, mtime, bytes */
+};
+
+int	selected(char*, int, char*[]);
+void	mkdirs(char*, char*);
+void	mkdir(char*, ulong, ulong, char*, char*);
+void	extract(char*, ulong, ulong, char*, char*, uvlong);
+void	seekpast(uvlong);
+void	error(char*, ...);
+void	warn(char*, ...);
+void	usage(void);
+#pragma varargck argpos warn 1
+#pragma varargck argpos error 1
+
+Biobuf	bin;
+uchar	binbuf[2*LEN];
+char	linebuf[LEN];
+int	uflag;
+int	hflag;
+int	vflag;
+int	Tflag;
+
+void
+main(int argc, char **argv)
+{
+	Biobuf bout;
+	char *fields[NFLDS], name[2*LEN], *p, *namep;
+	char *uid, *gid;
+	ulong mode, mtime;
+	uvlong bytes;
+
+	quotefmtinstall();
+	namep = name;
+	ARGBEGIN{
+	case 'd':
+		p = ARGF();
+		if(strlen(p) >= LEN)
+			error("destination fs name too long\n");
+		strcpy(name, p);
+		namep = name + strlen(name);
+		break;
+	case 'h':
+		hflag = 1;
+		Binit(&bout, 1, OWRITE);
+		break;
+	case 'u':
+		uflag = 1;
+		Tflag = 1;
+		break;
+	case 'T':
+		Tflag = 1;
+		break;
+	case 'v':
+		vflag = 1;
+		break;
+	default:
+		usage();
+	}ARGEND
+	
+	Binits(&bin, 0, OREAD, binbuf, sizeof binbuf);
+	while(p = Brdline(&bin, '\n')){
+		p[Blinelen(&bin)-1] = '\0';
+		strcpy(linebuf, p);
+		p = linebuf;
+		if(strcmp(p, "end of archive") == 0){
+			Bterm(&bout);
+			fprint(2, "done\n");
+			exits(0);
+		}
+		if (gettokens(p, fields, NFLDS, " \t") != NFLDS){
+			warn("too few fields in file header");
+			continue;
+		}
+		p = unquotestrdup(fields[0]);
+		strcpy(namep, p);
+		free(p);
+		mode = strtoul(fields[1], 0, 8);
+		uid = fields[2];
+		gid = fields[3];
+		mtime = strtoul(fields[4], 0, 10);
+		bytes = strtoull(fields[5], 0, 10);
+		if(argc){
+			if(!selected(namep, argc, argv)){
+				if(bytes)
+					seekpast(bytes);
+				continue;
+			}
+			mkdirs(name, namep);
+		}
+		if(hflag){
+			Bprint(&bout, "%q %luo %q %q %lud %llud\n",
+				name, mode, uid, gid, mtime, bytes);
+			if(bytes)
+				seekpast(bytes);
+			continue;
+		}
+		if(mode & DMDIR)
+			mkdir(name, mode, mtime, uid, gid);
+		else
+			extract(name, mode, mtime, uid, gid, bytes);
+	}
+	fprint(2, "premature end of archive\n");
+	exits("premature end of archive");
+}
+
+int
+fileprefix(char *prefix, char *s)
+{
+	while(*prefix)
+		if(*prefix++ != *s++)
+			return 0;
+	if(*s && *s != '/')
+		return 0;
+	return 1;
+}
+
+int
+selected(char *s, int argc, char *argv[])
+{
+	int i;
+
+	for(i=0; i<argc; i++)
+		if(fileprefix(argv[i], s))
+			return 1;
+	return 0;
+}
+
+void
+mkdirs(char *name, char *namep)
+{
+	char buf[2*LEN], *p;
+	int fd;
+
+	strcpy(buf, name);
+	for(p = &buf[namep - name]; p = utfrune(p, '/'); p++){
+		if(p[1] == '\0')
+			return;
+		*p = 0;
+		fd = create(buf, OREAD, 0775|DMDIR);
+		close(fd);
+		*p = '/';
+	}
+}
+
+void
+mkdir(char *name, ulong mode, ulong mtime, char *uid, char *gid)
+{
+	Dir *d, xd;
+	int fd;
+	char *p;
+	char olderr[256];
+
+	fd = create(name, OREAD, mode);
+	if(fd < 0){
+		rerrstr(olderr, sizeof(olderr));
+		if((d = dirstat(name)) == nil || !(d->mode & DMDIR)){
+			free(d);
+			warn("can't make directory %q, mode %luo: %s", name, mode, olderr);
+			return;
+		}
+		free(d);
+	}
+	close(fd);
+
+	d = &xd;
+	nulldir(d);
+	p = utfrrune(name, L'/');
+	if(p)
+		p++;
+	else
+		p = name;
+	d->name = p;
+	if(uflag){
+		d->uid = uid;
+		d->gid = gid;
+	}
+	if(Tflag)
+		d->mtime = mtime;
+	d->mode = mode;
+	if(dirwstat(name, d) < 0)
+		warn("can't set modes for %q: %r", name);
+
+	if(uflag||Tflag){
+		if((d = dirstat(name)) == nil){
+			warn("can't reread modes for %q: %r", name);
+			return;
+		}
+		if(Tflag && d->mtime != mtime)
+			warn("%q: time mismatch %lud %lud\n", name, mtime, d->mtime);
+		if(uflag && strcmp(uid, d->uid))
+			warn("%q: uid mismatch %q %q", name, uid, d->uid);
+		if(uflag && strcmp(gid, d->gid))
+			warn("%q: gid mismatch %q %q", name, gid, d->gid);
+	}
+}
+
+void
+extract(char *name, ulong mode, ulong mtime, char *uid, char *gid, uvlong bytes)
+{
+	Dir d, *nd;
+	Biobuf *b;
+	char buf[LEN];
+	char *p;
+	ulong n;
+	uvlong tot;
+
+	if(vflag)
+		print("x %q %llud bytes\n", name, bytes);
+
+	b = Bopen(name, OWRITE);
+	if(!b){
+		warn("can't make file %q: %r", name);
+		seekpast(bytes);
+		return;
+	}
+	for(tot = 0; tot < bytes; tot += n){
+		n = sizeof buf;
+		if(tot + n > bytes)
+			n = bytes - tot;
+		n = Bread(&bin, buf, n);
+		if(n <= 0)
+			error("premature eof reading %q", name);
+		if(Bwrite(b, buf, n) != n)
+			warn("error writing %q: %r", name);
+	}
+
+	nulldir(&d);
+	p = utfrrune(name, '/');
+	if(p)
+		p++;
+	else
+		p = name;
+	d.name = p;
+	if(uflag){
+		d.uid = uid;
+		d.gid = gid;
+	}
+	if(Tflag)
+		d.mtime = mtime;
+	d.mode = mode;
+	Bflush(b);
+	if(dirfwstat(Bfildes(b), &d) < 0)
+		warn("can't set modes for %q: %r", name);
+	if(uflag||Tflag){
+		if((nd = dirfstat(Bfildes(b))) == nil)
+			warn("can't reread modes for %q: %r", name);
+		else{
+			if(Tflag && nd->mtime != mtime)
+				warn("%q: time mismatch %lud %lud\n",
+					name, mtime, nd->mtime);
+			if(uflag && strcmp(uid, nd->uid))
+				warn("%q: uid mismatch %q %q",
+					name, uid, nd->uid);
+			if(uflag && strcmp(gid, nd->gid))
+				warn("%q: gid mismatch %q %q",
+					name, gid, nd->gid);
+			free(nd);
+		}
+	}
+	Bterm(b);
+}
+
+void
+seekpast(uvlong bytes)
+{
+	char buf[LEN];
+	long n;
+	uvlong tot;
+
+	for(tot = 0; tot < bytes; tot += n){
+		n = sizeof buf;
+		if(tot + n > bytes)
+			n = bytes - tot;
+		n = Bread(&bin, buf, n);
+		if(n < 0)
+			error("premature eof");
+	}
+}
+
+void
+error(char *fmt, ...)
+{
+	char buf[1024];
+	va_list arg;
+
+	sprint(buf, "%q: ", argv0);
+	va_start(arg, fmt);
+	vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	fprint(2, "%s\n", buf);
+	exits(0);
+}
+
+void
+warn(char *fmt, ...)
+{
+	char buf[1024];
+	va_list arg;
+
+	sprint(buf, "%q: ", argv0);
+	va_start(arg, fmt);
+	vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	fprint(2, "%s\n", buf);
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: mkext [-h] [-u] [-v] [-d dest-fs] [file ...]\n");
+	exits("usage");
+}
diff --git a/src/cmd/disk/mkfile b/src/cmd/disk/mkfile
new file mode 100644
index 0000000..d702a89
--- /dev/null
+++ b/src/cmd/disk/mkfile
@@ -0,0 +1,11 @@
+<$PLAN9/src/mkhdr
+
+TARG=\
+	mkext\
+	mkfs\
+
+BIN=$BIN/disk
+
+<$PLAN9/src/mkmany
+
+
diff --git a/src/cmd/disk/mkfs.c b/src/cmd/disk/mkfs.c
new file mode 100644
index 0000000..1e70ab7
--- /dev/null
+++ b/src/cmd/disk/mkfs.c
@@ -0,0 +1,840 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <bio.h>
+
+#define getmode plan9_getmode
+#define setuid plan9_setuid
+
+enum{
+	LEN	= 8*1024,
+	HUNKS	= 128,
+
+	/*
+	 * types of destination file sytems
+	 */
+	Kfs = 0,
+	Fs,
+	Archive,
+};
+
+typedef struct File	File;
+
+struct File{
+	char	*new;
+	char	*elem;
+	char	*old;
+	char	*uid;
+	char	*gid;
+	ulong	mode;
+};
+
+void	arch(Dir*);
+void	copy(Dir*);
+int	copyfile(File*, Dir*, int);
+void*	emalloc(ulong);
+void	error(char *, ...);
+void	freefile(File*);
+File*	getfile(File*);
+char*	getmode(char*, ulong*);
+char*	getname(char*, char**);
+char*	getpath(char*);
+void	kfscmd(char *);
+void	mkdir(Dir*);
+int	mkfile(File*);
+void	mkfs(File*, int);
+char*	mkpath(char*, char*);
+void	mktree(File*, int);
+void	mountkfs(char*);
+void	printfile(File*);
+void	setnames(File*);
+void	setusers(void);
+void	skipdir(void);
+char*	strdup(char*);
+int	uptodate(Dir*, char*);
+void	usage(void);
+void	warn(char *, ...);
+
+Biobuf	*b;
+Biobuf	bout;			/* stdout when writing archive */
+uchar	boutbuf[2*LEN];
+char	newfile[LEN];
+char	oldfile[LEN];
+char	*proto;
+char	*cputype;
+char	*users;
+char	*oldroot;
+char	*newroot;
+char	*prog = "mkfs";
+int	lineno;
+char	*buf;
+char	*zbuf;
+int	buflen = 1024-8;
+int	indent;
+int	verb;
+int	modes;
+int	ream;
+int	debug;
+int	xflag;
+int	sfd;
+int	fskind;			/* Kfs, Fs, Archive */
+int	setuid;			/* on Fs: set uid and gid? */
+char	*user;
+
+void
+main(int argc, char **argv)
+{
+	File file;
+	char *name;
+	int i, errs;
+
+	quotefmtinstall();
+	user = getuser();
+	name = "";
+	memset(&file, 0, sizeof file);
+	file.new = "";
+	file.old = 0;
+	oldroot = "";
+	newroot = "/n/kfs";
+	users = 0;
+	fskind = Kfs;
+	ARGBEGIN{
+	case 'a':
+		if(fskind != Kfs) {
+			fprint(2, "cannot use -a with -d\n");
+			usage();
+		}
+		fskind = Archive;
+		newroot = "";
+		Binits(&bout, 1, OWRITE, boutbuf, sizeof boutbuf);
+		break;
+	case 'd':
+		if(fskind != Kfs) {
+			fprint(2, "cannot use -d with -a\n");
+			usage();
+		}
+		fskind = Fs;
+		newroot = ARGF();
+		break;
+	case 'D':
+		debug = 1;
+		break;
+	case 'n':
+		name = EARGF(usage());
+		break;
+	case 'p':
+		modes = 1;
+		break;
+	case 'r':
+		ream = 1;
+		break;
+	case 's':
+		oldroot = ARGF();
+		break;
+	case 'u':
+		users = ARGF();
+		break;
+	case 'U':
+		setuid = 1;
+		break;
+	case 'v':
+		verb = 1;
+		break;
+	case 'x':
+		xflag = 1;
+		break;
+	case 'z':
+		buflen = atoi(ARGF())-8;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(!argc)	
+		usage();
+
+	buf = emalloc(buflen);
+	zbuf = emalloc(buflen);
+	memset(zbuf, 0, buflen);
+
+	mountkfs(name);
+	kfscmd("allow");
+	proto = "users";
+	setusers();
+	cputype = getenv("cputype");
+	if(cputype == 0)
+		cputype = "68020";
+
+	errs = 0;
+	for(i = 0; i < argc; i++){
+		proto = argv[i];
+		fprint(2, "processing %q\n", proto);
+
+		b = Bopen(proto, OREAD);
+		if(!b){
+			fprint(2, "%q: can't open %q: skipping\n", prog, proto);
+			errs++;
+			continue;
+		}
+
+		lineno = 0;
+		indent = 0;
+		mkfs(&file, -1);
+		Bterm(b);
+	}
+	fprint(2, "file system made\n");
+	kfscmd("disallow");
+	kfscmd("sync");
+	if(errs)
+		exits("skipped protos");
+	if(fskind == Archive){
+		Bprint(&bout, "end of archive\n");
+		Bterm(&bout);
+	}
+	exits(0);
+}
+
+void
+mkfs(File *me, int level)
+{
+	File *child;
+	int rec;
+
+	child = getfile(me);
+	if(!child)
+		return;
+	if((child->elem[0] == '+' || child->elem[0] == '*') && child->elem[1] == '\0'){
+		rec = child->elem[0] == '+';
+		free(child->new);
+		child->new = strdup(me->new);
+		setnames(child);
+		mktree(child, rec);
+		freefile(child);
+		child = getfile(me);
+	}
+	while(child && indent > level){
+		if(mkfile(child))
+			mkfs(child, indent);
+		freefile(child);
+		child = getfile(me);
+	}
+	if(child){
+		freefile(child);
+		Bseek(b, -Blinelen(b), 1);
+		lineno--;
+	}
+}
+
+void
+mktree(File *me, int rec)
+{
+	File child;
+	Dir *d;
+	int i, n, fd;
+
+	fd = open(oldfile, OREAD);
+	if(fd < 0){
+		warn("can't open %q: %r", oldfile);
+		return;
+	}
+
+	child = *me;
+	while((n = dirread(fd, &d)) > 0){
+		for(i = 0; i < n; i++){
+			child.new = mkpath(me->new, d[i].name);
+			if(me->old)
+				child.old = mkpath(me->old, d[i].name);
+			child.elem = d[i].name;
+			setnames(&child);
+			if(copyfile(&child, &d[i], 1) && rec)
+				mktree(&child, rec);
+			free(child.new);
+			if(child.old)
+				free(child.old);
+		}
+	}
+	close(fd);
+}
+
+int
+mkfile(File *f)
+{
+	Dir *dir;
+
+	if((dir = dirstat(oldfile)) == nil){
+		warn("can't stat file %q: %r", oldfile);
+		skipdir();
+		return 0;
+	}
+	return copyfile(f, dir, 0);
+}
+
+int
+copyfile(File *f, Dir *d, int permonly)
+{
+	ulong mode;
+	Dir nd;
+
+	if(xflag){
+		Bprint(&bout, "%q\t%ld\t%lld\n", f->new, d->mtime, d->length);
+		return (d->mode & DMDIR) != 0;
+	}
+	if(verb && (fskind == Archive || ream))
+		fprint(2, "%q\n", f->new);
+	d->name = f->elem;
+	if(d->type != 'M' && d->type != 'Z'){
+		d->uid = "sys";
+		d->gid = "sys";
+		mode = (d->mode >> 6) & 7;
+		d->mode |= mode | (mode << 3);
+	}
+	if(strcmp(f->uid, "-") != 0)
+		d->uid = f->uid;
+	if(strcmp(f->gid, "-") != 0)
+		d->gid = f->gid;
+	if(fskind == Fs && !setuid){
+		d->uid = "";
+		d->gid = "";
+	}
+	if(f->mode != ~0){
+		if(permonly)
+			d->mode = (d->mode & ~0666) | (f->mode & 0666);
+		else if((d->mode&DMDIR) != (f->mode&DMDIR))
+			warn("inconsistent mode for %q", f->new);
+		else
+			d->mode = f->mode;
+	}
+	if(!uptodate(d, newfile)){
+		if(verb && (fskind != Archive && ream == 0))
+			fprint(2, "%q\n", f->new);
+		if(d->mode & DMDIR)
+			mkdir(d);
+		else
+			copy(d);
+	}else if(modes){
+		nulldir(&nd);
+		nd.mode = d->mode;
+		nd.gid = d->gid;
+		nd.mtime = d->mtime;
+		if(verb && (fskind != Archive && ream == 0))
+			fprint(2, "%q\n", f->new);
+		if(dirwstat(newfile, &nd) < 0)
+			warn("can't set modes for %q: %r", f->new);
+		nulldir(&nd);
+		nd.uid = d->uid;
+		dirwstat(newfile, &nd);
+	}
+	return (d->mode & DMDIR) != 0;
+}
+
+/*
+ * check if file to is up to date with
+ * respect to the file represented by df
+ */
+int
+uptodate(Dir *df, char *to)
+{
+	int ret;
+	Dir *dt;
+
+	if(fskind == Archive || ream || (dt = dirstat(to)) == nil)
+		return 0;
+	ret = dt->mtime >= df->mtime;
+	free(dt);
+	return ret;
+}
+
+void
+copy(Dir *d)
+{
+	char cptmp[LEN], *p;
+	int f, t, n, needwrite, nowarnyet = 1;
+	vlong tot, len;
+	Dir nd;
+
+	f = open(oldfile, OREAD);
+	if(f < 0){
+		warn("can't open %q: %r", oldfile);
+		return;
+	}
+	t = -1;
+	if(fskind == Archive)
+		arch(d);
+	else{
+		strcpy(cptmp, newfile);
+		p = utfrrune(cptmp, L'/');
+		if(!p)
+			error("internal temporary file error");
+		strcpy(p+1, "__mkfstmp");
+		t = create(cptmp, OWRITE, 0666);
+		if(t < 0){
+			warn("can't create %q: %r", newfile);
+			close(f);
+			return;
+		}
+	}
+
+	needwrite = 0;
+	for(tot = 0; tot < d->length; tot += n){
+		len = d->length - tot;
+		/* don't read beyond d->length */
+		if (len > buflen)
+			len = buflen;
+		n = read(f, buf, len);
+		if(n <= 0) {
+			if(n < 0 && nowarnyet) {
+				warn("can't read %q: %r", oldfile);
+				nowarnyet = 0;
+			}
+			/*
+			 * don't quit: pad to d->length (in pieces) to agree
+			 * with the length in the header, already emitted.
+			 */
+			memset(buf, 0, len);
+			n = len;
+		}
+		if(fskind == Archive){
+			if(Bwrite(&bout, buf, n) != n)
+				error("write error: %r");
+		}else if(memcmp(buf, zbuf, n) == 0){
+			if(seek(t, n, 1) < 0)
+				error("can't write zeros to %q: %r", newfile);
+			needwrite = 1;
+		}else{
+			if(write(t, buf, n) < n)
+				error("can't write %q: %r", newfile);
+			needwrite = 0;
+		}
+	}
+	close(f);
+	if(needwrite){
+		if(seek(t, -1, 1) < 0 || write(t, zbuf, 1) != 1)
+			error("can't write zero at end of %q: %r", newfile);
+	}
+	if(tot != d->length){
+		/* this should no longer happen */
+		warn("wrong number of bytes written to %q (was %lld should be %lld)\n",
+			newfile, tot, d->length);
+		if(fskind == Archive){
+			warn("seeking to proper position\n");
+			/* does no good if stdout is a pipe */
+			Bseek(&bout, d->length - tot, 1);
+		}
+	}
+	if(fskind == Archive)
+		return;
+	remove(newfile);
+	nulldir(&nd);
+	nd.mode = d->mode;
+	nd.gid = d->gid;
+	nd.mtime = d->mtime;
+	nd.name = d->name;
+	if(dirfwstat(t, &nd) < 0)
+		error("can't move tmp file to %q: %r", newfile);
+	nulldir(&nd);
+	nd.uid = d->uid;
+	dirfwstat(t, &nd);
+	close(t);
+}
+
+void
+mkdir(Dir *d)
+{
+	Dir *d1;
+	Dir nd;
+	int fd;
+
+	if(fskind == Archive){
+		arch(d);
+		return;
+	}
+	fd = create(newfile, OREAD, d->mode);
+	nulldir(&nd);
+	nd.mode = d->mode;
+	nd.gid = d->gid;
+	nd.mtime = d->mtime;
+	if(fd < 0){
+		if((d1 = dirstat(newfile)) == nil || !(d1->mode & DMDIR)){
+			free(d1);
+			error("can't create %q", newfile);
+		}
+		free(d1);
+		if(dirwstat(newfile, &nd) < 0)
+			warn("can't set modes for %q: %r", newfile);
+		nulldir(&nd);
+		nd.uid = d->uid;
+		dirwstat(newfile, &nd);
+		return;
+	}
+	if(dirfwstat(fd, &nd) < 0)
+		warn("can't set modes for %q: %r", newfile);
+	nulldir(&nd);
+	nd.uid = d->uid;
+	dirfwstat(fd, &nd);
+	close(fd);
+}
+
+void
+arch(Dir *d)
+{
+	Bprint(&bout, "%q %luo %q %q %lud %lld\n",
+		newfile, d->mode, d->uid, d->gid, d->mtime, d->length);
+}
+
+char *
+mkpath(char *prefix, char *elem)
+{
+	char *p;
+	int n;
+
+	n = strlen(prefix) + strlen(elem) + 2;
+	p = emalloc(n);
+	sprint(p, "%s/%s", prefix, elem);
+	return p;
+}
+
+char *
+strdup(char *s)
+{
+	char *t;
+
+	t = emalloc(strlen(s) + 1);
+	return strcpy(t, s);
+}
+
+void
+setnames(File *f)
+{
+	sprint(newfile, "%s%s", newroot, f->new);
+	if(f->old){
+		if(f->old[0] == '/')
+			sprint(oldfile, "%s%s", oldroot, f->old);
+		else
+			strcpy(oldfile, f->old);
+	}else
+		sprint(oldfile, "%s%s", oldroot, f->new);
+	if(strlen(newfile) >= sizeof newfile 
+	|| strlen(oldfile) >= sizeof oldfile)
+		error("name overfile");
+}
+
+void
+freefile(File *f)
+{
+	if(f->old)
+		free(f->old);
+	if(f->new)
+		free(f->new);
+	free(f);
+}
+
+/*
+ * skip all files in the proto that
+ * could be in the current dir
+ */
+void
+skipdir(void)
+{
+	char *p, c;
+	int level;
+
+	if(indent < 0 || b == nil)	/* b is nil when copying adm/users */
+		return;
+	level = indent;
+	for(;;){
+		indent = 0;
+		p = Brdline(b, '\n');
+		lineno++;
+		if(!p){
+			indent = -1;
+			return;
+		}
+		while((c = *p++) != '\n')
+			if(c == ' ')
+				indent++;
+			else if(c == '\t')
+				indent += 8;
+			else
+				break;
+		if(indent <= level){
+			Bseek(b, -Blinelen(b), 1);
+			lineno--;
+			return;
+		}
+	}
+}
+
+File*
+getfile(File *old)
+{
+	File *f;
+	char *elem;
+	char *p;
+	int c;
+
+	if(indent < 0)
+		return 0;
+loop:
+	indent = 0;
+	p = Brdline(b, '\n');
+	lineno++;
+	if(!p){
+		indent = -1;
+		return 0;
+	}
+	while((c = *p++) != '\n')
+		if(c == ' ')
+			indent++;
+		else if(c == '\t')
+			indent += 8;
+		else
+			break;
+	if(c == '\n' || c == '#')
+		goto loop;
+	p--;
+	f = emalloc(sizeof *f);
+	p = getname(p, &elem);
+	if(debug)
+		fprint(2, "getfile: %q root %q\n", elem, old->new);
+	f->new = mkpath(old->new, elem);
+	f->elem = utfrrune(f->new, L'/') + 1;
+	p = getmode(p, &f->mode);
+	p = getname(p, &f->uid);
+	if(!*f->uid)
+		f->uid = "-";
+	p = getname(p, &f->gid);
+	if(!*f->gid)
+		f->gid = "-";
+	f->old = getpath(p);
+	if(f->old && strcmp(f->old, "-") == 0){
+		free(f->old);
+		f->old = 0;
+	}
+	setnames(f);
+
+	if(debug)
+		printfile(f);
+
+	return f;
+}
+
+char*
+getpath(char *p)
+{
+	char *q, *new;
+	int c, n;
+
+	while((c = *p) == ' ' || c == '\t')
+		p++;
+	q = p;
+	while((c = *q) != '\n' && c != ' ' && c != '\t')
+		q++;
+	if(q == p)
+		return 0;
+	n = q - p;
+	new = emalloc(n + 1);
+	memcpy(new, p, n);
+	new[n] = 0;
+	return new;
+}
+
+char*
+getname(char *p, char **buf)
+{
+	char *s, *start;
+	int c;
+
+	while((c = *p) == ' ' || c == '\t')
+		p++;
+
+	start = p;
+	while((c = *p) != '\n' && c != ' ' && c != '\t' && c != '\0')
+		p++;
+
+	*buf = malloc(p+1-start);
+	if(*buf == nil)
+		return nil;
+	memmove(*buf, start, p-start);
+	(*buf)[p-start] = '\0';
+
+	if(**buf == '$'){
+		s = getenv(*buf+1);
+		if(s == 0){
+			warn("can't read environment variable %q", *buf+1);
+			skipdir();
+			free(*buf);
+			return nil;
+		}
+		free(*buf);
+		*buf = s;
+	}
+	return p;
+}
+
+char*
+getmode(char *p, ulong *xmode)
+{
+	char *buf, *s;
+	ulong m;
+
+	*xmode = ~0;
+	p = getname(p, &buf);
+	if(p == nil)
+		return nil;
+
+	s = buf;
+	if(!*s || strcmp(s, "-") == 0)
+		return p;
+	m = 0;
+	if(*s == 'd'){
+		m |= DMDIR;
+		s++;
+	}
+	if(*s == 'a'){
+		m |= DMAPPEND;
+		s++;
+	}
+	if(*s == 'l'){
+		m |= DMEXCL;
+		s++;
+	}
+	if(s[0] < '0' || s[0] > '7'
+	|| s[1] < '0' || s[1] > '7'
+	|| s[2] < '0' || s[2] > '7'
+	|| s[3]){
+		warn("bad mode specification %q", buf);
+		free(buf);
+		return p;
+	}
+	*xmode = m | strtoul(s, 0, 8);
+	free(buf);
+	return p;
+}
+
+void
+setusers(void)
+{
+	File file;
+	int m;
+
+	if(fskind != Kfs)
+		return;
+	m = modes;
+	modes = 1;
+	file.uid = "adm";
+	file.gid = "adm";
+	file.mode = DMDIR|0775;
+	file.new = "/adm";
+	file.elem = "adm";
+	file.old = 0;
+	setnames(&file);
+	strcpy(oldfile, file.new);	/* Don't use root for /adm */
+	mkfile(&file);
+	file.new = "/adm/users";
+	file.old = users;
+	file.elem = "users";
+	file.mode = 0664;
+	setnames(&file);
+	if (file.old)
+		strcpy(oldfile, file.old);	/* Don't use root for /adm/users */
+	mkfile(&file);
+	kfscmd("user");
+	mkfile(&file);
+	file.mode = DMDIR|0775;
+	file.new = "/adm";
+	file.old = "/adm";
+	file.elem = "adm";
+	setnames(&file);
+	strcpy(oldfile, file.old);	/* Don't use root for /adm */
+	mkfile(&file);
+	modes = m;
+}
+
+void
+mountkfs(char *name)
+{
+	if(fskind != Kfs)
+		return;
+	sysfatal("no kfs: use -a or -d");
+}
+
+void
+kfscmd(char *cmd)
+{
+	char buf[4*1024];
+	int n;
+
+	if(fskind != Kfs)
+		return;
+	if(write(sfd, cmd, strlen(cmd)) != strlen(cmd)){
+		fprint(2, "%q: error writing %q: %r", prog, cmd);
+		return;
+	}
+	for(;;){
+		n = read(sfd, buf, sizeof buf - 1);
+		if(n <= 0)
+			return;
+		buf[n] = '\0';
+		if(strcmp(buf, "done") == 0 || strcmp(buf, "success") == 0)
+			return;
+		if(strcmp(buf, "unknown command") == 0){
+			fprint(2, "%q: command %q not recognized\n", prog, cmd);
+			return;
+		}
+	}
+}
+
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	if((p = malloc(n)) == 0)
+		error("out of memory");
+	return p;
+}
+
+void
+error(char *fmt, ...)
+{
+	char buf[1024];
+	va_list arg;
+
+	sprint(buf, "%q: %q:%d: ", prog, proto, lineno);
+	va_start(arg, fmt);
+	vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	fprint(2, "%s\n", buf);
+	kfscmd("disallow");
+	kfscmd("sync");
+	exits(0);
+}
+
+void
+warn(char *fmt, ...)
+{
+	char buf[1024];
+	va_list arg;
+
+	sprint(buf, "%q: %q:%d: ", prog, proto, lineno);
+	va_start(arg, fmt);
+	vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	fprint(2, "%s\n", buf);
+}
+
+void
+printfile(File *f)
+{
+	if(f->old)
+		fprint(2, "%q from %q %q %q %lo\n", f->new, f->old, f->uid, f->gid, f->mode);
+	else
+		fprint(2, "%q %q %q %lo\n", f->new, f->uid, f->gid, f->mode);
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %q [-aprvx] [-d root] [-n name] [-s source] [-u users] [-z n] proto ...\n", prog);
+	exits("usage");
+}