commit d2173bb552d308d60a4e4a53cd3b8e0949b38dbc from: Russ Cox date: Tue Jul 17 23:10:45 2012 UTC disk/mkfs, disk/mkext: add from Plan 9 R=rsc, rsc http://codereview.appspot.com/6405057 commit - f0add8ef24f83cb2085ef1c7534d16c57881e3f3 commit + d2173bb552d308d60a4e4a53cd3b8e0949b38dbc blob - /dev/null blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644) blob - /dev/null blob + 2eaba6c1cb79c8f5419ce2d75890e852b5b190d5 (mode 644) --- /dev/null +++ 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) blob - /dev/null blob + 05190d0470fe7f6ac59433ed7a21c3bbb2fbaeb2 (mode 644) --- /dev/null +++ src/cmd/disk/mkext.c @@ -0,0 +1,318 @@ +#include +#include +#include + +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; imode & 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"); +} blob - /dev/null blob + d702a8906d3b3920a295926b55454188e55fb88a (mode 644) --- /dev/null +++ src/cmd/disk/mkfile @@ -0,0 +1,11 @@ +<$PLAN9/src/mkhdr + +TARG=\ + mkext\ + mkfs\ + +BIN=$BIN/disk + +<$PLAN9/src/mkmany + + blob - /dev/null blob + 1e70ab76069ecfb8e95db1851708ae3cc4091952 (mode 644) --- /dev/null +++ src/cmd/disk/mkfs.c @@ -0,0 +1,840 @@ +#include +#include +#include +#include + +#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"); +}