URI:
       tchanges - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 7d59ed711467b2b2c8ac25f86724225c8be7524c
   DIR parent cf7bdb33d49aef8a4add73ee15f631f3fc0203d6
  HTML Author: rsc <devnull@localhost>
       Date:   Fri, 11 Feb 2005 19:51:21 +0000
       
       changes
       
       Diffstat:
         M CHANGES                             |       6 +++++-
         A log/.cvsignore                      |       1 +
         A src/cmd/netkey.c                    |      47 +++++++++++++++++++++++++++++++
         M src/cmd/tar.C                       |    1289 ++++++++++++++++++-------------
       
       4 files changed, 808 insertions(+), 535 deletions(-)
       ---
   DIR diff --git a/CHANGES b/CHANGES
       t@@ -9,11 +9,13 @@ February 11, 2005
                lib9pclient: add chatty9pclient
                libauth: add fsamount, nsamount, authdial ndb.
                libmach: use %#x explicitly
       +        libndb: add
                libsec: use new auth_allocrpc in tlshand
                libthread: add threadgetname prototype
                9l: fix libsec/lib9 cycle thanks to netcrypt
       -        9p: use nsamount, fsamount
       +        9p: use nsamount, fsamount; add write -l
                9pserve: quieter, auth bug fix
       +        9term: treat _ as word character
                acid: do not set %# implicitly anymore
                        add simple pthread support via acid code
                        set corpid
       t@@ -21,8 +23,10 @@ February 11, 2005
                dial: send input to net instead of back to 0
                dict: ahd path
                factotum: make it run and work, add secstore
       +                remove top-level factotum directory
                rc: set p->pid=-1 explicitly to avoid wait problems
                psv: add print buttons
       +        secstored: add it
        
        February 10, 2005
                libmach: abortive attempt at pthread support via libthread_db
   DIR diff --git a/log/.cvsignore b/log/.cvsignore
       t@@ -0,0 +1 @@
       +
   DIR diff --git a/src/cmd/netkey.c b/src/cmd/netkey.c
       t@@ -0,0 +1,47 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <libsec.h>
       +#include <authsrv.h>
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: netkey\n");
       +        exits("usage");
       +}
       +
       +void
       +main(int argc, char *argv[])
       +{
       +        char *chal, *pass, buf[32], key[DESKEYLEN];
       +        char *s;
       +        int n;
       +
       +        ARGBEGIN{
       +        default:
       +                usage();
       +        }ARGEND
       +        if(argc)
       +                usage();
       +
       +        s = getenv("service");
       +        if(s && strcmp(s, "cpu") == 0){
       +                fprint(2, "netkey must not be run on the cpu server\n");
       +                exits("boofhead");
       +        }
       +
       +        pass = readcons("password", nil, 1);
       +        if(pass == nil)
       +                sysfatal("reading password: %r");
       +        passtokey(key, pass);
       +
       +        for(;;){
       +                chal = readcons("challenge", nil, 0);
       +                if(chal == nil || *chal == 0)
       +                        exits(nil);
       +                n = strtol(chal, 0, 10);
       +                sprint(buf, "%d", n);
       +                netcrypt(key, buf);
       +                print("response: %s\n", buf);
       +        }
       +}
   DIR diff --git a/src/cmd/tar.C b/src/cmd/tar.C
       t@@ -1,17 +1,75 @@
       +/*
       + * tar - `tape archiver', actually usable on any medium.
       + *        POSIX "ustar" compliant when extracting, and by default when creating.
       + *        this tar attempts to read and write multiple Tblock-byte blocks
       + *        at once to and from the filesystem, and does not copy blocks
       + *        around internally.
       + */
       +
        #include <u.h>
        #include <libc.h>
       -#include <bio.h>
       -
       -#define TBLOCK        512
       -#define NBLOCK        40        /* maximum blocksize */
       -#define DBLOCK        20        /* default blocksize */
       -#define NAMSIZ        100
       -union        hblock
       -{
       -        char        dummy[TBLOCK];
       -        struct        header
       -        {
       -                char        name[NAMSIZ];
       +#include <fcall.h>                /* for %M */
       +#include <libString.h>
       +
       +#define creat tar_creat
       +
       +/*
       + * modified versions of those in libc.h; scans only the first arg for
       + * keyletters and options.
       + */
       +#define        TARGBEGIN {\
       +        (argv0 || (argv0 = *argv)), argv++, argc--;\
       +        if (argv[0]) {\
       +                char *_args, *_argt;\
       +                Rune _argc;\
       +                _args = &argv[0][0];\
       +                _argc = 0;\
       +                while(*_args && (_args += chartorune(&_argc, _args)))\
       +                        switch(_argc)
       +#define        TARGEND        SET(_argt); USED(_argt); USED(_argc); USED(_args); argc--, argv++; } \
       +        USED(argv);USED(argc); }
       +
       +#define ROUNDUP(a, b)        (((a) + (b) - 1)/(b))
       +#define BYTES2TBLKS(bytes) ROUNDUP(bytes, Tblock)
       +
       +typedef vlong Off;
       +typedef char *(*Refill)(int ar, char *bufs);
       +
       +enum { Stdin, Stdout, Stderr };
       +enum { None, Toc, Xtract, Replace };
       +enum {
       +        Tblock = 512,
       +        Nblock = 40,                /* maximum blocksize */
       +        Dblock = 20,                /* default blocksize */
       +        Namsiz = 100,
       +        Maxpfx = 155,                /* from POSIX */
       +        Maxname = Namsiz + 1 + Maxpfx,
       +        DEBUG = 0,
       +};
       +
       +/* POSIX link flags */
       +enum {
       +        LF_PLAIN1 =        '\0',
       +        LF_PLAIN2 =        '0',
       +        LF_LINK =        '1',
       +        LF_SYMLINK1 =        '2',
       +        LF_SYMLINK2 =        's',
       +        LF_CHR =        '3',
       +        LF_BLK =        '4',
       +        LF_DIR =        '5',
       +        LF_FIFO =        '6',
       +        LF_CONTIG =        '7',
       +        /* 'A' - 'Z' are reserved for custom implementations */
       +};
       +
       +#define islink(lf)        (isreallink(lf) || issymlink(lf))
       +#define isreallink(lf)        ((lf) == LF_LINK)
       +#define issymlink(lf)        ((lf) == LF_SYMLINK1 || (lf) == LF_SYMLINK2)
       +
       +typedef union {
       +        uchar        data[Tblock];
       +        struct {
       +                char        name[Namsiz];
                        char        mode[8];
                        char        uid[8];
                        char        gid[8];
       t@@ -19,622 +77,785 @@ union        hblock
                        char        mtime[12];
                        char        chksum[8];
                        char        linkflag;
       -                char        linkname[NAMSIZ];
       -        } dbuf;
       -} dblock, tbuf[NBLOCK];
       -
       -Dir *stbuf;
       -Biobuf bout;
       -
       -int        rflag, xflag, vflag, tflag, mt, cflag, fflag, Tflag, Rflag;
       -int        uflag, gflag;
       -int        chksum, recno, first;
       -int        nblock = DBLOCK;
       -
       -void        usage(void);
       -void        dorep(char **);
       -int        endtar(void);
       -void        getdir(void);
       -void        passtar(void);
       -void        putfile(char*, char *, char *);
       -void        doxtract(char **);
       -void        dotable(void);
       -void        putempty(void);
       -void        longt(Dir *);
       -int        checkdir(char *, int, Qid*);
       -void        tomodes(Dir *);
       -int        checksum(void);
       -int        checkupdate(char *);
       -int        prefix(char *, char *);
       -int        readtar(char *);
       -int        writetar(char *);
       -void        backtar(void);
       -void        flushtar(void);
       -void        affix(int, char *);
       -int        volprompt(void);
       -void
       -main(int argc, char **argv)
       +                char        linkname[Namsiz];
       +
       +                /* rest are defined by POSIX's ustar format; see p1003.2b */
       +                char        magic[6];        /* "ustar" */
       +                char        version[2];
       +                char        uname[32];
       +                char        gname[32];
       +                char        devmajor[8];
       +                char        devminor[8];
       +                char        prefix[Maxpfx]; /* if non-null, path= prefix "/" name */
       +        };
       +} Hdr;
       +
       +static int debug;
       +static int verb;
       +static int posix = 1;
       +static int creat;
       +static int aruid;
       +static int argid;
       +static int relative;
       +static int settime;
       +static int verbose;
       +
       +static int nblock = Dblock;
       +static char *usefile;
       +static char origdir[Maxname*2];
       +static Hdr *tpblk, *endblk;
       +static Hdr *curblk;
       +
       +static void
       +usage(void)
        {
       -        char *usefile;
       -        char *cp, *ap;
       -
       -        if (argc < 2)
       -                usage();
       +        fprint(2, "usage: %s {txrc}[pPvRTugf] [archive] file1 file2...\n",
       +                argv0);
       +        exits("usage");
       +}
        
       -        Binit(&bout, 1, OWRITE);
       -        usefile =  0;
       -        argv[argc] = 0;
       -        argv++;
       -        for (cp = *argv++; *cp; cp++) 
       -                switch(*cp) {
       -                case 'f':
       -                        usefile = *argv++;
       -                        if(!usefile)
       -                                usage();
       -                        fflag++;
       -                        break;
       -                case 'u':
       -                        ap = *argv++;
       -                        if(!ap)
       -                                usage();
       -                        uflag = strtoul(ap, 0, 0);
       -                        break;
       -                case 'g':
       -                        ap = *argv++;
       -                        if(!ap)
       -                                usage();
       -                        gflag = strtoul(ap, 0, 0);
       -                        break;
       -                case 'c':
       -                        cflag++;
       -                        rflag++;
       -                        break;
       -                case 'r':
       -                        rflag++;
       -                        break;
       -                case 'v':
       -                        vflag++;
       -                        break;
       -                case 'x':
       -                        xflag++;
       -                        break;
       -                case 'T':
       -                        Tflag++;
       -                        break;
       -                case 't':
       -                        tflag++;
       -                        break;
       -                case 'R':
       -                        Rflag++;
       -                        break;
       -                case '-':
       -                        break;
       -                default:
       -                        fprint(2, "tar: %c: unknown option\n", *cp);
       -                        usage();
       -                }
       +/*
       + * block-buffer management
       + */
        
       -        fmtinstall('M', dirmodefmt);
       +static void
       +initblks(void)
       +{
       +        free(tpblk);
       +        tpblk = malloc(Tblock * nblock);
       +        assert(tpblk != nil);
       +        endblk = tpblk + nblock;
       +}
        
       -        if (rflag) {
       -                if (!usefile) {
       -                        if (cflag == 0) {
       -                                fprint(2, "tar: can only create standard output archives\n");
       -                                exits("arg error");
       -                        }
       -                        mt = dup(1, -1);
       -                        nblock = 1;
       -                }
       -                else if ((mt = open(usefile, ORDWR)) < 0) {
       -                        if (cflag == 0 || (mt = create(usefile, OWRITE, 0666)) < 0) {
       -                                fprint(2, "tar: cannot open %s: %r\n", usefile);
       -                                exits("open");
       -                        }
       -                }
       -                dorep(argv);
       -        }
       -        else if (xflag)  {
       -                if (!usefile) {
       -                        mt = dup(0, -1);
       -                        nblock = 1;
       +static char *
       +refill(int ar, char *bufs)
       +{
       +        int i, n;
       +        unsigned bytes = Tblock * nblock;
       +        static int done, first = 1;
       +
       +        if (done)
       +                return nil;
       +
       +        /* try to size non-pipe input at first read */
       +        if (first && usefile) {
       +                first = 0;
       +                n = read(ar, bufs, bytes);
       +                if (n <= 0)
       +                        sysfatal("error reading archive: %r");
       +                i = n;
       +                if (i % Tblock != 0) {
       +                        fprint(2, "%s: archive block size (%d) error\n",
       +                                argv0, i);
       +                        exits("blocksize");
                        }
       -                else if ((mt = open(usefile, OREAD)) < 0) {
       -                        fprint(2, "tar: cannot open %s: %r\n", usefile);
       -                        exits("open");
       +                i /= Tblock;
       +                if (i != nblock) {
       +                        nblock = i;
       +                        fprint(2, "%s: blocking = %d\n", argv0, nblock);
       +                        endblk = (Hdr *)bufs + nblock;
       +                        bytes = n;
                        }
       -                doxtract(argv);
       +        } else
       +                n = readn(ar, bufs, bytes);
       +        if (n == 0)
       +                sysfatal("unexpected EOF reading archive");
       +        else if (n < 0)
       +                sysfatal("error reading archive: %r");
       +        else if (n%Tblock != 0)
       +                sysfatal("partial block read from archive");
       +        if (n != bytes) {
       +                done = 1;
       +                memset(bufs + n, 0, bytes - n);
                }
       -        else if (tflag) {
       -                if (!usefile) {
       -                        mt = dup(0, -1);
       -                        nblock = 1;
       -                }
       -                else if ((mt = open(usefile, OREAD)) < 0) {
       -                        fprint(2, "tar: cannot open %s: %r\n", usefile);
       -                        exits("open");
       -                }
       -                dotable();
       +        return bufs;
       +}
       +
       +static Hdr *
       +getblk(int ar, Refill rfp)
       +{
       +        if (curblk == nil || curblk >= endblk) {  /* input block exhausted? */
       +                if (rfp != nil && (*rfp)(ar, (char *)tpblk) == nil)
       +                        return nil;
       +                curblk = tpblk;
                }
       -        else
       -                usage();
       -        exits(0);
       +        return curblk++;
        }
        
       -void
       -usage(void)
       +static Hdr *
       +getblkrd(int ar)
        {
       -        fprint(2, "tar: usage  tar {txrc}[Rvf] [tarfile] file1 file2...\n");
       -        exits("usage");
       +        return getblk(ar, refill);
        }
        
       -void
       -dorep(char **argv)
       +static Hdr *
       +getblke(int ar)
        {
       -        char cwdbuf[2048], *cwd, thisdir[2048];
       -        char *cp, *cp2;
       -        int cd;
       +        return getblk(ar, nil);
       +}
        
       -        if (getwd(cwdbuf, sizeof(cwdbuf)) == 0) {
       -                fprint(2, "tar: can't find current directory: %r\n");
       -                exits("cwd");
       -        }
       -        cwd = cwdbuf;
       -
       -        if (!cflag) {
       -                getdir();
       -                do {
       -                        passtar();
       -                        getdir();
       -                } while (!endtar());
       -        }
       +static Hdr *
       +getblkz(int ar)
       +{
       +        Hdr *hp = getblke(ar);
        
       -        while (*argv) {
       -                cp2 = *argv;
       -                if (!strcmp(cp2, "-C") && argv[1]) {
       -                        argv++;
       -                        if (chdir(*argv) < 0)
       -                                perror(*argv);
       -                        cwd = *argv;
       -                        argv++;
       -                        continue;
       -                }
       -                cd = 0;
       -                for (cp = *argv; *cp; cp++)
       -                        if (*cp == '/')
       -                                cp2 = cp;
       -                if (cp2 != *argv) {
       -                        *cp2 = '\0';
       -                        chdir(*argv);
       -                        if(**argv == '/')
       -                                strncpy(thisdir, *argv, sizeof(thisdir));
       -                        else
       -                                snprint(thisdir, sizeof(thisdir), "%s/%s", cwd, *argv);
       -                        *cp2 = '/';
       -                        cp2++;
       -                        cd = 1;
       -                } else
       -                        strncpy(thisdir, cwd, sizeof(thisdir));
       -                putfile(thisdir, *argv++, cp2);
       -                if(cd && chdir(cwd) < 0) {
       -                        fprint(2, "tar: can't cd back to %s: %r\n", cwd);
       -                        exits("cwd");
       -                }
       -        }
       -        putempty();
       -        putempty();
       -        flushtar();
       +        if (hp != nil)
       +                memset(hp->data, 0, Tblock);
       +        return hp;
        }
        
       -int
       -endtar(void)
       +/*
       + * how many block buffers are available, starting at the address
       + * just returned by getblk*?
       + */
       +static int
       +gothowmany(int max)
        {
       -        if (dblock.dbuf.name[0] == '\0') {
       -                backtar();
       -                return(1);
       -        }
       -        else
       -                return(0);
       +        int n = endblk - (curblk - 1);
       +
       +        return n > max? max: n;
        }
        
       -void
       -getdir(void)
       +/*
       + * indicate that one is done with the last block obtained from getblke
       + * and it is now available to be written into the archive.
       + */
       +static void
       +putlastblk(int ar)
        {
       -        Dir *sp;
       +        unsigned bytes = Tblock * nblock;
        
       -        readtar((char*)&dblock);
       -        if (dblock.dbuf.name[0] == '\0')
       -                return;
       -        if(stbuf == nil){
       -                stbuf = malloc(sizeof(Dir));
       -                if(stbuf == nil) {
       -                        fprint(2, "tar: can't malloc: %r\n");
       -                        exits("malloc");
       -                }
       -        }
       -        sp = stbuf;
       -        sp->mode = strtol(dblock.dbuf.mode, 0, 8);
       -        sp->uid = "adm";
       -        sp->gid = "adm";
       -        sp->length = strtol(dblock.dbuf.size, 0, 8);
       -        sp->mtime = strtol(dblock.dbuf.mtime, 0, 8);
       -        chksum = strtol(dblock.dbuf.chksum, 0, 8);
       -        if (chksum != checksum()) {
       -                fprint(2, "directory checksum error\n");
       -                exits("checksum error");
       -        }
       -        sp->qid.type = 0;
       -        /* the mode test is ugly but sometimes necessary */
       -        if (dblock.dbuf.linkflag == '5' || (sp->mode&0170000) == 040000) {
       -                sp->qid.type |= QTDIR;
       -                sp->mode |= DMDIR;
       -        }
       +        /* if writing end-of-archive, aid compression (good hygiene too) */
       +        if (curblk < endblk)
       +                memset(curblk, 0, (char *)endblk - (char *)curblk);
       +        if (write(ar, tpblk, bytes) != bytes)
       +                sysfatal("error writing archive: %r");
        }
        
       -void
       -passtar(void)
       +static void
       +putblk(int ar)
        {
       -        long blocks;
       -        char buf[TBLOCK];
       -
       -        if (dblock.dbuf.linkflag == '1' || dblock.dbuf.linkflag == 's')
       -                return;
       -        blocks = stbuf->length;
       -        blocks += TBLOCK-1;
       -        blocks /= TBLOCK;
       +        if (curblk >= endblk)
       +                putlastblk(ar);
       +}
        
       -        while (blocks-- > 0)
       -                readtar(buf);
       +static void
       +putbackblk(int ar)
       +{
       +        curblk--;
       +        USED(ar);
        }
        
       -void
       -putfile(char *dir, char *longname, char *sname)
       -{
       -        int infile;
       -        long blocks;
       -        char buf[TBLOCK];
       -        char curdir[4096];
       -        char shortname[4096];
       -        char *cp, *cp2;
       -        Dir *db;
       -        int i, n;
       +static void
       +putreadblks(int ar, int blks)
       +{
       +        curblk += blks - 1;
       +        USED(ar);
       +}
        
       -        if(strlen(sname) > sizeof shortname - 3){
       -                fprint(2, "tar: %s: name too long (max %d)\n", sname, sizeof shortname - 3);
       -                return;
       -        }
       -        
       -        snprint(shortname, sizeof shortname, "./%s", sname);
       -        infile = open(shortname, OREAD);
       -        if (infile < 0) {
       -                fprint(2, "tar: %s: cannot open file - %r\n", longname);
       -                return;
       -        }
       +static void
       +putblkmany(int ar, int blks)
       +{
       +        curblk += blks - 1;
       +        putblk(ar);
       +}
        
       -        if(stbuf != nil)
       -                free(stbuf);
       -        stbuf = dirfstat(infile);
       -
       -        if (stbuf->qid.type & QTDIR) {
       -                /* Directory */
       -                for (i = 0, cp = buf; *cp++ = longname[i++];);
       -                *--cp = '/';
       -                *++cp = 0;
       -                if( (cp - buf) >= NAMSIZ) {
       -                        fprint(2, "tar: %s: file name too long\n", longname);
       -                        close(infile);
       -                        return;
       -                }
       -                stbuf->length = 0;
       -                tomodes(stbuf);
       -                strcpy(dblock.dbuf.name,buf);
       -                dblock.dbuf.linkflag = '5';                /* Directory */
       -                sprint(dblock.dbuf.chksum, "%6o", checksum());
       -                writetar( (char *) &dblock);
       -                if (chdir(shortname) < 0) {
       -                        fprint(2, "tar: can't cd to %s: %r\n", shortname);
       -                        snprint(curdir, sizeof(curdir), "cd %s", shortname);
       -                        exits(curdir);
       -                }
       -                sprint(curdir, "%s/%s", dir, sname);
       -                while ((n = dirread(infile, &db)) > 0) {
       -                        for(i = 0; i < n; i++){
       -                                strncpy(cp, db[i].name, sizeof buf - (cp-buf));
       -                                putfile(curdir, buf, db[i].name);
       -                        }free(db);
       -                }
       -                close(infile);
       -                if (chdir(dir) < 0 && chdir("..") < 0) {
       -                        fprint(2, "tar: can't cd to ..(%s): %r\n", dir);
       -                        snprint(curdir, sizeof(curdir), "cd ..(%s)", dir);
       -                        exits(curdir);
       -                }
       -                return;
       -        }
       +/*
       + * common routines
       + */
        
       +/* modifies hp->chksum */
       +long
       +chksum(Hdr *hp)
       +{
       +        int n = Tblock;
       +        long i = 0;
       +        uchar *cp = hp->data;
       +
       +        memset(hp->chksum, ' ', sizeof hp->chksum);
       +        while (n-- > 0)
       +                i += *cp++;
       +        return i;
       +}
        
       -        tomodes(stbuf);
       +static int
       +isustar(Hdr *hp)
       +{
       +        return strcmp(hp->magic, "ustar") == 0;
       +}
        
       -        cp2 = longname;
       -        for (cp = dblock.dbuf.name, i=0; (*cp++ = *cp2++) && i < NAMSIZ; i++);
       -        if (i >= NAMSIZ) {
       -                fprint(2, "%s: file name too long\n", longname);
       -                close(infile);
       -                return;
       -        }
       +/*
       + * s is at most n bytes long, but need not be NUL-terminated.
       + * if shorter than n bytes, all bytes after the first NUL must also
       + * be NUL.
       + */
       +static int
       +strnlen(char *s, int n)
       +{
       +        return s[n - 1] != '\0'? n: strlen(s);
       +}
        
       -        blocks = (stbuf->length + (TBLOCK-1)) / TBLOCK;
       -        if (vflag) {
       -                fprint(2, "a %s ", longname);
       -                fprint(2, "%ld blocks\n", blocks);
       +/* set fullname from header */
       +static char *
       +name(Hdr *hp)
       +{
       +        int pfxlen, namlen;
       +        static char fullname[Maxname + 1];
       +
       +        namlen = strnlen(hp->name, sizeof hp->name);
       +        if (hp->prefix[0] == '\0' || !isustar(hp)) {        /* old-style name? */
       +                memmove(fullname, hp->name, namlen);
       +                fullname[namlen] = '\0';
       +                return fullname;
                }
       -        dblock.dbuf.linkflag = 0;                        /* Regular file */
       -        sprint(dblock.dbuf.chksum, "%6o", checksum());
       -        writetar( (char *) &dblock);
        
       -        while ((i = readn(infile, buf, TBLOCK)) > 0 && blocks > 0) {
       -                writetar(buf);
       -                blocks--;
       -        }
       -        close(infile);
       -        if (blocks != 0 || i != 0)
       -                fprint(2, "%s: file changed size\n", longname);
       -        while (blocks-- >  0)
       -                putempty();
       +        /* name is in two pieces */
       +        pfxlen = strnlen(hp->prefix, sizeof hp->prefix);
       +        memmove(fullname, hp->prefix, pfxlen);
       +        fullname[pfxlen] = '/';
       +        memmove(fullname + pfxlen + 1, hp->name, namlen);
       +        fullname[pfxlen + 1 + namlen] = '\0';
       +        return fullname;
        }
        
       -
       -void
       -doxtract(char **argv)
       +static int
       +isdir(Hdr *hp)
        {
       -        Dir null;
       -        long blocks, bytes;
       -        char buf[TBLOCK], outname[NAMSIZ+4];
       -        char **cp;
       -        int ofile;
       +        /* the mode test is ugly but sometimes necessary */
       +        return hp->linkflag == LF_DIR ||
       +                strrchr(name(hp), '\0')[-1] == '/' ||
       +                (strtoul(hp->mode, nil, 8)&0170000) == 040000;
       +}
        
       -        for (;;) {
       -                getdir();
       -                if (endtar())
       -                        break;
       +static int
       +eotar(Hdr *hp)
       +{
       +        return name(hp)[0] == '\0';
       +}
        
       -                if (*argv == 0)
       -                        goto gotit;
       +static Hdr *
       +readhdr(int ar)
       +{
       +        long hdrcksum;
       +        Hdr *hp;
       +
       +        hp = getblkrd(ar);
       +        if (hp == nil)
       +                sysfatal("unexpected EOF instead of archive header");
       +        if (eotar(hp))                        /* end-of-archive block? */
       +                return nil;
       +        hdrcksum = strtoul(hp->chksum, nil, 8);
       +        if (chksum(hp) != hdrcksum)
       +                sysfatal("bad archive header checksum: name %.64s...",
       +                        hp->name);
       +        return hp;
       +}
        
       -                for (cp = argv; *cp; cp++)
       -                        if (prefix(*cp, dblock.dbuf.name))
       -                                goto gotit;
       -                passtar();
       -                continue;
       +/*
       + * tar r[c]
       + */
        
       -gotit:
       -                if(checkdir(dblock.dbuf.name, stbuf->mode, &(stbuf->qid)))
       -                        continue;
       +/*
       + * if name is longer than Namsiz bytes, try to split it at a slash and fit the
       + * pieces into hp->prefix and hp->name.
       + */
       +static int
       +putfullname(Hdr *hp, char *name)
       +{
       +        int namlen, pfxlen;
       +        char *sl, *osl;
       +        String *slname = nil;
       +
       +        if (isdir(hp)) {
       +                slname = s_new();
       +                s_append(slname, name);
       +                s_append(slname, "/");                /* posix requires this */
       +                name = s_to_c(slname);
       +        }
        
       -                if (dblock.dbuf.linkflag == '1') {
       -                        fprint(2, "tar: can't link %s %s\n",
       -                                dblock.dbuf.linkname, dblock.dbuf.name);
       -                        remove(dblock.dbuf.name);
       -                        continue;
       -                }
       -                if (dblock.dbuf.linkflag == 's') {
       -                        fprint(2, "tar: %s: cannot symlink\n", dblock.dbuf.name);
       -                        continue;
       -                }
       -                if(dblock.dbuf.name[0] != '/' || Rflag)
       -                        sprint(outname, "./%s", dblock.dbuf.name);
       -                else
       -                        strcpy(outname, dblock.dbuf.name);
       -                if ((ofile = create(outname, OWRITE, stbuf->mode & 0777)) < 0) {
       -                        fprint(2, "tar: %s - cannot create: %r\n", outname);
       -                        passtar();
       -                        continue;
       -                }
       +        namlen = strlen(name);
       +        if (namlen <= Namsiz) {
       +                strncpy(hp->name, name, Namsiz);
       +                hp->prefix[0] = '\0';                /* ustar paranoia */
       +                return 0;
       +        }
        
       -                blocks = ((bytes = stbuf->length) + TBLOCK-1)/TBLOCK;
       -                if (vflag)
       -                        fprint(2, "x %s, %ld bytes\n",
       -                                dblock.dbuf.name, bytes);
       -                while (blocks-- > 0) {
       -                        readtar(buf);
       -                        if (bytes > TBLOCK) {
       -                                if (write(ofile, buf, TBLOCK) < 0) {
       -                                        fprint(2, "tar: %s: HELP - extract write error: %r\n", dblock.dbuf.name);
       -                                        exits("extract write");
       -                                }
       -                        } else
       -                                if (write(ofile, buf, bytes) < 0) {
       -                                        fprint(2, "tar: %s: HELP - extract write error: %r\n", dblock.dbuf.name);
       -                                        exits("extract write");
       -                                }
       -                        bytes -= TBLOCK;
       -                }
       -                if(Tflag){
       -                        nulldir(&null);
       -                        null.mtime = stbuf->mtime;
       -                        dirfwstat(ofile, &null);
       -                }
       -                close(ofile);
       +        if (!posix || namlen > Maxname) {
       +                fprint(2, "%s: name too long for tar header: %s\n",
       +                        argv0, name);
       +                return -1;
       +        }
       +        /*
       +         * try various splits until one results in pieces that fit into the
       +         * appropriate fields of the header.  look for slashes from right
       +         * to left, in the hopes of putting the largest part of the name into
       +         * hp->prefix, which is larger than hp->name.
       +         */
       +        sl = strrchr(name, '/');
       +        while (sl != nil) {
       +                pfxlen = sl - name;
       +                if (pfxlen <= sizeof hp->prefix && namlen-1 - pfxlen <= Namsiz)
       +                        break;
       +                osl = sl;
       +                *osl = '\0';
       +                sl = strrchr(name, '/');
       +                *osl = '/';
       +        }
       +        if (sl == nil) {
       +                fprint(2, "%s: name can't be split to fit tar header: %s\n",
       +                        argv0, name);
       +                return -1;
                }
       +        *sl = '\0';
       +        strncpy(hp->prefix, name, sizeof hp->prefix);
       +        *sl++ = '/';
       +        strncpy(hp->name, sl, sizeof hp->name);
       +        if (slname)
       +                s_free(slname);
       +        return 0;
        }
        
       -void
       -dotable(void)
       +static int
       +mkhdr(Hdr *hp, Dir *dir, char *file)
        {
       -        for (;;) {
       -                getdir();
       -                if (endtar())
       -                        break;
       -                if (vflag)
       -                        longt(stbuf);
       -                Bprint(&bout, "%s", dblock.dbuf.name);
       -                if (dblock.dbuf.linkflag == '1')
       -                        Bprint(&bout, " linked to %s", dblock.dbuf.linkname);
       -                if (dblock.dbuf.linkflag == 's')
       -                        Bprint(&bout, " -> %s", dblock.dbuf.linkname);
       -                Bprint(&bout, "\n");
       -                passtar();
       +        /*
       +         * these fields run together, so we format them in order and don't use
       +         * snprint.
       +         */
       +        sprint(hp->mode, "%6lo ", dir->mode & 0777);
       +        sprint(hp->uid, "%6o ", aruid);
       +        sprint(hp->gid, "%6o ", argid);
       +        /*
       +         * files > 2⁳⁳ bytes can't be described
       +         * (unless we resort to xustar or exustar formats).
       +         */
       +        if (dir->length >= (Off)1<<33) {
       +                fprint(2, "%s: %s: too large for tar header format\n",
       +                        argv0, file);
       +                return -1;
       +        }
       +        sprint(hp->size, "%11lluo ", dir->length);
       +        sprint(hp->mtime, "%11luo ", dir->mtime);
       +        hp->linkflag = (dir->mode&DMDIR? LF_DIR: LF_PLAIN1);
       +        putfullname(hp, file);
       +        if (posix) {
       +                strncpy(hp->magic, "ustar", sizeof hp->magic);
       +                strncpy(hp->version, "00", sizeof hp->version);
       +                strncpy(hp->uname, dir->uid, sizeof hp->uname);
       +                strncpy(hp->gname, dir->gid, sizeof hp->gname);
                }
       +        sprint(hp->chksum, "%6luo", chksum(hp));
       +        return 0;
        }
        
       -void
       -putempty(void)
       +static void addtoar(int ar, char *file, char *shortf);
       +
       +static void
       +addtreetoar(int ar, char *file, char *shortf, int fd)
        {
       -        char buf[TBLOCK];
       +        int n;
       +        Dir *dent, *dirents;
       +        String *name = s_new();
       +
       +        n = dirreadall(fd, &dirents);
       +        close(fd);
       +        if (n == 0)
       +                return;
        
       -        memset(buf, 0, TBLOCK);
       -        writetar(buf);
       +        if (chdir(shortf) < 0)
       +                sysfatal("chdir %s: %r", file);
       +        if (DEBUG)
       +                fprint(2, "chdir %s\t# %s\n", shortf, file);
       +
       +        for (dent = dirents; dent < dirents + n; dent++) {
       +                s_reset(name);
       +                s_append(name, file);
       +                s_append(name, "/");
       +                s_append(name, dent->name);
       +                addtoar(ar, s_to_c(name), dent->name);
       +        }
       +        s_free(name);
       +        free(dirents);
       +
       +        if (chdir("..") < 0)
       +                sysfatal("chdir %s: %r", file);
       +        if (DEBUG)
       +                fprint(2, "chdir ..\n");
        }
        
       -void
       -longt(Dir *st)
       +static void
       +addtoar(int ar, char *file, char *shortf)
        {
       -        char *cp;
       -
       -        Bprint(&bout, "%M %4d/%1d ", st->mode, 0, 0);        /* 0/0 uid/gid */
       -        Bprint(&bout, "%8lld", st->length);
       -        cp = ctime(st->mtime);
       -        Bprint(&bout, " %-12.12s %-4.4s ", cp+4, cp+24);
       +        int n, fd, isdir, r;
       +        long bytes;
       +        ulong blksleft, blksread;
       +        Hdr *hbp;
       +        Dir *dir;
       +
       +        fd = open(shortf, OREAD);
       +        if (fd < 0) {
       +                fprint(2, "%s: can't open %s: %r\n", argv0, file);
       +                return;
       +        }
       +        dir = dirfstat(fd);
       +        if (dir == nil)
       +                sysfatal("can't fstat %s: %r", file);
       +
       +        hbp = getblkz(ar);
       +        isdir = !!(dir->qid.type&QTDIR);
       +        r = mkhdr(hbp, dir, file);
       +        if (r < 0) {
       +                putbackblk(ar);
       +                free(dir);
       +                close(fd);
       +                return;
       +        }
       +        putblk(ar);
       +
       +        blksleft = BYTES2TBLKS(dir->length);
       +        free(dir);
       +
       +        if (isdir)
       +                addtreetoar(ar, file, shortf, fd);
       +        else {
       +                for (; blksleft > 0; blksleft -= blksread) {
       +                        hbp = getblke(ar);
       +                        blksread = gothowmany(blksleft);
       +                        bytes = blksread * Tblock;
       +                        n = read(fd, hbp->data, bytes);
       +                        if (n < 0)
       +                                sysfatal("error reading %s: %r", file);
       +                        /*
       +                         * ignore EOF.  zero any partial block to aid
       +                         * compression and emergency recovery of data.
       +                         */
       +                        if (n < Tblock)
       +                                memset(hbp->data + n, 0, bytes - n);
       +                        putblkmany(ar, blksread);
       +                }
       +                close(fd);
       +                if (verbose)
       +                        fprint(2, "%s: a %s\n", argv0, file);
       +        }
        }
        
       -int
       -checkdir(char *name, int mode, Qid *qid)
       +static void
       +replace(char **argv)
        {
       -        char *cp;
       -        int f;
       -        Dir *d, null;
       -
       -        if(Rflag && *name == '/')
       -                name++;
       -        cp = name;
       -        if(*cp == '/')
       -                cp++;
       -        for (; *cp; cp++) {
       -                if (*cp == '/') {
       -                        *cp = '\0';
       -                        if (access(name, 0) < 0) {
       -                                f = create(name, OREAD, DMDIR + 0775L);
       -                                if(f < 0)
       -                                        fprint(2, "tar: mkdir %s failed: %r\n", name);
       -                                close(f);
       +        int i, ar;
       +        ulong blksleft, blksread;
       +        Off bytes;
       +        Hdr *hp;
       +
       +        if (usefile && creat)
       +                ar = create(usefile, ORDWR, 0666);
       +        else if (usefile)
       +                ar = open(usefile, ORDWR);
       +        else
       +                ar = Stdout;
       +        if (ar < 0)
       +                sysfatal("can't open archive %s: %r", usefile);
       +
       +        if (usefile && !creat) {
       +                /* skip quickly to the end */
       +                while ((hp = readhdr(ar)) != nil) {
       +                        bytes = (isdir(hp)? 0: strtoull(hp->size, nil, 8));
       +                        for (blksleft = BYTES2TBLKS(bytes);
       +                             blksleft > 0 && getblkrd(ar) != nil;
       +                             blksleft -= blksread) {
       +                                blksread = gothowmany(blksleft);
       +                                putreadblks(ar, blksread);
                                }
       -                        *cp = '/';
                        }
       +                /*
       +                 * we have just read the end-of-archive Tblock.
       +                 * now seek back over the (big) archive block containing it,
       +                 * and back up curblk ptr over end-of-archive Tblock in memory.
       +                 */
       +                if (seek(ar, -Tblock*nblock, 1) < 0)
       +                        sysfatal("can't seek back over end-of-archive: %r");
       +                curblk--;
                }
        
       -        /* if this is a directory, chmod it to the mode in the tar plus 700 */
       -        if(cp[-1] == '/' || (qid->type&QTDIR)){
       -                if((d=dirstat(name)) != 0){
       -                        nulldir(&null);
       -                        null.mode = DMDIR | (mode & 0777) | 0700;
       -                        dirwstat(name, &null);
       -                        free(d);
       -                }
       -                return 1;
       -        } else
       -                return 0;
       +        for (i = 0; argv[i] != nil; i++)
       +                addtoar(ar, argv[i], argv[i]);
       +
       +        /* write end-of-archive marker */
       +        getblkz(ar);
       +        putblk(ar);
       +        getblkz(ar);
       +        putlastblk(ar);
       +
       +        if (ar > Stderr)
       +                close(ar);
        }
        
       -void
       -tomodes(Dir *sp)
       +/*
       + * tar [xt]
       + */
       +
       +/* is pfx a file-name prefix of name? */
       +static int
       +prefix(char *name, char *pfx)
        {
       -        char *cp;
       +        int pfxlen = strlen(pfx);
       +        char clpfx[Maxname+1];
        
       -        for (cp = dblock.dummy; cp < &dblock.dummy[TBLOCK]; cp++)
       -                *cp = '\0';
       -        sprint(dblock.dbuf.mode, "%6lo ", sp->mode & 0777);
       -        sprint(dblock.dbuf.uid, "%6o ", uflag);
       -        sprint(dblock.dbuf.gid, "%6o ", gflag);
       -        sprint(dblock.dbuf.size, "%11llo ", sp->length);
       -        sprint(dblock.dbuf.mtime, "%11lo ", sp->mtime);
       +        if (pfxlen > Maxname)
       +                return 0;
       +        strcpy(clpfx, pfx);
       +        cleanname(clpfx);
       +        return strncmp(pfx, name, pfxlen) == 0 &&
       +                (name[pfxlen] == '\0' || name[pfxlen] == '/');
        }
        
       -int
       -checksum(void)
       +static int
       +match(char *name, char **argv)
        {
                int i;
       -        char *cp;
       +        char clname[Maxname+1];
        
       -        for (cp = dblock.dbuf.chksum; cp < &dblock.dbuf.chksum[sizeof(dblock.dbuf.chksum)]; cp++)
       -                *cp = ' ';
       -        i = 0;
       -        for (cp = dblock.dummy; cp < &dblock.dummy[TBLOCK]; cp++)
       -                i += *cp & 0xff;
       -        return(i);
       +        if (argv[0] == nil)
       +                return 1;
       +        strcpy(clname, name);
       +        cleanname(clname);
       +        for (i = 0; argv[i] != nil; i++)
       +                if (prefix(clname, argv[i]))
       +                        return 1;
       +        return 0;
        }
        
       -int
       -prefix(char *s1, char *s2)
       +static int
       +makedir(char *s)
        {
       -        while (*s1)
       -                if (*s1++ != *s2++)
       -                        return(0);
       -        if (*s2)
       -                return(*s2 == '/');
       -        return(1);
       +        int f;
       +
       +        if (access(s, AEXIST) == 0)
       +                return -1;
       +        f = create(s, OREAD, DMDIR | 0777);
       +        if (f >= 0)
       +                close(f);
       +        return f;
        }
        
       -int
       -readtar(char *buffer)
       +static void
       +mkpdirs(char *s)
        {
       -        int i;
       +        int done = 0;
       +        char *p = s;
        
       -        if (recno >= nblock || first == 0) {
       -                if ((i = readn(mt, tbuf, TBLOCK*nblock)) <= 0) {
       -                        fprint(2, "tar: archive read error: %r\n");
       -                        exits("archive read");
       -                }
       -                if (first == 0) {
       -                        if ((i % TBLOCK) != 0) {
       -                                fprint(2, "tar: archive blocksize error: %r\n");
       -                                exits("blocksize");
       -                        }
       -                        i /= TBLOCK;
       -                        if (i != nblock) {
       -                                fprint(2, "tar: blocksize = %d\n", i);
       -                                nblock = i;
       -                        }
       -                }
       -                recno = 0;
       +        while (!done && (p = strchr(p + 1, '/')) != nil) {
       +                *p = '\0';
       +                done = (access(s, AEXIST) < 0 && makedir(s) < 0);
       +                *p = '/';
                }
       -        first = 1;
       -        memmove(buffer, &tbuf[recno++], TBLOCK);
       -        return(TBLOCK);
        }
        
       -int
       -writetar(char *buffer)
       +/* copy a file from the archive into the filesystem */
       +static void
       +extract1(int ar, Hdr *hp, char *fname)
        {
       -        first = 1;
       -        if (recno >= nblock) {
       -                if (write(mt, tbuf, TBLOCK*nblock) != TBLOCK*nblock) {
       -                        fprint(2, "tar: archive write error: %r\n");
       -                        exits("write");
       +        int wrbytes, fd = -1, dir = 0, okcreate = 1;
       +        long mtime = strtol(hp->mtime, nil, 8);
       +        ulong mode = strtoul(hp->mode, nil, 8) & 0777;
       +        Off bytes = strtoll(hp->size, nil, 8);
       +        ulong blksread, blksleft = BYTES2TBLKS(bytes);
       +        Hdr *hbp;
       +
       +        if (isdir(hp)) {
       +                mode |= DMDIR|0700;
       +                blksleft = 0;
       +                dir = 1;
       +        }
       +        switch (hp->linkflag) {
       +        case LF_LINK:
       +        case LF_SYMLINK1:
       +        case LF_SYMLINK2:
       +        case LF_FIFO:
       +                blksleft = okcreate = 0;
       +                break;
       +        }
       +        if (relative && fname[0] == '/')
       +                fname++;
       +        if (verb == Xtract) {
       +                cleanname(fname);
       +                switch (hp->linkflag) {
       +                case LF_LINK:
       +                case LF_SYMLINK1:
       +                case LF_SYMLINK2:
       +                        fprint(2, "%s: can't make (sym)link %s\n",
       +                                argv0, fname);
       +                        break;
       +                case LF_FIFO:
       +                        fprint(2, "%s: can't make fifo %s\n", argv0, fname);
       +                        break;
       +                }
       +                if (okcreate)
       +                        fd = create(fname, (dir? OREAD: OWRITE), mode);
       +                if (fd < 0) {
       +                        mkpdirs(fname);
       +                        fd = create(fname, (dir? OREAD: OWRITE), mode);
       +                        if (fd < 0 && (!dir || access(fname, AEXIST) < 0))
       +                                fprint(2, "%s: can't create %s: %r\n",
       +                                        argv0, fname);
                        }
       -                recno = 0;
       +                if (fd >= 0 && verbose)
       +                        fprint(2, "%s: x %s\n", argv0, fname);
       +        } else if (verbose) {
       +                char *cp = ctime(mtime);
       +
       +                print("%M %8lld %-12.12s %-4.4s %s\n",
       +                        mode, bytes, cp+4, cp+24, fname);
       +        } else
       +                print("%s\n", fname);
       +
       +        for (; blksleft > 0; blksleft -= blksread) {
       +                hbp = getblkrd(ar);
       +                if (hbp == nil)
       +                        sysfatal("unexpected EOF on archive extracting %s",
       +                                fname);
       +                blksread = gothowmany(blksleft);
       +                wrbytes = (bytes >= Tblock*blksread? Tblock*blksread: bytes);
       +                if (fd >= 0 && write(fd, hbp->data, wrbytes) != wrbytes)
       +                        sysfatal("write error on %s: %r", fname);
       +                putreadblks(ar, blksread);
       +                bytes -= wrbytes;
                }
       -        memmove(&tbuf[recno++], buffer, TBLOCK);
       -        if (recno >= nblock) {
       -                if (write(mt, tbuf, TBLOCK*nblock) != TBLOCK*nblock) {
       -                        fprint(2, "tar: archive write error: %r\n");
       -                        exits("write");
       +        if (fd >= 0) {
       +                /*
       +                 * directories should be wstated after we're done
       +                 * creating files in them.
       +                 */
       +                if (settime) {
       +                        Dir nd;
       +
       +                        nulldir(&nd);
       +                        nd.mtime = mtime;
       +                        if (isustar(hp))
       +                                nd.gid = hp->gname;
       +                        dirfwstat(fd, &nd);
                        }
       -                recno = 0;
       +                close(fd);
                }
       -        return(TBLOCK);
        }
        
       -/*
       - * backup over last tar block
       - */
       -void
       -backtar(void)
       +static void
       +skip(int ar, Hdr *hp, char *fname)
       +{
       +        Off bytes;
       +        ulong blksleft, blksread;
       +        Hdr *hbp;
       +
       +        if (isdir(hp))
       +                return;
       +        bytes = strtoull(hp->size, nil, 8);
       +        blksleft = BYTES2TBLKS(bytes);
       +        for (; blksleft > 0; blksleft -= blksread) {
       +                hbp = getblkrd(ar);
       +                if (hbp == nil)
       +                        sysfatal("unexpected EOF on archive extracting %s",
       +                                fname);
       +                blksread = gothowmany(blksleft);
       +                putreadblks(ar, blksread);
       +        }
       +}
       +
       +static void
       +extract(char **argv)
        {
       -        seek(mt, -TBLOCK*nblock, 1);
       -        recno--;
       +        int ar;
       +        char *longname;
       +        Hdr *hp;
       +
       +        if (usefile)
       +                ar = open(usefile, OREAD);
       +        else
       +                ar = Stdin;
       +        if (ar < 0)
       +                sysfatal("can't open archive %s: %r", usefile);
       +        while ((hp = readhdr(ar)) != nil) {
       +                longname = name(hp);
       +                if (match(longname, argv))
       +                        extract1(ar, hp, longname);
       +                else
       +                        skip(ar, hp, longname);
       +        }
       +        if (ar > Stderr)
       +                close(ar);
        }
        
        void
       -flushtar(void)
       +main(int argc, char *argv[])
        {
       -        write(mt, tbuf, TBLOCK*nblock);
       +        int errflg = 0;
       +        char *ret = nil;
       +
       +        quotefmtinstall();
       +        fmtinstall('M', dirmodefmt);
       +
       +        TARGBEGIN {
       +        case 'c':
       +                creat++;
       +                verb = Replace;
       +                break;
       +        case 'f':
       +                usefile = EARGF(usage());
       +                break;
       +        case 'g':
       +                argid = strtoul(EARGF(usage()), 0, 0);
       +                break;
       +        case 'p':
       +                posix++;
       +                break;
       +        case 'P':
       +                posix = 0;
       +                break;
       +        case 'r':
       +                verb = Replace;
       +                break;
       +        case 'R':
       +                relative++;
       +                break;
       +        case 't':
       +                verb = Toc;
       +                break;
       +        case 'T':
       +                settime++;
       +                break;
       +        case 'u':
       +                aruid = strtoul(EARGF(usage()), 0, 0);
       +                break;
       +        case 'v':
       +                verbose++;
       +                break;
       +        case 'x':
       +                verb = Xtract;
       +                break;
       +        case '-':
       +                break;
       +        default:
       +                errflg++;
       +                break;
       +        } TARGEND
       +
       +        if (argc < 0 || errflg)
       +                usage();
       +
       +        initblks();
       +        switch (verb) {
       +        case Toc:
       +        case Xtract:
       +                extract(argv);
       +                break;
       +        case Replace:
       +                if (getwd(origdir, sizeof origdir) == nil)
       +                        strcpy(origdir, "/tmp");
       +                replace(argv);
       +                chdir(origdir);                /* for profiling */
       +                break;
       +        default:
       +                usage();
       +                break;
       +        }
       +        exits(ret);
        }