tdisk/mkfs, disk/mkext: add from Plan 9 - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit d2173bb552d308d60a4e4a53cd3b8e0949b38dbc
 (DIR) parent f0add8ef24f83cb2085ef1c7534d16c57881e3f3
 (HTM) Author: Russ Cox <rsc@swtch.com>
       Date:   Tue, 17 Jul 2012 19:10:45 -0400
       
       disk/mkfs, disk/mkext: add from Plan 9
       
       R=rsc, rsc
       http://codereview.appspot.com/6405057
       
       Diffstat:
         A bin/disk/.placeholder               |       0 
         A man/man8/mkfs.8                     |     187 +++++++++++++++++++++++++++++++
         A src/cmd/disk/mkext.c                |     318 +++++++++++++++++++++++++++++++
         A src/cmd/disk/mkfile                 |      11 +++++++++++
         A src/cmd/disk/mkfs.c                 |     840 +++++++++++++++++++++++++++++++
       
       5 files changed, 1356 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/bin/disk/.placeholder b/bin/disk/.placeholder
 (DIR) diff --git a/man/man8/mkfs.8 b/man/man8/mkfs.8
       t@@ -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)
 (DIR) diff --git a/src/cmd/disk/mkext.c b/src/cmd/disk/mkext.c
       t@@ -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");
       +}
 (DIR) diff --git a/src/cmd/disk/mkfile b/src/cmd/disk/mkfile
       t@@ -0,0 +1,11 @@
       +<$PLAN9/src/mkhdr
       +
       +TARG=\
       +        mkext\
       +        mkfs\
       +
       +BIN=$BIN/disk
       +
       +<$PLAN9/src/mkmany
       +
       +
 (DIR) diff --git a/src/cmd/disk/mkfs.c b/src/cmd/disk/mkfs.c
       t@@ -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");
       +}