tDump-like file system backup for Unix, built on Venti. - 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 004aa293f360ea0f63ec50f5042f8c0fb2831e4f
 (DIR) parent 0c98da8bf8ea51d0288222f6c6ba3c125cf20f46
 (HTM) Author: rsc <devnull@localhost>
       Date:   Wed, 13 Jul 2005 03:49:41 +0000
       
       Dump-like file system backup for Unix, built on Venti.
       
       Diffstat:
         A man/man1/hist.1                     |      78 +++++++++++++++++++++++++++++++
         A man/man1/vbackup.1                  |     263 +++++++++++++++++++++++++++++++
         A man/man1/yesterday.1                |      99 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/COPYRIGHT           |      27 +++++++++++++++++++++++++++
         A src/cmd/vbackup/config.c            |     515 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/diskcat.c           |      54 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/diskftp.c           |     134 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/disknfs.c           |     126 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/hist.c              |     282 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/mkfile              |      28 ++++++++++++++++++++++++++++
         A src/cmd/vbackup/mount-FreeBSD.c     |      52 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/mount-Linux.c       |      58 ++++++++++++++++++++++++++++++
         A src/cmd/vbackup/mount-none.c        |      12 ++++++++++++
         A src/cmd/vbackup/mountnfs.h          |       1 +
         A src/cmd/vbackup/nfs3srv.c           |     428 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/nfs3srv.h           |      16 ++++++++++++++++
         A src/cmd/vbackup/queue.c             |      64 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/queue.h             |      22 ++++++++++++++++++++++
         A src/cmd/vbackup/util.c              |      23 +++++++++++++++++++++++
         A src/cmd/vbackup/vbackup.c           |     524 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/vcat.c              |      76 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/vftp.c              |       0 
         A src/cmd/vbackup/vmount.c            |      77 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/vmount.rc           |      19 +++++++++++++++++++
         A src/cmd/vbackup/vmount0.c           |      77 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/vnfs.c              |    1273 +++++++++++++++++++++++++++++++
         A src/cmd/vbackup/yesterday.rc        |     109 +++++++++++++++++++++++++++++++
       
       27 files changed, 4437 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/man/man1/hist.1 b/man/man1/hist.1
       t@@ -0,0 +1,78 @@
       +.TH HIST 1
       +.SH NAME
       +hist \- print file names from the dump
       +.SH SYNOPSIS
       +.B hist
       +[
       +.B -vdu
       +] [
       +.B -s
       +.I yyyymmdd
       +]
       +.I files ...
       +.SH DESCRIPTION
       +.I Hist
       +prints the names, dates, and sizes of all versions of the named
       +.IR files ,
       +looking backwards in time,
       +stored in the dump file system.
       +If the file exists in the main tree, the first line of output will be its current state.
       +For example,
       +.IP
       +.EX
       +hist ~rsc/.bash_history
       +.EE
       +.PP
       +produces
       +.IP
       +.EX
       +.nf
       +May 19 16:11:37 EDT 2005 /home/am3/rsc/.bash_history 6175
       +May 18 23:32:16 EDT 2005 /dump/am/2005/0519/home/am3/rsc/.bash_history 5156
       +May 17 23:32:31 EDT 2005 /dump/am/2005/0518/home/am3/rsc/.bash_history 5075
       +May 16 07:53:47 EDT 2005 /dump/am/2005/0517/home/am3/rsc/.bash_history 5065
       +.fi
       +.EE
       +.PP
       +The
       +.B -v
       +option enables verbose debugging printout.
       +.PP
       +The 
       +.B -d
       +option causes
       +.IR diff (1)
       +.B -c
       +to be run for each adjacent pair of dump files, while
       +.B -b
       +runs
       +.IR diff
       +.BR -cb .
       +.PP
       +The
       +.B -u
       +option causes times to be printed in GMT (UT) rather than local time.
       +.PP
       +Finally, the
       +.B -s
       +option
       +sets the starting (most recent) date for the output.
       +.SH EXAMPLES
       +.PP
       +Examine changes in block.c:
       +.IP
       +.EX
       +hist -d block.c
       +.EE
       +.SH FILES
       +.B /dump
       +.SH SOURCE
       +.B /home/am3/rsc/src/backup/cmd/history.c
       +.SH SEE ALSO
       +.IR yesterday (1)
       +.SH BUGS
       +Should be called
       +.IR history ,
       +but
       +that name is taken by
       +.IR sh (1).
 (DIR) diff --git a/man/man1/vbackup.1 b/man/man1/vbackup.1
       t@@ -0,0 +1,263 @@
       +.TH VBACKUP 8
       +.SH NAME
       +vbackup, vcat, vftp, vmount, vmount0, vnfs \- 
       +back up Unix file systems to Venti
       +.SH SYNOPSIS
       +.B vbackup
       +[
       +.B -DVnv
       +]
       +[
       +.B -s
       +.I secs
       +]
       +[
       +.B -w
       +.I n
       +]
       +.I disk
       +[
       +.I score
       +]
       +.PP
       +.B vcat
       +[
       +.B -z
       +]
       +.I disk
       +|
       +.I score
       +.B >
       +.I disk
       +.PP
       +.B vftp
       +.I disk
       +|
       +.I score
       +.PP
       +.B vmount
       +[
       +.B -v
       +]
       +.I addr
       +.I mtpt
       +.PP
       +.B vmount0
       +[
       +.B -v
       +]
       +[
       +.B -h
       +.I handle
       +]
       +.I addr
       +.I mtpt
       +.PP
       +.B vnfs
       +[
       +.B -LLMRVr
       +]
       +[
       +.B -a
       +.I addr
       +]
       +[
       +.B -m
       +.I mntaddr
       +]
       +[
       +.B -b
       +.I blocksize
       +]
       +[
       +.B -c
       +.I cachesize
       +]
       +.I config
       +.SH DESCRIPTION
       +These programs back up and restore standard
       +Unix file system images stored in
       +.IR venti (8).
       +Images stored in
       +.I venti
       +are named by
       +.IR scores ,
       +which consist of a file system type followed
       +by a colon and forty hexadecimal digits, as in:
       +.IP
       +.EX
       +ffs:0123456789abcdef0123456789abcdef01234567
       +.EE
       +.PP
       +(The hexadecimal data is the SHA1 hash of the Venti
       +root block representing the file system image.)
       +.PP
       +These programs expect the environment variable
       +.B $venti
       +to be set to the network address of the Venti server to use
       +(for example,
       +.B yourhost
       +or
       +.BR tcp!yourhost!venti ).
       +.PP
       +.I Vbackup
       +copies the file system stored on
       +.I disk
       +to the Venti server and prints the 
       +score for the newly-stored image.
       +The argument
       +.I disk
       +should be a disk or disk partition device
       +that would be appropriate to pass to
       +.IR mount (8).
       +.PP
       +The optional argument
       +.I score
       +is the score of a previous backup of the disk image.
       +If
       +.I score
       +is given, 
       +.I vbackup
       +will not write to Venti any blocks that have not changed
       +since the previous backup.
       +This is only a speed optimization: since the blocks are already
       +stored on Venti they need not be sent to the Venti server again.
       +.PP
       +The options to
       +.I vbackup
       +are:
       +.TP
       +.B -D
       +.TP
       +.B -V
       +.TP
       +.B -n
       +.TP
       +.B -v
       +.TP
       +.B -w \fIn
       +.TP
       +.B -s \fIsecs
       +.PP
       +.I Vcat
       +writes the named disk image to standard output.
       +Unused file system blocks are printed zeroed regardless
       +of their actual content.
       +.PP
       +If the
       +.B -z
       +flag is given, 
       +.I vcat
       +will attempt to seek over unused blocks instead of writing to them.
       +The
       +.B -z
       +flag should only be used when standard output is seekable
       +.RI ( i.e. ,
       +when it has been redirected to a file or disk).
       +.PP
       +.I Vftp
       +presents the
       +file system image named by
       +.I disk
       +or
       +.I score
       +in a shell-like
       +interactive session.
       +Type
       +.B help
       +at the
       +.B vftp>
       +prompt for details.
       +.PP
       +.I Vmount
       +mounts the NFS service at the network connection
       +.I address
       +onto
       +.IR mountpoint .
       +On most operating systems,
       +.I vmount
       +must be run by the user
       +.BR root .
       +.PP
       +.I Vmount0
       +is a simple C program that 
       +.I vmount
       +uses if 
       +.IR mount (8)
       +does not suffice.
       +.PP
       +.I Vnfs
       +serves, using the
       +NFS version 3 protocol,
       +one or more disk images in a synthetic tree defined
       +by the configuration file
       +.IR config .
       +.I Vnfs
       +announces NFS service at
       +.IR addr 
       +(default
       +.BR udp!*!nfs )
       +and NFS mount service at
       +.IR mntaddr
       +(default
       +.BR udp!*!\fI999 ),
       +registering both with the port mapper.
       +If no port mapper is found running (on port 111),
       +.I vnfs
       +starts its own port mapper.
       +The options are:
       +.TP
       +.B -r
       +Reply to all NFS requests with RPC rejections.
       +.TP
       +.B -M
       +Do not announce an NFS mount service.
       +.TP
       +.B -P
       +Do not register service with the port mapper.
       +.TP
       +.B -a
       +
       +
       +.SH EXAMPLES
       +.PP
       +Back up the file system stored on
       +.BR /dev/da0s1a :
       +.IP
       +.EX
       +% vbackup /dev/da0s1a
       +ffs:0123456789abcdef0123456789abcdef01234567
       +% 
       +.EE
       +.PP
       +Serve that backup and a few others in a tree reminiscent
       +of Plan 9's dump file system, but hide each day's contents of
       +.B /tmp :
       +.IP
       +.EX
       +% cat config
       +mount /2005/0510 ffs:0123456789abcdef\fI...\fP
       +mount /2005/0510/home ffs:0123456789abcdef\fI...\fP
       +mount /2005/0510 ffs:0123456789abcdef\fI...\fP
       +mount /2005/0510/home ffs:0123456789abcdef\fI...\fP
       +hide /*/*/tmp
       +% vnfs -m -b 16k -c 1k config
       +% 
       +.EE
       +.PP
       +Mount the backups on a client machine using
       +.IR vmount :
       +.IP
       +.EX
       +# vmount udp!yourserver!nfs /dump
       +# ls /dump
       +2005
       +# 
       +.EE
       +.PP
       +Mount the backups using the standard NFS mount program:
       +.IP
       +.EX
       +# mount -t nfs -o soft,intr,ro,nfsv3,rsize=8192,timeo=100 \
       +        -o nfsvers=3,nolock,noatime,nodev,nosuid \
       +.EE
 (DIR) diff --git a/man/man1/yesterday.1 b/man/man1/yesterday.1
       t@@ -0,0 +1,99 @@
       +.TH YESTERDAY 1
       +.SH NAME
       +yesterday \- print file names from the dump
       +.SH SYNOPSIS
       +.B yesterday
       +[
       +.B -cCd
       +] [
       +.B -n
       +.I daysago
       +] [
       +.I \-date
       +]
       +.I files ...
       +.SH DESCRIPTION
       +.I Yesterday
       +prints the names of the
       +.I files
       +from the most recent dump.
       +Since dumps are done early in the morning,
       +yesterday's files are really in today's dump.
       +For example, if today is February 11, 2003,
       +.IP
       +.EX
       +yesterday /home/am3/rsc/.profile
       +.EE
       +.PP
       +prints
       +.IP
       +.EX
       +/dump/am/2003/0211/home/am3/rsc/.profile
       +.EE
       +.PP
       +In fact, the implementation is to select the most recent dump in
       +the current year, so the dump selected may not be from today.
       +.PP
       +By default, 
       +.I yesterday
       +prints the names of the dump files corresponding to the named files.
       +The first set of options changes this behavior.
       +.TP
       +.B -c
       +Copy the dump files over the named files.
       +.TP
       +.B -C
       +Copy the dump files over the named files only when
       +they differ.
       +.TP
       +.B -d
       +Run 
       +.B diff
       +to compare the dump files with the named files.
       +.PP
       +The
       +.I date
       +option selects other day's dumps, with a format of
       +1, 2, 4, 6, or 8 digits of the form
       +.IR d,
       +.IR dd ,
       +.IR mmdd ,
       +.IR yymmdd ,
       +or
       +.IR yyyymmdd .
       +.PP
       +The
       +.B -n
       +option selects the dump
       +.I daysago
       +prior to the current day.
       +.PP
       +.I Yesterday
       +does not guarantee that the string it prints represents an existing file.
       +.SH EXAMPLES
       +.PP
       +See what's changed in the last week in your profile:
       +.IP
       +.EX
       +yesterday -d -n 7 ~/.profile
       +.EE
       +.PP
       +Restore your profile from yesterday:
       +.IP
       +.EX
       +yesterday -c ~/.profile
       +.EE
       +.SH FILES
       +.B /dump
       +.SH SOURCE
       +.B /usr/local/bin/yesterday
       +.SH SEE ALSO
       +.IR diff (1),
       +.IR hist (1)
       +.SH BUGS
       +Backups are only available on
       +.B amsterdam
       +and
       +.BR toil .
       +.PP
       +It's hard to use this command without singing.
 (DIR) diff --git a/src/cmd/vbackup/COPYRIGHT b/src/cmd/vbackup/COPYRIGHT
       t@@ -0,0 +1,27 @@
       +This software was developed as part of a project at MIT:
       +        $PLAN9/src/libdiskfs/*
       +        $PLAN9/include/diskfs.h
       +        $PLAN9/src/cmd/vbackup/*
       +
       +Copyright (c) 2005 Russ Cox,
       +                   Massachusetts Institute of Technology
       +
       +Permission is hereby granted, free of charge, to any person obtaining
       +a copy of this software and associated documentation files (the
       +"Software"), to deal in the Software without restriction, including
       +without limitation the rights to use, copy, modify, merge, publish,
       +distribute, sublicense, and/or sell copies of the Software, and to
       +permit persons to whom the Software is furnished to do so, subject to
       +the following conditions:
       +
       +The above copyright notice and this permission notice shall be
       +included in all copies or substantial portions of the Software.
       +
       +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
       +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
       +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
       +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       +
 (DIR) diff --git a/src/cmd/vbackup/config.c b/src/cmd/vbackup/config.c
       t@@ -0,0 +1,515 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <bio.h>
       +#include <thread.h>
       +#include <sunrpc.h>
       +#include <nfs3.h>
       +#include <diskfs.h>
       +#include <venti.h>
       +#include <libsec.h>
       +
       +#undef stime
       +#define stime configstime        /* sometimes in <time.h> */
       +typedef struct Entry Entry;
       +struct Entry
       +{
       +        Entry *parent;
       +        Entry *nextdir;
       +        Entry *nexthash;
       +        Entry *kids;
       +        int isfsys;
       +        Fsys *fsys;
       +        uchar score[VtScoreSize];        /* of fsys */
       +        char *name;
       +        uchar sha1[VtScoreSize];        /* of path to this entry */
       +        ulong time;
       +};
       +
       +typedef struct Config Config;
       +struct Config
       +{
       +        VtCache *vcache;
       +        Entry *root;
       +        Entry *hash[1024];
       +        Qid qid;
       +};
       +
       +Config *config;
       +static        ulong         mtime;        /* mod time */
       +static        ulong         stime;        /* sync time */
       +static        char*        configfile;
       +
       +static int addpath(Config*, char*, uchar[VtScoreSize], ulong);
       +Fsys fsysconfig;
       +
       +static void
       +freeconfig(Config *c)
       +{
       +        Entry *next, *e;
       +        int i;
       +
       +        for(i=0; i<nelem(c->hash); i++){
       +                for(e=c->hash[i]; e; e=next){
       +                        next = e->nexthash;
       +                        free(e);
       +                }
       +        }
       +        free(c);
       +}
       +
       +static int
       +namehash(uchar *s)
       +{
       +        return (s[0]<<2)|(s[1]>>6);
       +}
       +
       +static Entry*
       +entrybyhandle(Nfs3Handle *h)
       +{
       +        int hh;
       +        Entry *e;
       +
       +        hh = namehash(h->h);
       +        for(e=config->hash[hh]; e; e=e->nexthash)
       +                if(memcmp(e->sha1, h->h, VtScoreSize) == 0)
       +                        return e;
       +        return nil;
       +}
       +
       +static Config*
       +readconfigfile(char *name, VtCache *vcache)
       +{
       +        char *p, *pref, *f[10];
       +        int ok;
       +        Config *c;
       +        uchar score[VtScoreSize];
       +        int h, nf, line;
       +        Biobuf *b;
       +        Dir *dir;
       +
       +        configfile = vtstrdup(name);
       +
       +        if((dir = dirstat(name)) == nil)
       +                return nil;
       +        
       +        if((b = Bopen(name, OREAD)) == nil){
       +                free(dir);
       +                return nil;
       +        }
       +
       +        line = 0;
       +        ok = 1;
       +        c = emalloc(sizeof(Config));
       +        c->vcache = vcache;
       +        c->qid = dir->qid;
       +        free(dir);
       +        c->root = emalloc(sizeof(Entry));
       +        c->root->name = "/";
       +        c->root->parent = c->root;
       +        sha1((uchar*)"/", 1, c->root->sha1, nil);
       +        h = namehash(c->root->sha1);
       +        c->hash[h] = c->root;
       +
       +        for(; (p = Brdstr(b, '\n', 1)) != nil; free(p)){
       +                line++;
       +                if(p[0] == '#')
       +                        continue;
       +                nf = tokenize(p, f, nelem(f));
       +                if(nf != 3){
       +                        fprint(2, "%s:%d: syntax error\n", name, line);
       +                        // ok = 0;
       +                        continue;
       +                }
       +                if(vtparsescore(f[1], &pref, score) < 0){
       +                        fprint(2, "%s:%d: bad score '%s'\n", name, line, f[1]);
       +                        // ok = 0;
       +                        continue;
       +                }
       +                if(f[0][0] != '/'){
       +                        fprint(2, "%s:%d: unrooted path '%s'\n", name, line, f[0]);
       +                        // ok = 0;
       +                        continue;
       +                }
       +                if(addpath(c, f[0], score, strtoul(f[2], 0, 0)) < 0){
       +                        fprint(2, "%s:%d: %s: %r\n", name, line, f[0]);
       +                        // ok = 0;
       +                        continue;
       +                }
       +        }
       +        Bterm(b);
       +
       +        if(!ok){
       +                freeconfig(c);
       +                return nil;
       +        }
       +        
       +        return c;
       +}
       +
       +static void
       +refreshconfig(void)
       +{
       +        ulong now;
       +        Config *c, *old;
       +        Dir *d;
       +
       +        now = time(0);
       +        if(now - stime < 60)
       +                return;
       +        if((d = dirstat(configfile)) == nil)
       +                return;
       +        if(d->mtime == mtime){
       +                free(d);
       +                stime = now;
       +                return;
       +        }
       +
       +        c = readconfigfile(configfile, config->vcache);
       +        if(c == nil){
       +                free(d);
       +                return;
       +        }
       +
       +        old = config;
       +        config = c;
       +        stime = now;
       +        mtime = d->mtime;
       +        free(d);
       +        freeconfig(old);
       +}
       +
       +static Entry*
       +entrylookup(Entry *e, char *p, int np)
       +{
       +        for(e=e->kids; e; e=e->nextdir)
       +                if(strlen(e->name) == np && memcmp(e->name, p, np) == 0)
       +                        return e;
       +        return nil;
       +}
       +
       +static Entry*
       +walkpath(Config *c, char *name)
       +{
       +        Entry *e, *ee;
       +        char *p, *nextp;
       +        int h;
       +
       +        e = c->root;
       +        p = name;
       +        for(; *p; p=nextp){
       +                assert(*p == '/');
       +                p++;
       +                nextp = strchr(p, '/');
       +                if(nextp == nil)
       +                        nextp = p+strlen(p);
       +                if(e->fsys){
       +                        werrstr("%.*s is already a mount point", utfnlen(name, nextp-name), name);
       +                        return nil;
       +                }
       +                if((ee = entrylookup(e, p, nextp-p)) == nil){
       +                        ee = emalloc(sizeof(Entry)+(nextp-p)+1);
       +                        ee->parent = e;
       +                        ee->nextdir = e->kids;
       +                        e->kids = ee;
       +                        ee->name = (char*)&ee[1];
       +                        memmove(ee->name, p, nextp-p);
       +                        ee->name[nextp-p] = 0;
       +                        sha1((uchar*)name, nextp-name, ee->sha1, nil);
       +                        h = namehash(ee->sha1);
       +                        ee->nexthash = c->hash[h];
       +                        c->hash[h] = ee;
       +                }
       +                e = ee;
       +        }
       +        if(e->kids){
       +                werrstr("%s already has children; cannot be mount point", name);
       +                return nil;
       +        }
       +        return e;
       +}
       +
       +static int
       +addpath(Config *c, char *name, uchar score[VtScoreSize], ulong time)
       +{
       +        Entry *e;
       +
       +        e = walkpath(c, name);
       +        if(e == nil)
       +                return -1;
       +        e->isfsys = 1;
       +        e->time = time;
       +        memmove(e->score, score, VtScoreSize);
       +        return 0;
       +}
       +
       +static void
       +mkhandle(Nfs3Handle *h, Entry *e)
       +{
       +        memmove(h->h, e->sha1, VtScoreSize);
       +        h->len = VtScoreSize;
       +}
       +
       +Nfs3Status
       +handleparse(Nfs3Handle *h, Fsys **pfsys, Nfs3Handle *nh, int isgetattr)
       +{
       +        int hh;
       +        Entry *e;
       +        Disk *disk;
       +        Fsys *fsys;
       +
       +        refreshconfig();
       +
       +        if(h->len < VtScoreSize)
       +                return Nfs3ErrBadHandle;
       +
       +        hh = namehash(h->h);
       +        for(e=config->hash[hh]; e; e=e->nexthash)
       +                if(memcmp(e->sha1, h->h, VtScoreSize) == 0)
       +                        break;
       +        if(e == nil)
       +                return Nfs3ErrBadHandle;
       +
       +        if(e->isfsys == 1 && e->fsys == nil && (h->len != VtScoreSize || !isgetattr)){
       +                if((disk = diskopenventi(config->vcache, e->score)) == nil){
       +                        fprint(2, "cannot open disk %V: %r\n", e->score);
       +                        return Nfs3ErrIo;
       +                }
       +                if((fsys = fsysopen(disk)) == nil){
       +                        fprint(2, "cannot open fsys on %V: %r\n", e->score);
       +                        diskclose(disk);
       +                        return Nfs3ErrIo;
       +                }
       +                e->fsys = fsys;
       +        }
       +
       +        if(e->fsys == nil || (isgetattr && h->len == VtScoreSize)){
       +                if(h->len != VtScoreSize)
       +                        return Nfs3ErrBadHandle;
       +                *pfsys = &fsysconfig;
       +                *nh = *h;
       +                return Nfs3Ok;
       +        }
       +        *pfsys = e->fsys;
       +        if(h->len == VtScoreSize)
       +                return fsysroot(*pfsys, nh);
       +        nh->len = h->len - VtScoreSize;
       +        memmove(nh->h, h->h+VtScoreSize, nh->len);
       +        return Nfs3Ok;
       +}
       +
       +void
       +handleunparse(Fsys *fsys, Nfs3Handle *h, Nfs3Handle *nh, int dotdot)
       +{
       +        Entry *e;
       +        int hh;
       +
       +        refreshconfig();
       +
       +        if(fsys == &fsysconfig)
       +                return;
       +
       +        if(dotdot && nh->len == h->len - VtScoreSize
       +        && memcmp(h->h+VtScoreSize, nh->h, nh->len) == 0){
       +                /* walked .. but didn't go anywhere: must be at root */
       +                hh = namehash(h->h);
       +                for(e=config->hash[hh]; e; e=e->nexthash)
       +                        if(memcmp(e->sha1, h->h, VtScoreSize) == 0)
       +                                break;
       +                if(e == nil)
       +                        return;        /* cannot happen */
       +
       +                /* walk .. */
       +                e = e->parent;
       +                nh->len = VtScoreSize;
       +                memmove(nh->h, e->sha1, VtScoreSize);
       +                return;
       +        }
       +
       +        /* otherwise just insert the same prefix */
       +        memmove(nh->h+VtScoreSize, nh->h, VtScoreSize);
       +        nh->len += VtScoreSize;
       +        memmove(nh->h, h->h, VtScoreSize);
       +}
       +
       +Nfs3Status
       +fsysconfigroot(Fsys *fsys, Nfs3Handle *h)
       +{
       +        USED(fsys);
       +
       +        mkhandle(h, config->root);
       +        return Nfs3Ok;
       +}
       +
       +Nfs3Status
       +fsysconfiggetattr(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, Nfs3Attr *attr)
       +{
       +        Entry *e;
       +
       +        USED(fsys);
       +        USED(au);
       +
       +        if(h->len != VtScoreSize)
       +                return Nfs3ErrBadHandle;
       +
       +        e = entrybyhandle(h);
       +        if(e == nil)
       +                return Nfs3ErrNoEnt;
       +
       +        memset(attr, 0, sizeof *attr);
       +        attr->type = Nfs3FileDir;
       +        attr->mode = 0555;
       +        attr->nlink = 2;
       +        attr->size = 1024;
       +        attr->fileid = *(u64int*)h->h;
       +        attr->atime.sec = e->time;
       +        attr->mtime.sec = e->time;
       +        attr->ctime.sec = e->time;
       +        return Nfs3Ok;
       +}
       +
       +Nfs3Status
       +fsysconfigaccess(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int want, u32int *got, Nfs3Attr *attr)
       +{
       +        want &= Nfs3AccessRead|Nfs3AccessLookup|Nfs3AccessExecute;
       +        *got = want;
       +        return fsysconfiggetattr(fsys, au, h, attr);
       +}
       +
       +Nfs3Status
       +fsysconfiglookup(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, char *name, Nfs3Handle *nh)
       +{
       +        Entry *e;
       +
       +        USED(fsys);
       +        USED(au);
       +
       +        if(h->len != VtScoreSize)
       +                return Nfs3ErrBadHandle;
       +
       +        e = entrybyhandle(h);
       +        if(e == nil)
       +                return Nfs3ErrNoEnt;
       +
       +        if(strcmp(name, "..") == 0)
       +                e = e->parent;
       +        else if(strcmp(name, ".") == 0){
       +                /* nothing */
       +        }else{
       +                if((e = entrylookup(e, name, strlen(name))) == nil)
       +                        return Nfs3ErrNoEnt;
       +        }
       +
       +        mkhandle(nh, e);
       +        return Nfs3Ok;
       +}
       +
       +Nfs3Status
       +fsysconfigreadlink(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, char **link)
       +{
       +        USED(h);
       +        USED(fsys);
       +        USED(au);
       +
       +        *link = 0;
       +        return Nfs3ErrNotSupp;
       +}
       +
       +Nfs3Status
       +fsysconfigreadfile(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int offset, uchar **pdata, u32int *pcount, u1int *peof)
       +{
       +        USED(fsys);
       +        USED(h);
       +        USED(count);
       +        USED(offset);
       +        USED(pdata);
       +        USED(pcount);
       +        USED(peof);
       +        USED(au);
       +
       +        return Nfs3ErrNotSupp;
       +}
       +
       +Nfs3Status
       +fsysconfigreaddir(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int cookie, uchar **pdata, u32int *pcount, u1int *peof)
       +{
       +        uchar *data, *p, *ep, *np;
       +        u64int c;
       +        Entry *e;
       +        Nfs3Entry ne;
       +
       +        USED(fsys);
       +        USED(au);
       +
       +        if(h->len != VtScoreSize)
       +                return Nfs3ErrBadHandle;
       +
       +        e = entrybyhandle(h);
       +        if(e == nil)
       +                return Nfs3ErrNoEnt;
       +
       +        e = e->kids;
       +        c = cookie;
       +        for(; c && e; c--)
       +                e = e->nextdir;
       +        if(e == nil){
       +                *pdata = 0;
       +                *pcount = 0;
       +                *peof = 1;
       +                return Nfs3Ok;
       +        }
       +
       +        data = emalloc(count);
       +        p = data;
       +        ep = data+count;
       +        while(e && p < ep){
       +                ne.name = e->name;
       +                ne.cookie = ++cookie;
       +                ne.fileid = *(u64int*)e->sha1;
       +                if(nfs3entrypack(p, ep, &np, &ne) < 0)
       +                        break;
       +                p = np;
       +                e = e->nextdir;
       +        }
       +        *pdata = data;
       +        *pcount = p - data;
       +        *peof = 0;
       +        return Nfs3Ok;
       +}
       +
       +void
       +fsysconfigclose(Fsys *fsys)
       +{
       +        USED(fsys);
       +}
       +
       +int
       +readconfig(char *name, VtCache *vcache, Nfs3Handle *h)
       +{
       +        Config *c;
       +        Dir *d;
       +
       +        if((d = dirstat(name)) == nil)
       +                return -1;
       +
       +        c = readconfigfile(name, vcache);
       +        if(c == nil){
       +                free(d);
       +                return -1;
       +        }
       +
       +        config = c;
       +        mtime = d->mtime;
       +        stime = time(0);
       +        free(d);
       +
       +        mkhandle(h, c->root);
       +        fsysconfig._lookup = fsysconfiglookup;
       +        fsysconfig._access = fsysconfigaccess;
       +        fsysconfig._getattr = fsysconfiggetattr;
       +        fsysconfig._readdir = fsysconfigreaddir;
       +        fsysconfig._readfile = fsysconfigreadfile;
       +        fsysconfig._readlink = fsysconfigreadlink;
       +        fsysconfig._root = fsysconfigroot;
       +        fsysconfig._close = fsysconfigclose;
       +        return 0;
       +}
 (DIR) diff --git a/src/cmd/vbackup/diskcat.c b/src/cmd/vbackup/diskcat.c
       t@@ -0,0 +1,54 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <diskfs.h>
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: fscat fspartition\n");
       +        exits("usage");
       +}
       +
       +int
       +main(int argc, char **argv)
       +{
       +        extern int nfilereads;
       +        u8int *zero;
       +        u32int i;
       +        u32int n;
       +        Block *b;
       +        Disk *disk;
       +        Fsys *fsys;
       +
       +        ARGBEGIN{
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        if(argc != 1)
       +                usage();
       +
       +        if((disk = diskopenfile(argv[0])) == nil)
       +                sysfatal("diskopen: %r");
       +        if((disk = diskcache(disk, 16384, 16)) == nil)
       +                sysfatal("diskcache: %r");
       +        if((fsys = fsysopen(disk)) == nil)
       +                sysfatal("ffsopen: %r");
       +
       +        zero = emalloc(fsys->blocksize);
       +        fprint(2, "%d blocks total\n", fsys->nblock);
       +        n = 0;
       +        for(i=0; i<fsys->nblock; i++){
       +                if((b = fsysreadblock(fsys, i)) != nil){
       +                        write(1, b->data, fsys->blocksize);
       +                        n++;
       +                        blockput(b);
       +                }else
       +                        write(1, zero, fsys->blocksize);
       +                if(b == nil && i < 2)
       +                        sysfatal("block %d not in use", i);
       +        }
       +        fprint(2, "%d blocks in use, %d file reads\n", n, nfilereads);
       +        exits(nil);
       +        return 0;
       +}
 (DIR) diff --git a/src/cmd/vbackup/diskftp.c b/src/cmd/vbackup/diskftp.c
       t@@ -0,0 +1,134 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <thread.h>
       +#include <sunrpc.h>
       +#include <nfs3.h>
       +#include <diskfs.h>
       +
       +int debug;
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: fsview fspartition cmd\n");
       +        fprint(2, "cmd is:\n");
       +        fprint(2, "\tcat file\n");
       +        fprint(2, "\tls dir\n");
       +        fprint(2, "\tstat file\n");
       +        exits("usage");
       +}
       +
       +void
       +printattr(Nfs3Attr *attr)
       +{
       +        Fmt fmt;
       +        char buf[256];
       +
       +        fmtfdinit(&fmt, 1, buf, sizeof buf);
       +        nfs3attrprint(&fmt, attr);
       +        fmtfdflush(&fmt);
       +        print("\n");
       +}
       +
       +char buf[8192];
       +
       +void
       +x(int ok)
       +{
       +        if(ok != Nfs3Ok){
       +                nfs3errstr(ok);
       +                sysfatal("%r");
       +        }
       +}
       +
       +void
       +threadmain(int argc, char **argv)
       +{
       +        char *p, *q;
       +        u32int n;
       +        Disk *disk;
       +        Fsys *fsys;
       +        Nfs3Handle h;
       +        SunAuthUnix au;
       +        Nfs3Attr attr;
       +        u64int offset;
       +        u1int eof;
       +        uchar *data;
       +        char *link;
       +
       +        ARGBEGIN{
       +        case 'd':
       +                debug = 1;
       +                break;
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        if(argc != 3)
       +                usage();
       +
       +        if((disk = diskopenfile(argv[0])) == nil)
       +                sysfatal("diskopen: %r");
       +        if((disk = diskcache(disk, 16384, 16)) == nil)
       +                sysfatal("diskcache: %r");
       +        if((fsys = fsysopen(disk)) == nil)
       +                sysfatal("ffsopen: %r");
       +
       +        allowall = 1;
       +        memset(&au, 0, sizeof au);
       +
       +        /* walk */
       +        if(debug) fprint(2, "get root...");
       +        x(fsysroot(fsys, &h));
       +        p = argv[2];
       +        while(*p){
       +                while(*p == '/')
       +                        p++;
       +                if(*p == 0)
       +                        break;
       +                q = strchr(p, '/');
       +                if(q){
       +                        *q = 0;
       +                        q++;
       +                }else
       +                        q = "";
       +                if(debug) fprint(2, "walk %s...", p);
       +                x(fsyslookup(fsys, &au, &h, p, &h));
       +                p = q;
       +        }
       +
       +        if(debug) fprint(2, "getattr...");
       +        x(fsysgetattr(fsys, &au, &h, &attr));
       +        printattr(&attr);
       +
       +        /* do the op */
       +        if(strcmp(argv[1], "cat") == 0){
       +                switch(attr.type){
       +                case Nfs3FileReg:
       +                        offset = 0;
       +                        for(;;){
       +                                x(fsysreadfile(fsys, &au, &h, sizeof buf, offset, &data, &n, &eof));
       +                                if(n){
       +                                        write(1, data, n);
       +                                        free(data);
       +                                        offset += n;
       +                                }
       +                                if(eof)
       +                                        break;
       +                        }
       +                        break;
       +                case Nfs3FileSymlink:
       +                        x(fsysreadlink(fsys, &au, &h, &link));
       +                        print("%s\n", link);
       +                        break;
       +                default:
       +                        print("cannot cat: not file, not link\n");
       +                        break;
       +                }
       +        }else if(strcmp(argv[1], "ls") == 0){
       +                /* not implemented */
       +        }else if(strcmp(argv[1], "stat") == 0){
       +                /* already done */
       +        }
       +        threadexitsall(nil);
       +}
 (DIR) diff --git a/src/cmd/vbackup/disknfs.c b/src/cmd/vbackup/disknfs.c
       t@@ -0,0 +1,126 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <ip.h>
       +#include <thread.h>
       +#include <sunrpc.h>
       +#include <nfs3.h>
       +#include <diskfs.h>
       +#include "nfs3srv.h"
       +
       +Disk *disk;
       +Fsys *fsys;
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: disknfs [-RTr] disk\n");
       +        threadexitsall("usage");
       +}
       +
       +extern int _threaddebuglevel;
       +
       +void
       +threadmain(int argc, char **argv)
       +{
       +        char *addr;
       +        SunSrv *srv;
       +        Channel *nfs3chan;
       +        Channel *mountchan;
       +        Nfs3Handle h;
       +
       +        fmtinstall('B', sunrpcfmt);
       +        fmtinstall('C', suncallfmt);
       +        fmtinstall('H', encodefmt);
       +        fmtinstall('I', eipfmt);
       +        sunfmtinstall(&nfs3prog);
       +        sunfmtinstall(&nfsmount3prog);
       +
       +        srv = sunsrv();
       +        addr = "*";
       +
       +        ARGBEGIN{
       +        case 'R':
       +                srv->chatty++;
       +                break;
       +        case 'T':
       +                _threaddebuglevel = 0xFFFFFFFF;
       +                break;
       +        case 'r':
       +                srv->alwaysreject++;
       +                break;
       +        }ARGEND
       +
       +        if(argc != 1 && argc != 2)
       +                usage();
       +
       +        if((disk = diskopenfile(argv[0])) == nil)
       +                sysfatal("diskopen: %r");
       +        if((disk = diskcache(disk, 16384, 256)) == nil)
       +                sysfatal("diskcache: %r");
       +
       +        if((fsys = fsysopen(disk)) == nil)
       +                sysfatal("ffsopen: %r");
       +
       +        nfs3chan = chancreate(sizeof(SunMsg*), 0);
       +        mountchan = chancreate(sizeof(SunMsg*), 0);
       +
       +        if(argc > 1)
       +                addr = argv[1];
       +        addr = netmkaddr(addr, "udp", "2049");
       +
       +        if(sunsrvudp(srv, addr) < 0)
       +                sysfatal("starting server: %r");
       +        sunsrvprog(srv, &nfs3prog, nfs3chan);
       +        sunsrvprog(srv, &nfsmount3prog, mountchan);
       +
       +        sunsrvthreadcreate(srv, nfs3proc, nfs3chan);
       +        sunsrvthreadcreate(srv, mount3proc, mountchan);
       +
       +        fsgetroot(&h);
       +        print("mountbackups -h %.*H %s /mountpoint\n", h.len, h.h, addr);
       +
       +        threadexits(nil);
       +}
       +
       +void
       +fsgetroot(Nfs3Handle *h)
       +{
       +        fsysroot(fsys, h);
       +}
       +
       +Nfs3Status
       +fsgetattr(SunAuthUnix *au, Nfs3Handle *h, Nfs3Attr *attr)
       +{
       +        return fsysgetattr(fsys, au, h, attr);
       +}
       +
       +Nfs3Status
       +fslookup(SunAuthUnix *au, Nfs3Handle *h, char *name, Nfs3Handle *nh)
       +{
       +        return fsyslookup(fsys, au, h, name, nh);
       +}
       +
       +Nfs3Status
       +fsaccess(SunAuthUnix *au, Nfs3Handle *h, u32int want, u32int *got, Nfs3Attr *attr)
       +{
       +        return fsysaccess(fsys, au, h, want, got, attr);
       +}
       +
       +Nfs3Status
       +fsreadlink(SunAuthUnix *au, Nfs3Handle *h, char **link)
       +{
       +        return fsysreadlink(fsys, au, h, link);
       +}
       +
       +Nfs3Status
       +fsreadfile(SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
       +{
       +        return fsysreadfile(fsys, au, h, count, offset, data, pcount, peof);
       +}
       +
       +Nfs3Status
       +fsreaddir(SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int cookie, uchar **data, u32int *pcount, u1int *peof)
       +{
       +        return fsysreaddir(fsys, au, h, count, cookie, data, pcount, peof);
       +}
       +
 (DIR) diff --git a/src/cmd/vbackup/hist.c b/src/cmd/vbackup/hist.c
       t@@ -0,0 +1,282 @@
       +#include        <u.h>
       +#include        <libc.h>
       +#include        <ctype.h>
       +
       +#define        MINUTE(x)        ((long)(x)*60L)
       +#define        HOUR(x)                (MINUTE(x)*60L)
       +#define        YEAR(x)                (HOUR(x)*24L*360L)
       +
       +int        verb;
       +int        uflag;
       +int        force;
       +int        diff;
       +int        diffb;
       +char*        sflag;
       +char *sys;
       +
       +void        ysearch(char*);
       +long        starttime(char*);
       +void        lastbefore(ulong, char*, char*, char*);
       +char*        prtime(ulong);
       +
       +int
       +main(int argc, char *argv[])
       +{
       +        int i;
       +        
       +        sys = sysname();
       +        if(strncmp(sys, "amsterdam", 9) == 0)
       +                sys = "am";
       +        else if(strncmp(sys, "toil", 4) == 0)
       +                sys = "toil";
       +
       +        ARGBEGIN {
       +        default:
       +                goto usage;
       +        case 'v':
       +                verb = 1;
       +                break;
       +        case 'f':
       +                force = 1;
       +                break;
       +        case 'd':
       +                diff = 1;
       +                break;
       +        case 'b':
       +                diffb = 1;
       +                break;
       +        case 's':
       +                sflag = ARGF();
       +                break;
       +        case 'u':
       +                uflag = 1;
       +                break;
       +        } ARGEND
       +
       +        if(argc == 0) {
       +        usage:
       +                fprint(2, "usage: hist [-bdfuv] [-s yyyymmdd] files\n");
       +                exits(0);
       +        }
       +
       +        for(i=0; i<argc; i++)
       +                ysearch(argv[i]);
       +        exits(0);
       +        return 0;
       +}
       +
       +int
       +strprefix(char *a, char *aa)
       +{
       +        return memcmp(a, aa, strlen(a)) == 0;
       +}
       +
       +void
       +ysearch(char *file)
       +{
       +        char *ndump;
       +        char fil[400], buf[500], nbuf[100], pair[2][500], *p;
       +        Tm *tm;
       +        Dir *dir, *d;
       +        ulong otime, dt;
       +        int toggle, started, missing;
       +
       +        started = 0;
       +        dir = dirstat(file);
       +        if(dir == nil)
       +                fprint(2, "history: warning: %s does not exist\n", file);
       +        else{
       +                print("%s %s %lld\n", prtime(dir->mtime), file, dir->length);
       +                started = 1;
       +                strcpy(pair[1], file);
       +        }
       +        free(dir);
       +        fil[0] = 0;
       +        if(file[0] != '/') {
       +                getwd(strchr(fil, 0), 100);
       +                strcat(fil, "/");
       +        }
       +        strcat(fil, file);
       +        cleanname(fil);
       +
       +        sprint(nbuf, "/dump/%s", sys);
       +        ndump = nbuf;
       +
       +        tm = localtime(time(0));
       +        sprint(buf, "%s/%.4d/", ndump, tm->year+1900);
       +        if(access(buf, AREAD) < 0){
       +                print("cannot access %s\n", buf);
       +                return;
       +        }
       +
       +        otime = starttime(sflag);
       +        toggle = 0;
       +        for(;;) {
       +                lastbefore(otime, fil, buf, ndump);
       +                dir = dirstat(buf);
       +                if(dir == nil) {
       +                        if(!force)
       +                                return;
       +                        dir = malloc(sizeof(Dir));
       +                        nulldir(dir);
       +                        dir->mtime = otime + 1;
       +                }
       +                dt = HOUR(12);
       +                missing = 0;
       +                while(otime <= dir->mtime){
       +                        if(verb)
       +                                print("backup %ld, %ld\n", dir->mtime, otime-dt);
       +                        lastbefore(otime-dt, fil, buf, ndump);
       +                        d = dirstat(buf);
       +                        if(d == nil){
       +                                if(!force)
       +                                        return;
       +                                if(!missing)
       +                                        print("removed %s\n", buf);
       +                                missing = 1;
       +                        }else{
       +                                free(dir);
       +                                dir = d;
       +                        }
       +                        dt += HOUR(12);
       +                }
       +                strcpy(pair[toggle], buf);
       +                if(diff && started){
       +                        if(verb)
       +                                print("diff %s %s\n", pair[toggle^1], pair[toggle]);
       +                        switch(rfork(RFFDG|RFPROC)){
       +                        case 0:
       +                                execlp("diff", "diff", diffb ? "-cb" : "-c", pair[toggle], pair[toggle ^ 1], 0);
       +                                fprint(2, "can't exec diff: %r\n");
       +                                exits(0);
       +                        case -1:
       +                                fprint(2, "can't fork diff: %r\n");
       +                                break;
       +                        default:
       +                                while(waitpid() != -1)
       +                                        ;
       +                                break;
       +                        }
       +                }
       +                print("%s %s %lld\n", prtime(dir->mtime), buf, dir->length);
       +                toggle ^= 1;
       +                started = 1;
       +                otime = dir->mtime;
       +                free(dir);
       +        }
       +}
       +
       +void
       +lastbefore(ulong t, char *f, char *b, char *ndump)
       +{
       +        Tm *tm;
       +        Dir *dir;
       +        int vers, try;
       +        ulong t0, mtime;
       +
       +        t0 = t;
       +        if(verb)
       +                print("%ld lastbefore %s\n", t0, f);
       +        mtime = 0;
       +        for(try=0; try<10; try++) {
       +                tm = localtime(t);
       +                sprint(b, "%s/%.4d/%.2d%.2d", ndump,
       +                        tm->year+1900, tm->mon+1, tm->mday);
       +                dir = dirstat(b);
       +                if(dir){
       +                        mtime = dir->mtime;
       +                        free(dir);
       +                }
       +                if(dir==nil || mtime > t0) {
       +                        if(verb)
       +                                print("%ld earlier %s\n", mtime, b);
       +                        t -= HOUR(24);
       +                        continue;
       +                }
       +                for(vers=0;; vers++) {
       +                        sprint(b, "%s/%.4d/%.2d%.2d%d", ndump,
       +                                tm->year+1900, tm->mon+1, tm->mday, vers+1);
       +                        dir = dirstat(b);
       +                        if(dir){
       +                                mtime = dir->mtime;
       +                                free(dir);
       +                        }
       +                        if(dir==nil || mtime > t0)
       +                                break;
       +                        if(verb)
       +                                print("%ld later %s\n", mtime, b);
       +                }
       +                sprint(b, "%s/%.4d/%.2d%.2d%s", ndump,
       +                        tm->year+1900, tm->mon+1, tm->mday, f);
       +                if(vers)
       +                        sprint(b, "%s/%.4d/%.2d%.2d%d%s", ndump,
       +                                tm->year+1900, tm->mon+1, tm->mday, vers, f);
       +                return;
       +        }
       +        strcpy(b, "XXX");        /* error */
       +}
       +
       +char*
       +prtime(ulong t)
       +{
       +        static char buf[100];
       +        char *b;
       +        Tm *tm;
       +
       +        if(uflag)
       +                tm = gmtime(t);
       +        else
       +                tm = localtime(t);
       +        b = asctime(tm);
       +        memcpy(buf, b+4, 24);
       +        buf[24] = 0;
       +        return buf;
       +}
       +
       +long
       +starttime(char *s)
       +{
       +        Tm *tm;
       +        long t, dt;
       +        int i, yr, mo, da;
       +
       +        t = time(0);
       +        if(s == 0)
       +                return t;
       +        for(i=0; s[i]; i++)
       +                if(s[i] < '0' || s[i] > '9') {
       +                        fprint(2, "bad start time: %s\n", s);
       +                        return t;
       +                }
       +        if(strlen(s)==6){
       +                yr = (s[0]-'0')*10 + s[1]-'0';
       +                mo = (s[2]-'0')*10 + s[3]-'0' - 1;
       +                da = (s[4]-'0')*10 + s[5]-'0';
       +                if(yr < 70)
       +                        yr += 100;
       +        }else if(strlen(s)==8){
       +                yr = (((s[0]-'0')*10 + s[1]-'0')*10 + s[2]-'0')*10 + s[3]-'0';
       +                yr -= 1900;
       +                mo = (s[4]-'0')*10 + s[5]-'0' - 1;
       +                da = (s[6]-'0')*10 + s[7]-'0';
       +        }else{
       +                fprint(2, "bad start time: %s\n", s);
       +                return t;
       +        }
       +        t = 0;
       +        dt = YEAR(10);
       +        for(i=0; i<50; i++) {
       +                tm = localtime(t+dt);
       +                if(yr > tm->year ||
       +                  (yr == tm->year && mo > tm->mon) ||
       +                  (yr == tm->year && mo == tm->mon) && da > tm->mday) {
       +                        t += dt;
       +                        continue;
       +                }
       +                dt /= 2;
       +                if(dt == 0)
       +                        break;
       +        }
       +        t += HOUR(12);        /* .5 day to get to noon of argument */
       +        return t;
       +}
 (DIR) diff --git a/src/cmd/vbackup/mkfile b/src/cmd/vbackup/mkfile
       t@@ -0,0 +1,28 @@
       +<$PLAN9/src/mkhdr
       +CC=9c
       +
       +TARG=\
       +        disknfs\
       +        vbackup\
       +        vcat\
       +        vmount0\
       +        vnfs\
       +
       +OFILES=util.$O
       +HFILES=$PLAN9/include/diskfs.h
       +
       +<$PLAN9/src/mkmany
       +
       +disknfs.$O: nfs3srv.h
       +mount-%.$O: mountnfs.h
       +nfs3srv.$O: nfs3srv.h
       +queue.$O: queue.h
       +vbackup.$O: queue.h
       +vmount0.$O: mountnfs.h
       +vnfs.$O: nfs3srv.h
       +
       +$O.disknfs: nfs3srv.$O
       +$O.vbackup: vbackup.$O queue.$O
       +$O.vmount0: vmount0.$O mount-$SYSNAME.$O
       +$O.vnfs: nfs3srv.$O
       +
 (DIR) diff --git a/src/cmd/vbackup/mount-FreeBSD.c b/src/cmd/vbackup/mount-FreeBSD.c
       t@@ -0,0 +1,52 @@
       +#include <u.h>
       +#include <sys/socket.h>
       +#include <netinet/in.h>
       +#include <netdb.h>
       +#include <sys/stat.h>
       +#include <sys/param.h>
       +#include <sys/mount.h>
       +#include <sys/syslog.h>
       +#include <rpc/rpc.h>
       +#include <rpc/pmap_clnt.h>
       +#include <rpc/pmap_prot.h>
       +#include <nfs/rpcv2.h>
       +#include <nfs/nfsproto.h>
       +#include <nfs/nfs.h>
       +#include <libc.h>
       +#include "mountnfs.h"
       +
       +void
       +mountnfs(int proto, struct sockaddr_in *sa,
       +        uchar *handle, int nhandle, char *mtpt)
       +{
       +        int mflag;
       +        struct nfs_args na;
       +
       +        memset(&na, 0, sizeof na);
       +        na.version = NFS_ARGSVERSION;
       +        na.addr = (struct sockaddr*)sa;
       +        na.addrlen = sizeof *sa;
       +        na.sotype = proto;
       +        na.proto = (proto == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP;
       +        na.fh = handle;
       +        na.fhsize = nhandle;
       +        na.flags = NFSMNT_RESVPORT|NFSMNT_NFSV3|NFSMNT_INT;
       +        na.wsize = NFS_WSIZE;
       +        na.rsize = NFS_RSIZE;
       +        na.readdirsize = NFS_READDIRSIZE;
       +        na.timeo = 2;
       +        na.retrans = NFS_RETRANS;
       +        na.maxgrouplist = NFS_MAXGRPS;
       +        na.readahead = 0;
       +        na.leaseterm = 0;
       +        na.deadthresh = 0;
       +        na.hostname = "backup";
       +        na.acregmin = 60;
       +        na.acregmax = 600;
       +        na.acdirmin = 60;
       +        na.acdirmax = 600;
       +
       +        mflag = MNT_RDONLY|MNT_NOSUID|MNT_NOATIME|MNT_NODEV;
       +        if(mount("nfs", mtpt, mflag, &na) < 0)
       +                sysfatal("mount: %r");
       +}
 (DIR) diff --git a/src/cmd/vbackup/mount-Linux.c b/src/cmd/vbackup/mount-Linux.c
       t@@ -0,0 +1,58 @@
       +#include <u.h>
       +#include <sys/socket.h>
       +#include <sys/mount.h>
       +#ifdef __Linux24__
       +#        define __KERNEL__
       +#        include <linux/nfs.h>
       +#        undef __KERNEL__
       +#else
       +#        include <linux/nfs.h>
       +#endif
       +#include <linux/nfs2.h>
       +#include <linux/nfs_mount.h>
       +#include <libc.h>
       +#include "mountnfs.h"
       +
       +void
       +mountnfs(int proto, struct sockaddr_in *sa, uchar *handle, int nhandle, char *mtpt)
       +{
       +        int mflag, fd;
       +        struct nfs_mount_data nfs;
       +
       +        fd = socket(AF_INET, proto, proto==SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP);
       +        if(fd < 0)
       +                sysfatal("socket: %r");
       +
       +        memset(&nfs, 0, sizeof nfs);
       +        nfs.version = NFS_MOUNT_VERSION;
       +        nfs.fd = fd;
       +        nfs.flags =
       +                NFS_MOUNT_SOFT|
       +                NFS_MOUNT_INTR|
       +                NFS_MOUNT_NOAC|
       +                NFS_MOUNT_NOCTO|
       +                NFS_MOUNT_VER3|
       +                NFS_MOUNT_NONLM;
       +        if(proto==SOCK_STREAM)
       +                nfs.flags |= NFS_MOUNT_TCP;
       +        nfs.rsize = 8192;
       +        nfs.wsize = 8192;
       +        nfs.timeo = 120;
       +        nfs.retrans = 2;
       +        nfs.acregmin = 60;
       +        nfs.acregmax = 600;
       +        nfs.acdirmin = 60;
       +        nfs.acdirmax = 600;
       +        nfs.addr = *sa;
       +        strcpy(nfs.hostname, "backup");
       +        nfs.namlen = 1024;
       +        nfs.bsize = 8192;
       +        memcpy(nfs.root.data, handle, nhandle);
       +        nfs.root.size = nhandle;
       +        mflag = MS_NOATIME|MS_NODEV|MS_NODIRATIME|
       +                MS_NOEXEC|MS_NOSUID|MS_RDONLY;
       +
       +        if(mount("backup:/", mtpt, "nfs", mflag, &nfs) < 0)
       +                sysfatal("mount: %r");
       +}
       +
 (DIR) diff --git a/src/cmd/vbackup/mount-none.c b/src/cmd/vbackup/mount-none.c
       t@@ -0,0 +1,12 @@
       +#include <u.h>
       +#include <sys/socket.h>
       +#include <netinet/in.h>
       +#include <libc.h>
       +#include "mountnfs.h"
       +
       +void
       +mountnfs(int proto, struct sockaddr_in *addr, uchar *handle, int hlen, char *mtpt)
       +{
       +        sysfatal("mountnfs not implemented");
       +}
       +
 (DIR) diff --git a/src/cmd/vbackup/mountnfs.h b/src/cmd/vbackup/mountnfs.h
       t@@ -0,0 +1 @@
       +void mountnfs(int proto, struct sockaddr_in*, uchar*, int, char*);
 (DIR) diff --git a/src/cmd/vbackup/nfs3srv.c b/src/cmd/vbackup/nfs3srv.c
       t@@ -0,0 +1,428 @@
       +/*
       + * Simple read-only NFS v3 server.
       + * Runs every request in its own thread.
       + * Expects client to provide the fsxxx routines in nfs3srv.h.
       + */
       +#include <u.h>
       +#include <libc.h>
       +#include <thread.h>
       +#include <sunrpc.h>
       +#include <nfs3.h>
       +#include "nfs3srv.h"
       +
       +static SunStatus
       +authunixunpack(SunRpc *rpc, SunAuthUnix *au)
       +{
       +        uchar *p, *ep;
       +        SunAuthInfo *ai;
       +
       +        ai = &rpc->cred;
       +        if(ai->flavor != SunAuthSys)
       +                return SunAuthTooWeak;
       +        p = ai->data;
       +        ep = p+ai->ndata;
       +        if(sunauthunixunpack(p, ep, &p, au) < 0)
       +                return SunGarbageArgs;
       +        if(au->uid == 0)
       +                au->uid = -1;
       +        if(au->gid == 0)
       +                au->gid = -1;
       +
       +        return SunSuccess;
       +}
       +
       +static int
       +rnull(SunMsg *m)
       +{
       +        NfsMount3RNull rx;
       +
       +        memset(&rx, 0, sizeof rx);
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rmnt(SunMsg *m)
       +{
       +        Nfs3Handle nh;
       +        NfsMount3RMnt rx;
       +        SunAuthUnix au;
       +        int ok;
       +
       +        if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
       +                return sunmsgreplyerror(m, ok);
       +
       +        /* ignore file system path and return the dump tree */
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.nauth = 0;
       +        rx.status = 0;
       +        memset(&nh, 0, sizeof nh);
       +        fsgetroot(&nh);
       +        rx.handle = nh.h;
       +        rx.len = nh.len;
       +
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rumnt(SunMsg *m)
       +{
       +        NfsMount3RUmnt rx;
       +
       +        /* ignore */
       +        
       +        memset(&rx, 0, sizeof rx);
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rumntall(SunMsg *m)
       +{
       +        NfsMount3RUmntall rx;
       +
       +        /* ignore */
       +
       +        memset(&rx, 0, sizeof rx);
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rexport(SunMsg *m)
       +{
       +        NfsMount3RExport rx;
       +
       +        /* ignore */
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.count = 0;
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static void
       +rmount3(void *v)
       +{
       +        SunMsg *m;
       +
       +        m = v;
       +        switch(m->call->type){
       +        default:
       +                sunmsgreplyerror(m, SunProcUnavail);
       +        case NfsMount3CallTNull:
       +                rnull(m);
       +                break;
       +        case NfsMount3CallTMnt:
       +                rmnt(m);
       +                break;
       +        case NfsMount3CallTDump:
       +                rmnt(m);
       +                break;
       +        case NfsMount3CallTUmnt:
       +                rumnt(m);
       +                break;
       +        case NfsMount3CallTUmntall:
       +                rumntall(m);
       +                break;
       +        case NfsMount3CallTExport:
       +                rexport(m);
       +                break;
       +        }
       +}
       +
       +void
       +mount3proc(void *v)
       +{
       +        Channel *c;
       +        SunMsg *m;
       +
       +        threadsetname("mount1");
       +        c = v;
       +        while((m=recvp(c)) != nil)
       +                threadcreate(rmount3, m, SunStackSize);
       +}
       +
       +static int
       +senderror(SunMsg *m, SunCall *rc, Nfs3Status status)
       +{
       +        /* knows that status is first field in all replies */
       +        ((Nfs3RGetattr*)rc)->status = status;
       +        return sunmsgreply(m, rc);
       +}
       +
       +static int
       +rnull0(SunMsg *m)
       +{
       +        Nfs3RNull rx;
       +
       +        memset(&rx, 0, sizeof rx);
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rgetattr(SunMsg *m)
       +{
       +        Nfs3TGetattr *tx = (Nfs3TGetattr*)m->call;
       +        Nfs3RGetattr rx;
       +        SunAuthUnix au;
       +        int ok;
       +
       +        if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
       +                return sunmsgreplyerror(m, ok);
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.status = fsgetattr(&au, &tx->handle, &rx.attr);
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rlookup(SunMsg *m)
       +{
       +        Nfs3TLookup *tx = (Nfs3TLookup*)m->call;
       +        Nfs3RLookup rx;
       +        SunAuthUnix au;
       +        int ok;
       +
       +        if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
       +                return sunmsgreplyerror(m, ok);
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.status = fsgetattr(&au, &tx->handle, &rx.dirAttr);
       +        if(rx.status != Nfs3Ok)
       +                return sunmsgreply(m, &rx.call);
       +        rx.haveDirAttr = 1;
       +        rx.status = fslookup(&au, &tx->handle, tx->name, &rx.handle);
       +        if(rx.status != Nfs3Ok)
       +                return sunmsgreply(m, &rx.call);
       +        rx.status = fsgetattr(&au, &rx.handle, &rx.attr);
       +        if(rx.status != Nfs3Ok)
       +                return sunmsgreply(m, &rx.call);
       +        rx.haveAttr = 1;
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +raccess(SunMsg *m)
       +{
       +        Nfs3TAccess *tx = (Nfs3TAccess*)m->call;
       +        Nfs3RAccess rx;
       +        SunAuthUnix au;
       +        int ok;
       +
       +        if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
       +                return sunmsgreplyerror(m, ok);
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.haveAttr = 1;
       +        rx.status = fsaccess(&au, &tx->handle, tx->access, &rx.access, &rx.attr);
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rreadlink(SunMsg *m)
       +{
       +        Nfs3RReadlink rx;
       +        Nfs3TReadlink *tx = (Nfs3TReadlink*)m->call;
       +        SunAuthUnix au;
       +        int ok;
       +
       +        if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
       +                return sunmsgreplyerror(m, ok);
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.haveAttr = 0;
       +        rx.data = nil;
       +        rx.status = fsreadlink(&au, &tx->handle, &rx.data);
       +        sunmsgreply(m, &rx.call);
       +        free(rx.data);
       +        return 0;
       +}
       +
       +static int
       +rread(SunMsg *m)
       +{
       +        Nfs3TRead *tx = (Nfs3TRead*)m->call;
       +        Nfs3RRead rx;
       +        SunAuthUnix au;
       +        int ok;
       +
       +        if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
       +                return sunmsgreplyerror(m, ok);
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.haveAttr = 0;
       +        rx.data = nil;
       +        rx.status = fsreadfile(&au, &tx->handle, tx->count, tx->offset, &rx.data, &rx.count, &rx.eof);
       +        if(rx.status == Nfs3Ok)
       +                rx.ndata = rx.count;
       +
       +        sunmsgreply(m, &rx.call);
       +        free(rx.data);
       +        return 0;
       +}
       +
       +static int
       +rreaddir(SunMsg *m)
       +{
       +        Nfs3TReadDir *tx = (Nfs3TReadDir*)m->call;
       +        Nfs3RReadDir rx;
       +        SunAuthUnix au;
       +        int ok;
       +
       +        if((ok = authunixunpack(&m->rpc, &au)) != SunSuccess)
       +                return sunmsgreplyerror(m, ok);
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.status = fsreaddir(&au, &tx->handle, tx->count, tx->cookie, &rx.data, &rx.count, &rx.eof);
       +        sunmsgreply(m, &rx.call);
       +        free(rx.data);
       +        return 0;
       +}
       +
       +static int
       +rreaddirplus(SunMsg *m)
       +{
       +        Nfs3RReadDirPlus rx;
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.status = Nfs3ErrNotSupp;
       +        sunmsgreply(m, &rx.call);
       +        return 0;
       +}
       +
       +static int
       +rfsstat(SunMsg *m)
       +{
       +        Nfs3RFsStat rx;
       +
       +        /* just make something up */
       +        memset(&rx, 0, sizeof rx);
       +        rx.status = Nfs3Ok;
       +        rx.haveAttr = 0;
       +        rx.totalBytes = 1000000000;
       +        rx.freeBytes = 0;
       +        rx.availBytes = 0;
       +        rx.totalFiles = 100000;
       +        rx.freeFiles = 0;
       +        rx.availFiles = 0;
       +        rx.invarSec = 0;
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rfsinfo(SunMsg *m)
       +{
       +        Nfs3RFsInfo rx;
       +
       +        /* just make something up */
       +        memset(&rx, 0, sizeof rx);
       +        rx.status = Nfs3Ok;
       +        rx.haveAttr = 0;
       +        rx.readMax = MaxDataSize;
       +        rx.readPref = MaxDataSize;
       +        rx.readMult = MaxDataSize;
       +        rx.writeMax = MaxDataSize;
       +        rx.writePref = MaxDataSize;
       +        rx.writeMult = MaxDataSize;
       +        rx.readDirPref = MaxDataSize;
       +        rx.maxFileSize = 1LL<<60;
       +        rx.timePrec.sec = 1;
       +        rx.timePrec.nsec = 0;
       +        rx.flags = Nfs3FsHomogeneous|Nfs3FsCanSetTime;
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rpathconf(SunMsg *m)
       +{
       +        Nfs3RPathconf rx;
       +
       +        memset(&rx, 0, sizeof rx);
       +        rx.status = Nfs3Ok;
       +        rx.haveAttr = 0;
       +        rx.maxLink = 1;
       +        rx.maxName = 1024;
       +        rx.noTrunc = 1;
       +        rx.chownRestricted = 0;
       +        rx.caseInsensitive = 0;
       +        rx.casePreserving = 1;
       +        return sunmsgreply(m, &rx.call);
       +}
       +
       +static int
       +rrofs(SunMsg *m)
       +{
       +        uchar buf[512];        /* clumsy hack*/
       +
       +        memset(buf, 0, sizeof buf);
       +        return senderror(m, (SunCall*)buf, Nfs3ErrRoFs);
       +}
       +        
       +
       +static void
       +rnfs3(void *v)
       +{
       +        SunMsg *m;
       +
       +        m = v;
       +        switch(m->call->type){
       +        default:
       +                abort();
       +        case Nfs3CallTNull:
       +                rnull0(m);
       +                break;
       +        case Nfs3CallTGetattr:
       +                rgetattr(m);
       +                break;
       +        case Nfs3CallTLookup:
       +                rlookup(m);
       +                break;
       +        case Nfs3CallTAccess:
       +                raccess(m);
       +                break;
       +        case Nfs3CallTReadlink:
       +                rreadlink(m);
       +                break;
       +        case Nfs3CallTRead:
       +                rread(m);
       +                break;
       +        case Nfs3CallTReadDir:
       +                rreaddir(m);
       +                break;
       +        case Nfs3CallTReadDirPlus:
       +                rreaddirplus(m);
       +                break;
       +        case Nfs3CallTFsStat:
       +                rfsstat(m);
       +                break;
       +        case Nfs3CallTFsInfo:
       +                rfsinfo(m);
       +                break;
       +        case Nfs3CallTPathconf:
       +                rpathconf(m);
       +                break;
       +        case Nfs3CallTSetattr:
       +        case Nfs3CallTWrite:
       +        case Nfs3CallTCreate:
       +        case Nfs3CallTMkdir:
       +        case Nfs3CallTSymlink:
       +        case Nfs3CallTMknod:
       +        case Nfs3CallTRemove:
       +        case Nfs3CallTRmdir:
       +        case Nfs3CallTLink:
       +        case Nfs3CallTCommit:
       +                rrofs(m);
       +                break;
       +        }
       +}
       +
       +void
       +nfs3proc(void *v)
       +{
       +        Channel *c;
       +        SunMsg *m;
       +
       +        c = v;
       +        threadsetname("nfs3");
       +        while((m = recvp(c)) != nil)
       +                threadcreate(rnfs3, m, SunStackSize);
       +}
       +
 (DIR) diff --git a/src/cmd/vbackup/nfs3srv.h b/src/cmd/vbackup/nfs3srv.h
       t@@ -0,0 +1,16 @@
       +void                fsgetroot(Nfs3Handle*);
       +Nfs3Status        fsgetattr(SunAuthUnix*, Nfs3Handle*, Nfs3Attr*);
       +Nfs3Status        fslookup(SunAuthUnix*, Nfs3Handle*, char*, Nfs3Handle*);
       +Nfs3Status        fsaccess(SunAuthUnix*, Nfs3Handle*, u32int, u32int*, Nfs3Attr*);
       +Nfs3Status        fsreadlink(SunAuthUnix*, Nfs3Handle*, char**);
       +Nfs3Status        fsreadfile(SunAuthUnix*, Nfs3Handle*, u32int, u64int, uchar**, u32int*, u1int*);
       +Nfs3Status        fsreaddir(SunAuthUnix*, Nfs3Handle*, u32int, u64int, uchar**, u32int*, u1int*);
       +
       +extern void nfs3proc(void*);
       +extern void mount3proc(void*);
       +
       +enum
       +{
       +        MaxDataSize = 8192,
       +};
       +
 (DIR) diff --git a/src/cmd/vbackup/queue.c b/src/cmd/vbackup/queue.c
       t@@ -0,0 +1,64 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <thread.h>
       +#include <venti.h>
       +#include <diskfs.h>
       +#include "queue.h"
       +
       +Queue*
       +qalloc(void)
       +{
       +        Queue *q;
       +
       +        q = vtmallocz(sizeof(Queue));
       +        q->r.l = &q->lk;
       +        return q;
       +}
       +
       +Block*
       +qread(Queue *q, u32int *pbno)
       +{
       +        Block *db;
       +        u32int bno;
       +
       +        qlock(&q->lk);
       +        while(q->nel == 0 && !q->closed)
       +                rsleep(&q->r);
       +        if(q->nel == 0 && q->closed){
       +                qunlock(&q->lk);
       +                return nil;
       +        }
       +        db = q->el[q->ri].db;
       +        bno = q->el[q->ri].bno;
       +        if(++q->ri == MAXQ)
       +                q->ri = 0;
       +        if(q->nel-- == MAXQ/2)
       +                rwakeup(&q->r);
       +        qunlock(&q->lk);
       +        *pbno = bno;
       +        return db;
       +}
       +
       +void
       +qwrite(Queue *q, Block *db, u32int bno)
       +{
       +        qlock(&q->lk);
       +        while(q->nel == MAXQ)
       +                rsleep(&q->r);
       +        q->el[q->wi].db = db;
       +        q->el[q->wi].bno = bno;
       +        if(++q->wi == MAXQ)
       +                q->wi = 0;
       +        if(q->nel++ == MAXQ/2)
       +                rwakeup(&q->r);
       +        qunlock(&q->lk);
       +}
       +
       +void
       +qclose(Queue *q)
       +{
       +        qlock(&q->lk);
       +        q->closed = 1;
       +        rwakeup(&q->r);
       +        qunlock(&q->lk);
       +}
 (DIR) diff --git a/src/cmd/vbackup/queue.h b/src/cmd/vbackup/queue.h
       t@@ -0,0 +1,22 @@
       +enum
       +{
       +        MAXQ = 256,
       +};
       +
       +typedef struct Queue Queue;
       +struct Queue
       +{
       +        struct {
       +                Block *db;
       +                u32int bno;
       +        } el[MAXQ];
       +        int ri, wi, nel, closed;
       +
       +        QLock lk;
       +        Rendez r;
       +};
       +
       +Queue        *qalloc(void);
       +void        qclose(Queue*);
       +Block        *qread(Queue*, u32int*);
       +void        qwrite(Queue*, Block*, u32int);
 (DIR) diff --git a/src/cmd/vbackup/util.c b/src/cmd/vbackup/util.c
       t@@ -0,0 +1,23 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <diskfs.h>
       +
       +void*
       +emalloc(ulong n)
       +{
       +        void *v;
       +
       +        v = mallocz(n, 1);
       +        if(v == nil)
       +                abort();
       +        return v;
       +}
       +
       +void*
       +erealloc(void *v, ulong n)
       +{
       +        v = realloc(v, n);
       +        if(v == nil)
       +                abort();
       +        return v;
       +}
 (DIR) diff --git a/src/cmd/vbackup/vbackup.c b/src/cmd/vbackup/vbackup.c
       t@@ -0,0 +1,524 @@
       +/*
       + * vbackup [-Dnv] fspartition [score]
       + *
       + * Copy a file system to a disk image stored on Venti.
       + * Prints a vnfs config line for the copied image.
       + *
       + *        -D        print debugging
       + *        -m        set mount name
       + *        -n        nop -- don't actually write blocks
       + *        -s        print status updates
       + *        -v        print debugging trace
       + *        -w        write parallelism
       + * 
       + * If score is given on the command line, it should be the
       + * score from a previous vbackup on this fspartition.
       + * In this mode, only the new blocks are stored to Venti.
       + * The result is still a complete image, but requires many
       + * fewer Venti writes in the common case.
       + *
       + * This program is structured as three processes connected
       + * by buffered queues:
       + *
       + *         fsysproc | cmpproc | ventiproc
       + * 
       + * Fsysproc reads the disk and queues the blocks.
       + * Cmpproc compares the blocks against the SHA1 hashes
       + * in the old image, if any.  It discards the unchanged blocks
       + * and queues the changed ones.  Ventiproc writes blocks to Venti.
       + * 
       + * There is a fourth proc, statusproc, which prints status
       + * updates about how the various procs are progressing.
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include <bio.h>
       +#include <thread.h>
       +#include <libsec.h>
       +#include <venti.h>
       +#include <diskfs.h>
       +#include "queue.h"
       +
       +enum
       +{
       +        STACK = 8192,
       +};
       +
       +typedef struct WriteReq WriteReq;
       +struct WriteReq
       +{
       +        Packet *p;
       +        uint type;
       +};
       +
       +Biobuf        bscores;                /* biobuf filled with block scores */
       +int                debug;                /* debugging flag (not used) */
       +Disk*        disk;                        /* disk being backed up */
       +RWLock        endlk;                /* silly synchonization */
       +int                errors;                /* are we exiting with an error status? */
       +int                fsscanblock;        /* last block scanned */
       +Fsys*        fsys;                        /* file system being backed up */
       +int                nchange;                /* number of changed blocks */
       +int                nop;                        /* don't actually send blocks to venti */
       +int                nwrite;                /* number of write-behind threads */
       +Queue*        qcmp;                /* queue fsys->cmp */
       +Queue*        qventi;                /* queue cmp->venti */
       +int                statustime;        /* print status every _ seconds */
       +int                verbose;                /* print extra stuff */
       +VtFile*        vfile;                        /* venti file being written */
       +Channel*        writechan;        /* chan(WriteReq) */
       +VtConn*        z;                        /* connection to venti */
       +VtCache*        zcache;                /* cache of venti blocks */
       +uchar*        zero;                        /* blocksize zero bytes */
       +
       +extern        int        ncopy, nread, nwrite;        /* hidden in libventi */
       +
       +void                cmpproc(void*);
       +void                fsysproc(void*);
       +void                statusproc(void*);
       +void                ventiproc(void*);
       +int                timefmt(Fmt*);
       +char*        mountplace(char *dev);
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: vbackup [-DVnv] [-m mtpt] [-s secs] [-w n] disk [score]\n");
       +        threadexitsall("usage");
       +}
       +
       +void
       +threadmain(int argc, char **argv)
       +{
       +        char *pref, *mountname;
       +        uchar score[VtScoreSize], prev[VtScoreSize];
       +        int i, fd, csize;
       +        vlong bsize;
       +        Tm tm;
       +        VtEntry e;
       +        VtBlock *b;
       +        VtCache *c;
       +        VtRoot root;
       +        char *tmp, *tmpnam;
       +
       +        fmtinstall('F', vtfcallfmt);
       +        fmtinstall('H', encodefmt);
       +        fmtinstall('T', timefmt);
       +        fmtinstall('V', vtscorefmt);
       +
       +        mountname = sysname();
       +        ARGBEGIN{
       +        default:
       +                usage();
       +                break;
       +        case 'D':
       +                debug++;
       +                break;
       +        case 'V':
       +                chattyventi = 1;
       +                break;
       +        case 'm':
       +                mountname = EARGF(usage());
       +                break;
       +        case 'n':
       +                nop = 1;
       +                break;
       +        case 's':
       +                statustime = atoi(EARGF(usage()));
       +                break;
       +        case 'v':
       +                verbose = 1;
       +                break;
       +        case 'w':
       +                nwrite = atoi(EARGF(usage()));
       +                break;
       +        }ARGEND
       +
       +        if(argc != 1 && argc != 2)
       +                usage();
       +
       +        if(statustime)
       +                print("# %T vbackup %s %s\n", argv[0], argc>=2 ? argv[1] : "");
       +        /*
       +         * open fs
       +         */
       +        if((disk = diskopenfile(argv[0])) == nil)
       +                sysfatal("diskopen: %r");
       +        if((disk = diskcache(disk, 16384, 2*MAXQ+16)) == nil)
       +                sysfatal("diskcache: %r");
       +        if((fsys = fsysopen(disk)) == nil)
       +                sysfatal("ffsopen: %r");
       +
       +        /*
       +         * connect to venti
       +         */
       +        if((z = vtdial(nil)) == nil)
       +                sysfatal("vtdial: %r");
       +        if(vtconnect(z) < 0)
       +                sysfatal("vtconnect: %r");
       +
       +        /*
       +         * set up venti block cache
       +         */
       +        zero = vtmallocz(fsys->blocksize);
       +        bsize = fsys->blocksize;
       +        csize = 50;        /* plenty; could probably do with 5 */
       +
       +        if(verbose)
       +                fprint(2, "cache %d blocks\n", csize);
       +        c = vtcachealloc(z, bsize, csize, VtORDWR);
       +        zcache = c;
       +
       +        /*
       +         * parse starting score
       +         */
       +        memset(prev, 0, sizeof prev);
       +        if(argc == 1){
       +                vfile = vtfilecreateroot(c, (fsys->blocksize/VtScoreSize)*VtScoreSize,
       +                        fsys->blocksize, VtDataType);
       +                if(vfile == nil)
       +                        sysfatal("vtfilecreateroot: %r");
       +                vtfilelock(vfile, VtORDWR);
       +                if(vtfilewrite(vfile, zero, 1, bsize*fsys->nblock-1) != 1)
       +                        sysfatal("vtfilewrite: %r");
       +                if(vtfileflush(vfile) < 0)
       +                        sysfatal("vtfileflush: %r");
       +        }else{
       +                if(vtparsescore(argv[1], &pref, score) < 0)
       +                        sysfatal("bad score: %r");
       +                if(pref!=nil && strcmp(pref, fsys->type) != 0)
       +                        sysfatal("score is %s but fsys is %s", pref, fsys->type);
       +                b = vtcacheglobal(c, score, VtRootType);
       +                if(b){
       +                        if(vtrootunpack(&root, b->data) < 0)
       +                                sysfatal("bad root: %r");
       +                        if(strcmp(root.type, fsys->type) != 0)
       +                                sysfatal("root is %s but fsys is %s", root.type, fsys->type);
       +                        memmove(prev, score, VtScoreSize);
       +                        memmove(score, root.score, VtScoreSize);
       +                        vtblockput(b);
       +                }
       +                b = vtcacheglobal(c, score, VtDirType);
       +                if(b == nil)
       +                        sysfatal("vtcacheglobal %V: %r", score);
       +                if(vtentryunpack(&e, b->data, 0) < 0)
       +                        sysfatal("%V: vtentryunpack failed", score);
       +                if(verbose)
       +                        fprint(2, "entry: size %llud psize %d dsize %d\n",
       +                                e.size, e.psize, e.dsize);
       +                vtblockput(b);
       +                if((vfile = vtfileopenroot(c, &e)) == nil)
       +                        sysfatal("vtfileopenroot: %r");
       +                vtfilelock(vfile, VtORDWR);
       +                if(e.dsize != bsize)
       +                        sysfatal("file system block sizes don't match %d %lld", e.dsize, bsize);
       +                if(e.size != fsys->nblock*bsize)
       +                        sysfatal("file system block counts don't match %lld %lld", e.size, fsys->nblock*bsize);
       +        }
       +
       +        /*
       +         * write scores of blocks into temporary file
       +         */
       +        if((tmp = getenv("TMP")) != nil){
       +                /* okay, good */
       +        }else if(access("/var/tmp", 0) >= 0)
       +                tmp = "/var/tmp";
       +        else
       +                tmp = "/tmp";
       +        tmpnam = smprint("%s/vbackup.XXXXXX", tmp);
       +        if(tmpnam == nil)
       +                sysfatal("smprint: %r");
       +
       +        if((fd = opentemp(tmpnam)) < 0)
       +                sysfatal("opentemp %s: %r", tmpnam);
       +        if(statustime)
       +                print("# %T reading scores into %s\n", tmpnam);
       +        if(verbose)
       +                fprint(2, "read scores into %s...\n", tmpnam);
       +
       +        Binit(&bscores, fd, OWRITE);
       +        for(i=0; i<fsys->nblock; i++){
       +                if(vtfileblockscore(vfile, i, score) < 0)
       +                        sysfatal("vtfileblockhash %d: %r", i);
       +                if(Bwrite(&bscores, score, VtScoreSize) != VtScoreSize)
       +                        sysfatal("Bwrite: %r");
       +        }
       +        Bterm(&bscores);
       +        vtfileunlock(vfile);
       +
       +        /*
       +         * prep scores for rereading
       +         */
       +        seek(fd, 0, 0);
       +        Binit(&bscores, fd, OREAD);
       +
       +        /*
       +         * start the main processes 
       +         */
       +        if(statustime)
       +                print("# %T starting procs\n");
       +        qcmp = qalloc();
       +        qventi = qalloc();
       +
       +        rlock(&endlk);
       +        proccreate(fsysproc, nil, STACK);
       +        rlock(&endlk);
       +        proccreate(ventiproc, nil, STACK);
       +        rlock(&endlk);
       +        proccreate(cmpproc, nil, STACK);
       +        if(statustime){
       +                rlock(&endlk);
       +                proccreate(statusproc, nil, STACK);
       +        }
       +
       +        /*
       +         * wait for processes to finish
       +         */
       +        wlock(&endlk);
       +
       +        if(statustime)
       +                print("# %T procs exited: %d blocks changed, %d read, %d written, %d copied\n",
       +                        nchange, nread, nwrite, ncopy);
       +
       +        /*
       +         * prepare root block
       +         */
       +        vtfilelock(vfile, -1);
       +        if(vtfileflush(vfile) < 0)
       +                sysfatal("vtfileflush: %r");
       +        if(vtfilegetentry(vfile, &e) < 0)
       +                sysfatal("vtfilegetentry: %r");
       +
       +        b = vtcacheallocblock(c, VtDirType);
       +        if(b == nil)
       +                sysfatal("vtcacheallocblock: %r");
       +        vtentrypack(&e, b->data, 0);
       +        if(vtblockwrite(b) < 0)
       +                sysfatal("vtblockwrite: %r");
       +
       +        memset(&root, 0, sizeof root);
       +        strecpy(root.name, root.name+sizeof root.name, argv[0]);
       +        strecpy(root.type, root.type+sizeof root.type, fsys->type);
       +        memmove(root.score, b->score, VtScoreSize);
       +        root.blocksize = fsys->blocksize;
       +        memmove(root.prev, prev, VtScoreSize);
       +        vtblockput(b);
       +
       +        b = vtcacheallocblock(c, VtRootType);
       +        if(b == nil)
       +                sysfatal("vtcacheallocblock: %r");
       +        vtrootpack(&root, b->data);
       +        if(vtblockwrite(b) < 0)
       +                sysfatal("vtblockwrite: %r");
       +
       +        tm = *localtime(time(0));
       +        tm.year += 1900;
       +        tm.mon++;
       +        print("mount /%s/%d/%02d%02d%s %s:%V %d/%02d%02d/%02d%02d\n",
       +                mountname, tm.year, tm.mon, tm.mday, 
       +                mountplace(argv[0]),
       +                root.type, b->score,
       +                tm.year, tm.mon, tm.mday, tm.hour, tm.min);
       +        print("# %T %s %s:%V\n", argv[0], root.type, b->score);
       +        if(statustime)
       +                print("# %T venti sync\n");
       +        vtblockput(b);
       +        if(vtsync(z) < 0)
       +                sysfatal("vtsync: %r");
       +        if(statustime)
       +                print("# %T synced\n");
       +        threadexitsall(nil);
       +}
       +
       +void
       +fsysproc(void *dummy)
       +{
       +        u32int i;
       +        Block *db;
       +
       +        USED(dummy);
       +
       +        for(i=0; i<fsys->nblock; i++){
       +                fsscanblock = i;
       +                if((db = fsysreadblock(fsys, i)) != nil)
       +                        qwrite(qcmp, db, i);
       +        }
       +        fsscanblock = i;
       +        qclose(qcmp);
       +
       +        print("# %T fsys proc exiting\n");
       +        runlock(&endlk);
       +}
       +
       +void
       +cmpproc(void *dummy)
       +{
       +        uchar *data;
       +        Block *db;
       +        u32int bno, bsize;
       +        uchar score[VtScoreSize];
       +        uchar score1[VtScoreSize];
       +
       +        USED(dummy);
       +
       +        bsize = fsys->blocksize;
       +        while((db = qread(qcmp, &bno)) != nil){
       +                data = db->data;
       +                sha1(data, vtzerotruncate(VtDataType, data, bsize), score, nil);
       +                if(Bseek(&bscores, (vlong)bno*VtScoreSize, 0) < 0)
       +                        sysfatal("cmpproc Bseek: %r");
       +                if(Bread(&bscores, score1, VtScoreSize) != VtScoreSize)
       +                        sysfatal("cmpproc Bread: %r");
       +                if(memcmp(score, score1, VtScoreSize) != 0){
       +                        nchange++;
       +                        if(verbose)
       +                                print("# block %ud: old %V new %V\n", bno, score1, score);
       +                        qwrite(qventi, db, bno);
       +                }else
       +                        blockput(db);
       +        }
       +        qclose(qventi);
       +        runlock(&endlk);
       +}
       +
       +void
       +writethread(void *v)
       +{
       +        WriteReq wr;
       +        uchar score[VtScoreSize];
       +
       +        USED(v);
       +
       +        while(recv(writechan, &wr) == 1){
       +                if(wr.p == nil)
       +                        break;
       +                if(vtwritepacket(z, score, wr.type, wr.p) < 0)
       +                        sysfatal("vtwritepacket: %r");
       +        }
       +}
       +
       +int
       +myvtwrite(VtConn *z, uchar score[VtScoreSize], uint type, uchar *buf, int n)
       +{
       +        WriteReq wr;
       +
       +        if(nwrite == 0)
       +                return vtwrite(z, score, type, buf, n);
       +
       +        wr.p = packetalloc();
       +        packetappend(wr.p, buf, n);
       +        packetsha1(wr.p, score);
       +        wr.type = type;
       +        send(writechan, &wr);
       +        return 0;
       +}
       +
       +void
       +ventiproc(void *dummy)
       +{
       +        int i;
       +        Block *db;
       +        u32int bno;
       +        u64int bsize;
       +
       +        USED(dummy);
       +
       +        proccreate(vtsendproc, z, STACK);
       +        proccreate(vtrecvproc, z, STACK);
       +
       +        writechan = chancreate(sizeof(WriteReq), 0);
       +        for(i=0; i<nwrite; i++)
       +                threadcreate(writethread, nil, STACK);
       +        vtcachesetwrite(zcache, myvtwrite);
       +
       +        bsize = fsys->blocksize;
       +        vtfilelock(vfile, -1);
       +        while((db = qread(qventi, &bno)) != nil){
       +                if(nop){
       +                        blockput(db);
       +                        continue;
       +                }
       +                if(vtfilewrite(vfile, db->data, bsize, bno*bsize) != bsize)
       +                        sysfatal("ventiproc vtfilewrite: %r");
       +                if(vtfileflushbefore(vfile, (bno+1)*bsize) < 0)
       +                        sysfatal("ventiproc vtfileflushbefore: %r");
       +                blockput(db);
       +        }
       +        vtfileunlock(vfile);
       +        vtcachesetwrite(zcache, nil);
       +        for(i=0; i<nwrite; i++)
       +                send(writechan, nil);
       +        runlock(&endlk);
       +}
       +
       +static int
       +percent(u32int a, u32int b)
       +{
       +        return (vlong)a*100/b;
       +}
       +
       +void
       +statusproc(void *dummy)
       +{
       +        int n;
       +        USED(dummy);
       +
       +        for(n=0;;n++){
       +                sleep(1000);
       +                if(qcmp->closed && qcmp->nel==0 && qventi->closed && qventi->nel==0)
       +                        break;
       +                if(n < statustime)
       +                        continue;
       +                n = 0;
       +                print("# %T fsscan=%d%% cmpq=%d%% ventiq=%d%%\n",
       +                        percent(fsscanblock, fsys->nblock),
       +                        percent(qcmp->nel, MAXQ),
       +                        percent(qventi->nel, MAXQ));
       +        }
       +        runlock(&endlk);
       +}
       +
       +int
       +timefmt(Fmt *fmt)
       +{
       +        vlong ns;
       +        Tm tm;
       +        ns = nsec();
       +        tm = *localtime(time(0));
       +        return fmtprint(fmt, "%04d/%02d%02d %02d:%02d:%02d.%03d", 
       +                tm.year+1900, tm.mon+1, tm.mday, tm.hour, tm.min, tm.sec,
       +                (int)(ns%1000000000)/1000000);
       +}
       +
       +char*
       +mountplace(char *dev)
       +{
       +        char *cmd, *q;
       +        int p[2], fd[3], n;
       +        char buf[100];
       +        
       +        if(pipe(p) < 0)
       +                sysfatal("pipe: %r");
       +
       +        fd[0] = -1;
       +        fd[1] = p[1];
       +        fd[2] = -1;
       +        cmd = smprint("mount | awk '$1==\"%s\" && $2 == \"on\" {print $3}'", dev);
       +        if(threadspawnl(fd, "sh", "sh", "-c", cmd, nil) < 0)
       +                sysfatal("exec mount|awk (to find mtpt of %s): %r", dev);
       +        /* threadspawnl closed p[1] */
       +        n = readn(p[0], buf, sizeof buf-1);
       +        close(p[0]);
       +        if(n <= 0)
       +                return dev;
       +        buf[n] = 0;
       +        if((q = strchr(buf, '\n')) == nil)
       +                return dev;
       +        *q = 0;
       +        q = buf+strlen(buf);
       +        if(q>buf && *(q-1) == '/')
       +                *--q = 0;
       +        return strdup(buf);
       +}
       +
 (DIR) diff --git a/src/cmd/vbackup/vcat.c b/src/cmd/vbackup/vcat.c
       t@@ -0,0 +1,76 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <venti.h>
       +#include <diskfs.h>
       +#include <thread.h>
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: vcat [-z] score >diskfile\n");
       +        threadexitsall("usage");
       +}
       +
       +void
       +threadmain(int argc, char **argv)
       +{
       +        extern int nfilereads;
       +        char *pref;
       +        int zerotoo;
       +        uchar score[VtScoreSize];
       +        u8int *zero;
       +        u32int i;
       +        u32int n;
       +        Block *b;
       +        Disk *disk;
       +        Fsys *fsys;
       +        VtCache *c;
       +        VtConn *z;
       +
       +        zerotoo = 0;
       +        ARGBEGIN{
       +        case 'z':
       +                zerotoo = 1;
       +                break;
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        if(argc != 1)
       +                usage();
       +
       +        fmtinstall('V', vtscorefmt);
       +
       +        if(vtparsescore(argv[0], &pref, score) < 0)
       +                sysfatal("bad score '%s'", argv[0]);
       +        if((z = vtdial(nil)) == nil)
       +                sysfatal("vtdial: %r");
       +        if(vtconnect(z) < 0)
       +                sysfatal("vtconnect: %r");
       +        if((c = vtcachealloc(z, 16384, 32, VtOREAD)) == nil)
       +                sysfatal("vtcache: %r");
       +        if((disk = diskopenventi(c, score)) == nil)
       +                sysfatal("diskopenventi: %r");
       +        if((fsys = fsysopen(disk)) == nil)
       +                sysfatal("ffsopen: %r");
       +
       +        zero = emalloc(fsys->blocksize);
       +        fprint(2, "%d blocks total\n", fsys->nblock);
       +        n = 0;
       +        for(i=0; i<fsys->nblock; i++){
       +                if((b = fsysreadblock(fsys, i)) != nil){
       +                        if(pwrite(1, b->data, fsys->blocksize,
       +                            (u64int)fsys->blocksize*i) != fsys->blocksize)
       +                                fprint(2, "error writing block %lud: %r\n", i);
       +                        n++;
       +                        blockput(b);
       +                }else if(zerotoo)
       +                        if(pwrite(1, zero, fsys->blocksize,
       +                            (u64int)fsys->blocksize*i) != fsys->blocksize)
       +                                fprint(2, "error writing block %lud: %r\n", i);
       +                if(b == nil && i < 2)
       +                        sysfatal("block %d not in use", i);
       +        }
       +        fprint(2, "%d blocks in use, %d file reads\n", n, nfilereads);
       +        threadexitsall(nil);
       +}
 (DIR) diff --git a/src/cmd/vbackup/vftp.c b/src/cmd/vbackup/vftp.c
 (DIR) diff --git a/src/cmd/vbackup/vmount.c b/src/cmd/vbackup/vmount.c
       t@@ -0,0 +1,77 @@
       +#include <u.h>
       +#include <sys/socket.h>
       +#include <netinet/in.h>
       +#include <libc.h>
       +#include "mountnfs.h"
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: vmount [-v] [-h handle] address mountpoint\n");
       +        exits("usage");
       +}
       +
       +int handlelen = 20;
       +uchar handle[64] = {
       +        /* SHA1("/") */
       +        0x42, 0x09, 0x9B, 0x4A, 0xF0, 0x21, 0xE5, 0x3F, 0xD8, 0xFD,
       +        0x4E, 0x05, 0x6C, 0x25, 0x68, 0xD7, 0xC2, 0xE3, 0xFF, 0xA8,
       +};
       +
       +void
       +main(int argc, char **argv)
       +{
       +        char *p, *net, *unx;
       +        u32int host;
       +        int n, port, proto, verbose;
       +        struct sockaddr_in sa;
       +
       +        verbose = 0;
       +        ARGBEGIN{
       +        case 'h':
       +                p = EARGF(usage());
       +                n = strlen(p);
       +                if(n%2)
       +                        sysfatal("bad handle '%s'", p);
       +                if(n > 2*sizeof handle)
       +                        sysfatal("handle too long '%s'", p);
       +                handlelen = n/2;
       +                if(dec16(handle, n/2, p, n) != n/2)
       +                        sysfatal("bad hex in handle '%s'", p);
       +                break;
       +
       +        case 'v':
       +                verbose = 1;
       +                break;
       +
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        if(argc != 2)
       +                usage();
       +
       +        p = p9netmkaddr(argv[0], "udp", "nfs");
       +        if(p9dialparse(strdup(p), &net, &unx, &host, &port) < 0)
       +                sysfatal("bad address '%s'", p);
       +
       +        if(verbose)
       +                print("nfs server is net=%s addr=%d.%d.%d.%d port=%d\n",
       +                        net, host&0xFF, (host>>8)&0xFF, (host>>16)&0xFF, host>>24, port);
       +
       +        proto = 0;
       +        if(strcmp(net, "tcp") == 0)
       +                proto = SOCK_STREAM;
       +        else if(strcmp(net, "udp") == 0)
       +                proto = SOCK_DGRAM;
       +        else
       +                sysfatal("bad proto %s: can only handle tcp and udp", net);
       +
       +        memset(&sa, 0, sizeof sa);
       +        memmove(&sa.sin_addr, &host, 4);
       +        sa.sin_family = AF_INET;
       +        sa.sin_port = htons(port);
       +
       +        mountnfs(proto, &sa, handle, handlelen, argv[1]);
       +        exits(0);
       +}
 (DIR) diff --git a/src/cmd/vbackup/vmount.rc b/src/cmd/vbackup/vmount.rc
       t@@ -0,0 +1,19 @@
       +#!/usr/local/plan9/bin/rc
       +
       +if(! ~ $#* 2){
       +        echo 'usage: vmount server mtpt' >[1=2]
       +        exit usage
       +}
       +
       +server=$1
       +mtpt=$2
       +
       +switch(`{uname}){
       +case Linux
       +        exec mount -o 'ro,timeo=100,rsize=8192,retrans=5,port=12049,mountport=12049,mountvers=3,nfsvers=3,nolock,soft,intr,udp' \
       +                $server:/dump $mtpt
       +case *
       +        echo 'cannot mount on' `{uname} >[1=2]
       +        exit usage
       +}
       +
 (DIR) diff --git a/src/cmd/vbackup/vmount0.c b/src/cmd/vbackup/vmount0.c
       t@@ -0,0 +1,77 @@
       +#include <u.h>
       +#include <sys/socket.h>
       +#include <netinet/in.h>
       +#include <libc.h>
       +#include "mountnfs.h"
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: vmount [-v] [-h handle] address mountpoint\n");
       +        exits("usage");
       +}
       +
       +int handlelen = 20;
       +uchar handle[64] = {
       +        /* SHA1("/") */
       +        0x42, 0x09, 0x9B, 0x4A, 0xF0, 0x21, 0xE5, 0x3F, 0xD8, 0xFD,
       +        0x4E, 0x05, 0x6C, 0x25, 0x68, 0xD7, 0xC2, 0xE3, 0xFF, 0xA8,
       +};
       +
       +void
       +main(int argc, char **argv)
       +{
       +        char *p, *net, *unx;
       +        u32int host;
       +        int n, port, proto, verbose;
       +        struct sockaddr_in sa;
       +
       +        verbose = 0;
       +        ARGBEGIN{
       +        case 'h':
       +                p = EARGF(usage());
       +                n = strlen(p);
       +                if(n%2)
       +                        sysfatal("bad handle '%s'", p);
       +                if(n > 2*sizeof handle)
       +                        sysfatal("handle too long '%s'", p);
       +                handlelen = n/2;
       +                if(dec16(handle, n/2, p, n) != n/2)
       +                        sysfatal("bad hex in handle '%s'", p);
       +                break;
       +
       +        case 'v':
       +                verbose = 1;
       +                break;
       +
       +        default:
       +                usage();
       +        }ARGEND
       +
       +        if(argc != 2)
       +                usage();
       +
       +        p = p9netmkaddr(argv[0], "udp", "nfs");
       +        if(p9dialparse(strdup(p), &net, &unx, &host, &port) < 0)
       +                sysfatal("bad address '%s'", p);
       +
       +        if(verbose)
       +                print("nfs server is net=%s addr=%d.%d.%d.%d port=%d\n",
       +                        net, host&0xFF, (host>>8)&0xFF, (host>>16)&0xFF, host>>24, port);
       +
       +        proto = 0;
       +        if(strcmp(net, "tcp") == 0)
       +                proto = SOCK_STREAM;
       +        else if(strcmp(net, "udp") == 0)
       +                proto = SOCK_DGRAM;
       +        else
       +                sysfatal("bad proto %s: can only handle tcp and udp", net);
       +
       +        memset(&sa, 0, sizeof sa);
       +        memmove(&sa.sin_addr, &host, 4);
       +        sa.sin_family = AF_INET;
       +        sa.sin_port = htons(port);
       +
       +        mountnfs(proto, &sa, handle, handlelen, argv[1]);
       +        exits(0);
       +}
 (DIR) diff --git a/src/cmd/vbackup/vnfs.c b/src/cmd/vbackup/vnfs.c
       t@@ -0,0 +1,1273 @@
       +/*
       + * TO DO:
       + *        - gc of file systems (not going to do just yet?)
       + *        - statistics file
       + *        - configure on amsterdam
       + */
       +
       +#include <u.h>
       +#include <libc.h>
       +#include <bio.h>
       +#include <ip.h>
       +#include <thread.h>
       +#include <libsec.h>
       +#include <sunrpc.h>
       +#include <nfs3.h>
       +#include <diskfs.h>
       +#include <venti.h>
       +#include "nfs3srv.h"
       +
       +#define trace if(!tracecalls){}else print
       +
       +typedef struct Ipokay Ipokay;
       +typedef struct Config Config;
       +typedef struct Ctree Ctree;
       +typedef struct Cnode Cnode;
       +
       +struct Ipokay
       +{
       +        int okay;
       +        uchar ip[IPaddrlen];
       +        uchar mask[IPaddrlen];
       +};
       +
       +struct Config
       +{
       +        Ipokay *ok;
       +        uint nok;
       +        ulong mtime;
       +        Ctree *ctree;
       +};
       +
       +char                 *addr;
       +int                        blocksize;
       +int                        cachesize;
       +Config                config;
       +char                        *configfile;
       +int                        encryptedhandles = 1;
       +Channel                *nfschan;
       +Channel                *mountchan;
       +Channel                *timerchan;
       +Nfs3Handle        root;
       +SunSrv                *srv;
       +int                        tracecalls;
       +VtCache                *vcache;
       +VtConn                *z;
       +
       +void                        cryptinit(void);
       +void                        timerthread(void*);
       +void                        timerproc(void*);
       +
       +extern        void                        handleunparse(Fsys*, Nfs3Handle*, Nfs3Handle*, int);
       +extern        Nfs3Status        handleparse(Nfs3Handle*, Fsys**, Nfs3Handle*, int);
       +
       +Nfs3Status        logread(Cnode*, u32int, u64int, uchar**, u32int*, u1int*);
       +Nfs3Status        refreshdiskread(Cnode*, u32int, u64int, uchar**, u32int*, u1int*);
       +Nfs3Status        refreshconfigread(Cnode*, u32int, u64int, uchar**, u32int*, u1int*);
       +
       +int readconfigfile(Config *cp);
       +void setrootfid(void);
       +int ipokay(uchar *ip, ushort port);
       +
       +void
       +usage(void)
       +{
       +        fprint(2, "usage: vnfs [-LLRVr] [-a addr] [-b blocksize] [-c cachesize] configfile\n");
       +        threadexitsall("usage");
       +}
       +
       +void
       +threadmain(int argc, char **argv)
       +{
       +        fmtinstall('B', sunrpcfmt);
       +        fmtinstall('C', suncallfmt);
       +        fmtinstall('F', vtfcallfmt);
       +        fmtinstall('H', encodefmt);
       +        fmtinstall('I', eipfmt);
       +        fmtinstall('V', vtscorefmt);
       +        sunfmtinstall(&nfs3prog);
       +        sunfmtinstall(&nfsmount3prog);
       +
       +        addr = "udp!*!2049";
       +        blocksize = 8192;
       +        cachesize = 400;
       +        srv = sunsrv();
       +        srv->ipokay = ipokay;
       +        cryptinit();
       +
       +        ARGBEGIN{
       +        default:
       +                usage();
       +        case 'E':
       +                encryptedhandles = 0;
       +                break;
       +        case 'L':
       +                if(srv->localonly == 0)
       +                        srv->localonly = 1;
       +                else
       +                        srv->localparanoia = 1;
       +                break;
       +        case 'R':
       +                srv->chatty++;
       +                break;
       +        case 'T':
       +                tracecalls = 1;
       +                break;
       +        case 'V':
       +                chattyventi = 1;
       +                break;
       +        case 'a':
       +                addr = EARGF(usage());
       +                break;
       +        case 'b':
       +                blocksize = atoi(EARGF(usage()));
       +                break;
       +        case 'c':
       +                cachesize = atoi(EARGF(usage()));
       +                break;
       +        case 'r':
       +                srv->alwaysreject++;
       +                break;
       +        }ARGEND
       +
       +        if(argc != 1)
       +                usage();
       +
       +        if((z = vtdial(nil)) == nil)
       +                sysfatal("vtdial: %r");
       +        if(vtconnect(z) < 0)
       +                sysfatal("vtconnect: %r");
       +        if((vcache = vtcachealloc(z, blocksize, cachesize, OREAD)) == nil)
       +                sysfatal("vtcache: %r");
       +
       +        configfile = argv[0];
       +        if(readconfigfile(&config) < 0)
       +                sysfatal("readConfig: %r");
       +        setrootfid();
       +
       +        nfschan = chancreate(sizeof(SunMsg*), 0);
       +        mountchan = chancreate(sizeof(SunMsg*), 0);
       +        timerchan = chancreate(sizeof(void*), 0);
       +        
       +        if(sunsrvudp(srv, addr) < 0)
       +                sysfatal("starting server: %r");
       +
       +        sunsrvthreadcreate(srv, nfs3proc, nfschan);
       +        sunsrvthreadcreate(srv, mount3proc, mountchan);
       +        sunsrvthreadcreate(srv, timerthread, nil);
       +        proccreate(timerproc, nil, 32768);
       +        
       +        sunsrvprog(srv, &nfs3prog, nfschan);
       +        sunsrvprog(srv, &nfsmount3prog, mountchan);
       +
       +        threadexits(nil);
       +}
       +
       +/*
       + * Handles.
       + * 
       + * We store all the state about which file a client is accessing in
       + * the handle, so that we don't have to maintain any per-client state
       + * ourselves.  In order to avoid leaking handles or letting clients 
       + * create arbitrary handles, we sign and encrypt each handle with
       + * AES using a key selected randomly when the server starts.
       + * Thus, handles cannot be used across sessions.  
       + *
       + * The decrypted handles begin with the following header:
       + *
       + *        rand[12]                random bytes used to make encryption non-deterministic
       + *        len[4]                length of handle that follows
       + *        sessid[8]                random session id chosen at start time
       + *
       + * If we're pressed for space in the rest of the handle, we could
       + * probably reduce the amount of randomness.
       + *
       + * Security woes aside, the fact that we have to shove everything
       + * into the handles is quite annoying.  We have to encode, in 40 bytes:
       + *
       + *        - position in the synthesized config tree
       + *        - enough of the path to do glob matching
       + *        - position in an archived file system image
       + *
       + * and the handles need to be stable across changes in the config file
       + * (though not across server restarts since encryption screws
       + * that up nicely).
       + * 
       + * We encode each of the first two as a 10-byte hash that is 
       + * the first half of a SHA1 hash.  
       + */
       +
       +enum
       +{
       +        RandSize = 16,
       +        SessidSize = 8,
       +        HeaderSize = RandSize+SessidSize,
       +        MaxHandleSize = Nfs3MaxHandleSize - HeaderSize,
       +};
       +
       +AESstate                aesstate;
       +uchar                sessid[SessidSize];
       +
       +static void
       +hencrypt(Nfs3Handle *h)
       +{
       +        uchar *p;
       +        AESstate aes;
       +
       +        /*
       +         * root handle has special encryption - a single 0 byte - so that it
       +         * never goes stale.
       +         */
       +        if(h->len == root.len && memcmp(h->h, root.h, root.len) == 0){
       +                h->h[0] = 0;
       +                h->len = 1;
       +                return;
       +        }
       +
       +        if(!encryptedhandles)
       +                return;
       +
       +        if(h->len > MaxHandleSize){
       +                /* oops */
       +                fprint(2, "handle too long: %.*lH\n", h->len, h->h);
       +                memset(h->h, 'X', Nfs3MaxHandleSize);
       +                h->len = Nfs3MaxHandleSize;
       +                return;
       +        }
       +
       +        p = h->h;
       +        memmove(p+HeaderSize, p, h->len);
       +        *(u32int*)p = fastrand();
       +        *(u32int*)(p+4) = fastrand();
       +        *(u32int*)(p+8) = fastrand();
       +        *(u32int*)(p+12) = h->len;
       +        memmove(p+16, sessid, SessidSize);
       +        h->len += HeaderSize;
       +
       +        if(encryptedhandles){
       +                while(h->len < MaxHandleSize)
       +                        h->h[h->len++] = fastrand();
       +                aes = aesstate;
       +                aesCBCencrypt(h->h, MaxHandleSize, &aes);
       +        }
       +}
       +
       +static Nfs3Status
       +hdecrypt(Nfs3Handle *h)
       +{
       +        AESstate aes;
       +        
       +        if(h->len == 1 && h->h[0] == 0){        /* single 0 byte is root */
       +                *h = root;
       +                return Nfs3Ok;
       +        }
       +
       +        if(!encryptedhandles)
       +                return Nfs3Ok;
       +
       +        if(h->len <= HeaderSize)
       +                return Nfs3ErrBadHandle;
       +        if(encryptedhandles){
       +                if(h->len != MaxHandleSize)
       +                        return Nfs3ErrBadHandle;
       +                aes = aesstate;
       +                aesCBCdecrypt(h->h, h->len, &aes);
       +        }
       +        if(memcmp(h->h+RandSize, sessid, sizeof sessid) != 0)
       +                return Nfs3ErrStale;        /* give benefit of doubt */
       +        h->len = *(u32int*)(h->h+12); /* XXX byte order */
       +        memmove(h->h, h->h+HeaderSize, h->len);
       +        return Nfs3Ok;
       +}
       +
       +void
       +cryptinit(void)
       +{
       +        uchar key[32], ivec[AESbsize];
       +        int i;
       +        
       +        *(u32int*)sessid = truerand();
       +        for(i=0; i<nelem(key); i+=4)
       +                *(u32int*)&key[i] = truerand();
       +        for(i=0; i<nelem(ivec); i++)
       +                ivec[i] = fastrand();
       +        setupAESstate(&aesstate, key, sizeof key, ivec);
       +}
       +
       +/*
       + * Config file.
       + *
       + * The main purpose of the configuration file is to define a tree
       + * in which the archived file system images are mounted.
       + * The tree is stored as Entry structures, defined below.
       + *
       + * The configuration file also allows one to define shell-like
       + * glob expressions matching paths that are not to be displayed.
       + * The matched files or directories are shown in directory listings
       + * (could suppress these if we cared) but they cannot be opened,
       + * read, or written, and getattr returns zeroed data.
       + */
       +enum
       +{
       +        /* sizes used in handles; see nfs server below */
       +        CnodeHandleSize = 8,
       +        FsysHandleOffset = CnodeHandleSize,
       +};
       +
       +/*
       + * Config file tree.
       + */
       +struct Ctree
       +{
       +        Cnode *root;
       +        Cnode *hash[1024];
       +};
       +
       +struct Cnode
       +{
       +        char *name;        /* path element */
       +        Cnode *parent;        /* in tree */
       +        Cnode *nextsib;        /* in tree */
       +        Cnode *kidlist;        /* in tree */
       +        Cnode *nexthash;        /* in hash list */
       +        
       +        Nfs3Status (*read)(Cnode*, u32int, u64int, uchar**, u32int*, u1int*);        /* synthesized read fn */
       +
       +        uchar handle[VtScoreSize];        /* sha1(path to here) */
       +        ulong mtime;        /* mtime for this directory entry */
       +        
       +        /* fsys overlay on this node */
       +        Fsys *fsys;        /* cache of memory structure */
       +        Nfs3Handle fsyshandle;
       +        int isblackhole;        /* walking down keeps you here */
       +
       +        /*
       +         * mount point info.
       +         * if a mount point is inside another file system,
       +         * the fsys and fsyshandle above have the old fs info,
       +         * the mfsys and mfsyshandle below have the new one.
       +         * getattrs must use the old info for consistency.
       +         */
       +        int ismtpt;        /* whether there is an fsys mounted here */
       +        uchar fsysscore[VtScoreSize];        /* score of fsys image on venti */
       +        char *fsysimage;        /* raw disk image */
       +        Fsys *mfsys;        /* mounted file system (nil until walked) */
       +        Nfs3Handle mfsyshandle;        /* handle to root of mounted fsys */
       +        
       +        int mark;        /* gc */
       +};
       +
       +static uint
       +dumbhash(uchar *s)
       +{
       +        return (s[0]<<2)|(s[1]>>6);        /* first 10 bits */
       +}
       +
       +static Cnode*
       +mkcnode(Ctree *t, Cnode *parent, char *elem, uint elen, char *path, uint plen)
       +{
       +        uint h;
       +        Cnode *n;
       +        
       +        n = emalloc(sizeof *n + elen+1);
       +        n->name = (char*)(n+1);
       +        memmove(n->name, elem, elen);
       +        n->name[elen] = 0;
       +        n->parent = parent;
       +        if(parent){
       +                n->nextsib = parent->kidlist;
       +                parent->kidlist = n;
       +        }
       +        n->kidlist = nil;
       +        sha1((uchar*)path, plen, n->handle, nil);
       +        h = dumbhash(n->handle);
       +        n->nexthash = t->hash[h];
       +        t->hash[h] = n;
       +        
       +        return n;
       +}
       +
       +void
       +markctree(Ctree *t)
       +{
       +        int i;
       +        Cnode *n;
       +
       +        for(i=0; i<nelem(t->hash); i++)
       +                for(n=t->hash[i]; n; n=n->nexthash)
       +                        if(n->name[0] != '+')
       +                                n->mark = 1;
       +}
       +
       +int
       +refreshdisk(void)
       +{
       +        int i;
       +        Cnode *n;
       +        Ctree *t;
       +        
       +        t = config.ctree;
       +        for(i=0; i<nelem(t->hash); i++)
       +                for(n=t->hash[i]; n; n=n->nexthash){
       +                        if(n->mfsys)
       +                                disksync(n->mfsys->disk);
       +                        if(n->fsys)
       +                                disksync(n->fsys->disk);
       +                }
       +        return 0;
       +}
       +
       +void
       +sweepctree(Ctree *t)
       +{
       +        int i;
       +        Cnode *n;
       +
       +        /* just zero all the garbage and leave it linked into the tree */
       +        for(i=0; i<nelem(t->hash); i++){
       +                for(n=t->hash[i]; n; n=n->nexthash){
       +                        if(!n->mark)
       +                                continue;
       +                        n->fsys = nil;
       +                        free(n->fsysimage);
       +                        n->fsysimage = nil;
       +                        memset(n->fsysscore, 0, sizeof n->fsysscore);
       +                        n->mfsys = nil;
       +                        n->ismtpt = 0;
       +                        memset(&n->fsyshandle, 0, sizeof n->fsyshandle);
       +                        memset(&n->mfsyshandle, 0, sizeof n->mfsyshandle);
       +                }
       +        }
       +}
       +
       +static Cnode*
       +cnodewalk(Cnode *n, char *name, uint len, int markokay)
       +{
       +        Cnode *nn;
       +        
       +        for(nn=n->kidlist; nn; nn=nn->nextsib)
       +                if(strncmp(nn->name, name, len) == 0 && nn->name[len] == 0)
       +                if(!nn->mark || markokay)
       +                        return nn;
       +        return nil;
       +}
       +
       +Cnode*
       +ctreewalkpath(Ctree *t, char *name, ulong createmtime)
       +{
       +        Cnode *n, *nn;
       +        char *p, *nextp;
       +        
       +        n = t->root;
       +        p = name;
       +        for(; *p; p=nextp){
       +                n->mark = 0;
       +                assert(*p == '/');
       +                p++;
       +                nextp = strchr(p, '/');
       +                if(nextp == nil)
       +                        nextp = p+strlen(p);
       +                if((nn = cnodewalk(n, p, nextp-p, 1)) == nil){
       +                        if(createmtime == 0)
       +                                return nil;
       +                        nn = mkcnode(t, n, p, nextp-p, name, nextp-name);
       +                        nn->mtime = createmtime;
       +                }
       +                if(nn->mark)
       +                        nn->mark = 0;
       +                n = nn;
       +        }
       +        n->mark = 0;
       +        return n;
       +}
       +
       +Ctree*
       +mkctree(void)
       +{
       +        Ctree *t;
       +        
       +        t = emalloc(sizeof *t);
       +        t->root = mkcnode(t, nil, "", 0, "", 0);
       +        
       +        ctreewalkpath(t, "/+log", time(0))->read = logread;
       +        ctreewalkpath(t, "/+refreshdisk", time(0))->read = refreshdiskread;
       +        ctreewalkpath(t, "/+refreshconfig", time(0))->read = refreshconfigread;
       +
       +        return t;
       +}
       +
       +Cnode*
       +ctreemountfsys(Ctree *t, char *path, ulong time, uchar *score, char *file)
       +{
       +        Cnode *n;
       +        
       +        if(time == 0)
       +                time = 1;
       +        n = ctreewalkpath(t, path, time);
       +        if(score){
       +                if(n->ismtpt && (n->fsysimage || memcmp(n->fsysscore, score, VtScoreSize) != 0)){
       +                        free(n->fsysimage);
       +                        n->fsysimage = nil;
       +                        n->fsys = nil;        /* leak (might be other refs) */
       +                }
       +                memmove(n->fsysscore, score, VtScoreSize);
       +        }else{
       +                if(n->ismtpt && (n->fsysimage==nil || strcmp(n->fsysimage, file) != 0)){
       +                        free(n->fsysimage);
       +                        n->fsysimage = nil;
       +                        n->fsys = nil;        /* leak (might be other refs) */
       +                }
       +                n->fsysimage = emalloc(strlen(file)+1);
       +                strcpy(n->fsysimage, file);
       +        }
       +        n->ismtpt = 1;
       +        return n;
       +}
       +
       +Cnode*
       +cnodebyhandle(Ctree *t, uchar *p)
       +{
       +        int h;
       +        Cnode *n;
       +        
       +        h = dumbhash(p);
       +        for(n=t->hash[h]; n; n=n->nexthash)
       +                if(memcmp(n->handle, p, CnodeHandleSize) == 0)
       +                        return n;
       +        return nil;
       +}
       +
       +static int
       +parseipandmask(char *s, uchar *ip, uchar *mask)
       +{
       +        char *p, *q;
       +        
       +        p = strchr(s, '/');
       +        if(p)
       +                *p++ = 0;
       +        if(parseip(ip, s) == ~0UL)
       +                return -1;
       +        if(p == nil)
       +                memset(mask, 0xFF, IPaddrlen);
       +        else{
       +                if(isdigit(*p) && strtol(p, &q, 10)>=0 && *q==0)
       +                        *--p = '/';
       +                if(parseipmask(mask, p) == ~0UL)
       +                        return -1;
       +                if(*p != '/')
       +                        *--p = '/';
       +        }
       +//fprint(2, "parseipandmask %s => %I %I\n", s, ip, mask);
       +        return 0;
       +}
       +
       +static int
       +parsetime(char *s, ulong *time)
       +{
       +        ulong x;
       +        char *p;
       +        int i;
       +        Tm tm;
       +        
       +        /* decimal integer is seconds since 1970 */
       +        x = strtoul(s, &p, 10);
       +        if(x > 0 && *p == 0){
       +                *time = x;
       +                return 0;
       +        }
       +        
       +        /* otherwise expect yyyy/mmdd/hhmm */
       +        if(strlen(s) != 14 || s[4] != '/' || s[9] != '/')
       +                return -1;
       +        for(i=0; i<4; i++)
       +                if(!isdigit(s[i]) || !isdigit(s[i+5]) || !isdigit(s[i+10]))
       +                        return -1;
       +        memset(&tm, 0, sizeof tm);
       +        tm.year = atoi(s)-1900;
       +        if(tm.year < 0 || tm.year > 200)
       +                return -1;
       +        tm.mon = (s[5]-'0')*10+s[6]-'0' - 1;
       +        if(tm.mon < 0 || tm.mon > 11)
       +                return -1; 
       +        tm.mday = (s[7]-'0')*10+s[8]-'0';
       +        if(tm.mday < 0 || tm.mday > 31)
       +                return -1;
       +        tm.hour = (s[10]-'0')*10+s[11]-'0';
       +        if(tm.hour < 0 || tm.hour > 23)
       +                return -1;
       +        tm.min = (s[12]-'0')*10+s[13]-'0';
       +        if(tm.min < 0 || tm.min > 59)
       +                return -1;
       +        strcpy(tm.zone, "XXX");        /* anything but GMT */
       +if(0){
       +print("tm2sec %d/%d/%d/%d/%d\n",
       +        tm.year, tm.mon, tm.mday, tm.hour, tm.min);
       +}
       +        *time = tm2sec(&tm);
       +if(0) print("time %lud\n", *time);
       +        return 0;
       +}
       +
       +
       +int
       +readconfigfile(Config *cp)
       +{
       +        char *f[10], *image, *p, *pref, *q, *name;
       +        int nf, line;
       +        uchar scorebuf[VtScoreSize], *score;
       +        ulong time;
       +        Biobuf *b;
       +        Config c;
       +        Dir *dir;
       +
       +        name = configfile;
       +        c = *cp;
       +        if((dir = dirstat(name)) == nil)
       +                return -1;
       +        if(c.mtime == dir->mtime){
       +                free(dir);
       +                return 0;
       +        }
       +        c.mtime = dir->mtime;
       +        free(dir);
       +        if((b = Bopen(name, OREAD)) == nil){
       +                free(dir);
       +                return -1;
       +        }
       +        
       +        /*
       +         * Reuse old tree, garbage collecting entries that
       +         * are not mentioned in the new config file.
       +         */
       +        if(c.ctree == nil)
       +                c.ctree = mkctree();
       +
       +        markctree(c.ctree);
       +        c.ok = nil;
       +        c.nok = 0;
       +        
       +        line = 0;
       +        for(; (p=Brdstr(b, '\n', 1)) != nil; free(p)){
       +                line++;
       +                if((q = strchr(p, '#')) != nil)
       +                        *q = 0;
       +                nf = tokenize(p, f, nelem(f));
       +                if(nf == 0)
       +                        continue;
       +                if(strcmp(f[0], "mount") == 0){
       +                        if(nf != 4){
       +                                werrstr("syntax error: mount /path /dev|score mtime");
       +                                goto badline;
       +                        }
       +                        if(f[1][0] != '/'){
       +                                werrstr("unrooted path %s", f[1]);
       +                                goto badline;
       +                        }
       +                        score = nil;
       +                        image = nil;
       +                        if(f[2][0] == '/'){
       +                                if(access(f[2], AEXIST) < 0){
       +                                        werrstr("image %s does not exist", f[2]);
       +                                        goto badline;
       +                                }
       +                                image = f[2];
       +                        }else{
       +                                if(vtparsescore(f[2], &pref, scorebuf) < 0){
       +                                        werrstr("bad score %s", f[2]);
       +                                        goto badline;
       +                                }
       +                                score = scorebuf;
       +                        }
       +                        if(parsetime(f[3], &time) < 0){
       +                                fprint(2, "%s:%d: bad time %s\n", name, line, f[3]);
       +                                time = 1;
       +                        }
       +                        ctreemountfsys(c.ctree, f[1], time, score, image);
       +                        continue;
       +                }
       +                if(strcmp(f[0], "allow") == 0 || strcmp(f[0], "deny") == 0){
       +                        if(nf != 2){
       +                                werrstr("syntax error: allow|deny ip[/mask]"); 
       +                                goto badline;
       +                        }
       +                        c.ok = erealloc(c.ok, (c.nok+1)*sizeof(c.ok[0]));
       +                        if(parseipandmask(f[1], c.ok[c.nok].ip, c.ok[c.nok].mask) < 0){
       +                                werrstr("bad ip[/mask]: %s", f[1]);
       +                                goto badline;
       +                        }
       +                        c.ok[c.nok].okay = (strcmp(f[0], "allow") == 0);
       +                        c.nok++;
       +                        continue;
       +                }
       +                werrstr("unknown verb '%s'", f[0]);
       +        badline:
       +                fprint(2, "%s:%d: %r\n", name, line);
       +        }
       +        Bterm(b);
       +
       +        sweepctree(c.ctree);
       +        free(cp->ok);
       +        *cp = c;
       +        return 0;
       +}
       +
       +int
       +ipokay(uchar *ip, ushort port)
       +{
       +        int i;
       +        uchar ipx[IPaddrlen];
       +        Ipokay *ok; 
       +        
       +        for(i=0; i<config.nok; i++){
       +                ok = &config.ok[i];
       +                maskip(ip, ok->mask, ipx);
       +if(0) fprint(2, "%I & %I = %I (== %I?)\n",
       +        ip, ok->mask, ipx, ok->ip);
       +                if(memcmp(ipx, ok->ip, IPaddrlen) == 0)
       +                        return ok->okay;
       +        }
       +        if(config.nok == 0)        /* all is permitted */
       +                return 1;
       +        /* otherwise default is none allowed */
       +        return 0;
       +}
       +
       +Nfs3Status
       +cnodelookup(Ctree *t, Cnode **np, char *name)
       +{
       +        Cnode *n, *nn;
       +        
       +        n = *np;
       +        if(n->isblackhole)
       +                return Nfs3Ok;
       +        if((nn = cnodewalk(n, name, strlen(name), 0)) == nil){
       +                if(n->ismtpt || n->fsys){
       +                        if((nn = cnodewalk(n, "", 0, 1)) == nil){
       +                                nn = mkcnode(t, n, "", 0, (char*)n->handle, SHA1dlen);
       +                                nn->isblackhole = 1;
       +                        }
       +                        nn->mark = 0;
       +                }
       +        }
       +        if(nn == nil)
       +                return Nfs3ErrNoEnt;
       +        *np = nn;
       +        return Nfs3Ok;
       +}
       +
       +Nfs3Status
       +cnodegetattr(Cnode *n, Nfs3Attr *attr)
       +{
       +        memset(attr, 0, sizeof *attr);
       +        if(n->read){
       +                attr->type = Nfs3FileReg;
       +                attr->mode = 0444;
       +                attr->size = 512;
       +                attr->nlink = 1;
       +        }else{
       +                attr->type = Nfs3FileDir;
       +                attr->mode = 0555;
       +                attr->size = 1024;
       +                attr->nlink = 10;
       +        }
       +        attr->fileid = *(u64int*)n->handle;
       +        attr->atime.sec = n->mtime;
       +        attr->mtime.sec = n->mtime;
       +        attr->ctime.sec = n->mtime;
       +        return Nfs3Ok;
       +}
       +
       +Nfs3Status
       +cnodereaddir(Cnode *n, u32int count, u64int cookie, uchar **pdata, u32int *pcount, u1int *peof)
       +{
       +        uchar *data, *p, *ep, *np;
       +        u64int c;
       +        Nfs3Entry ne;
       +        
       +        n = n->kidlist;
       +        c = cookie;
       +        for(; c && n; c--)
       +                n = n->nextsib;
       +        if(n == nil){
       +                *pdata = 0;
       +                *pcount = 0;
       +                *peof = 1;
       +                return Nfs3Ok;
       +        }
       +        
       +        data = emalloc(count);
       +        p = data;
       +        ep = data+count;
       +        while(n && p < ep){
       +                if(n->mark || n->name[0] == '+'){
       +                        n = n->nextsib;
       +                        ++cookie;
       +                        continue;
       +                }
       +                ne.name = n->name;
       +                ne.cookie = ++cookie;
       +                ne.fileid = *(u64int*)n->handle;
       +                if(nfs3entrypack(p, ep, &np, &ne) < 0)
       +                        break;
       +                p = np;
       +                n = n->nextsib;
       +        }
       +        *pdata = data;
       +        *pcount = p - data;
       +        *peof = n==nil;
       +        return Nfs3Ok;
       +}
       +
       +void
       +timerproc(void *v)
       +{
       +        for(;;){
       +                sleep(60*1000);
       +                sendp(timerchan, 0);
       +        }
       +}
       +
       +void
       +timerthread(void *v)
       +{
       +        for(;;){
       +                recvp(timerchan);
       +        //        refreshconfig();
       +        }
       +}
       +
       +/*
       + * Actually serve the NFS requests.  Called from nfs3srv.c.  
       + * Each request runs in its own thread (coroutine).
       + *
       + * Decrypted handles have the form:
       + *
       + *        config[20] - SHA1 hash identifying a config tree node
       + *        glob[10] - SHA1 hash prefix identifying a glob state
       + *        fsyshandle[<=10] - disk file system handle (usually 4 bytes)
       + */
       + 
       +/*
       + * A fid represents a point in the file tree.
       + * There are three components, all derived from the handle:
       + *
       + *        - config tree position (also used to find fsys)
       + *        - glob state for exclusions
       + *        - file system position
       + */
       +enum
       +{
       +        HAccess,
       +        HAttr,
       +        HWalk,
       +        HDotdot,
       +        HRead
       +};
       +typedef struct Fid Fid;
       +struct Fid
       +{
       +        Cnode *cnode;
       +        Fsys *fsys;
       +        Nfs3Handle fsyshandle;
       +};
       +
       +int
       +handlecmp(Nfs3Handle *h, Nfs3Handle *h1)
       +{
       +        if(h->len != h1->len)
       +                return h->len - h1->len;
       +        return memcmp(h->h, h1->h, h->len);
       +}
       +
       +Nfs3Status
       +handletofid(Nfs3Handle *eh, Fid *fid, int mode)
       +{
       +        int domount;
       +        Cnode *n;
       +        Disk *disk, *cdisk;
       +        Fsys *fsys;
       +        Nfs3Status ok;
       +        Nfs3Handle h2, *h, *fh;
       +        
       +        memset(fid, 0, sizeof *fid);
       +
       +        domount = 1;
       +        if(mode == HDotdot)
       +                domount = 0;
       +        /*
       +         * Not necessary, but speeds up ls -l /dump/2005
       +         * HAttr and HAccess must be handled the same way
       +         * because both can be used to fetch attributes.
       +         * Acting differently yields inconsistencies at mount points,
       +         * and causes FreeBSD ls -l to fail.
       +         */
       +        if(mode == HAttr || mode == HAccess)
       +                domount = 0;
       +
       +        /*
       +         * Decrypt handle.
       +         */
       +        h2 = *eh;
       +        h = &h2;
       +        if((ok = hdecrypt(h)) != Nfs3Ok)
       +                return ok;
       +        trace("handletofid: decrypted %.*lH\n", h->len, h->h);
       +        if(h->len < FsysHandleOffset)
       +                return Nfs3ErrBadHandle;
       +
       +        /*
       +         * Find place in config tree.
       +         */
       +        if((n = cnodebyhandle(config.ctree, h->h)) == nil)
       +                return Nfs3ErrStale;
       +        fid->cnode = n;
       +
       +        if(n->ismtpt && domount){
       +                /*
       +                 * Open fsys for mount point if needed.
       +                 */
       +                if(n->mfsys == nil){
       +                        trace("handletofid: mounting %V/%s\n", n->fsysscore, n->fsysimage);
       +                        if(n->fsysimage){
       +                                if(strcmp(n->fsysimage, "/dev/null") == 0)
       +                                        return Nfs3ErrAcces;
       +                                if((disk = diskopenfile(n->fsysimage)) == nil){
       +                                        fprint(2, "cannot open disk %s: %r\n", n->fsysimage);
       +                                        return Nfs3ErrIo;
       +                                }
       +                                if((cdisk = diskcache(disk, blocksize, 64)) == nil){
       +                                        fprint(2, "cannot cache disk %s: %r\n", n->fsysimage);
       +                                        diskclose(disk);
       +                                }
       +                                disk = cdisk;
       +                        }else{
       +                                if((disk = diskopenventi(vcache, n->fsysscore)) == nil){
       +                                        fprint(2, "cannot open venti disk %V: %r\n", n->fsysscore);
       +                                        return Nfs3ErrIo;
       +                                }
       +                        }
       +                        if((fsys = fsysopen(disk)) == nil){
       +                                fprint(2, "cannot open fsys on %V: %r\n", n->fsysscore);
       +                                diskclose(disk);
       +                                return Nfs3ErrIo;
       +                        }
       +                        n->mfsys = fsys;
       +                        fsysroot(fsys, &n->mfsyshandle);
       +                }
       +                
       +                /*
       +                 * Use inner handle.
       +                 */
       +                fid->fsys = n->mfsys;
       +                fid->fsyshandle = n->mfsyshandle;
       +        }else{
       +                /*
       +                 * Use fsys handle from tree or from handle.
       +                 * This assumes that fsyshandle was set by fidtohandle
       +                 * earlier, so it's not okay to reuse handles (except the root)
       +                 * across sessions.  The encryption above makes and 
       +                 * enforces the same restriction, so this is okay.
       +                 */
       +                fid->fsys = n->fsys;
       +                fh = &fid->fsyshandle;
       +                if(n->isblackhole){
       +                        fh->len = h->len-FsysHandleOffset;
       +                        memmove(fh->h, h->h+FsysHandleOffset, fh->len);
       +                }else
       +                        *fh = n->fsyshandle;
       +                trace("handletofid: fsyshandle %.*lH\n", fh->len, fh->h);
       +        }
       +
       +        /*
       +         * TO DO (maybe): some sort of path restriction here.
       +         */
       +        trace("handletofid: cnode %s fsys %p fsyshandle %.*lH\n",
       +                n->name, fid->fsys, fid->fsyshandle.len, fid->fsyshandle.h);
       +        return Nfs3Ok;
       +}
       +
       +void
       +_fidtohandle(Fid *fid, Nfs3Handle *h)
       +{
       +        Cnode *n;
       +        
       +        n = fid->cnode;
       +        /*
       +         * Record fsys handle in n, don't bother sending it to client
       +         * for black holes.
       +         */
       +        n->fsys = fid->fsys;
       +        if(!n->isblackhole){
       +                n->fsyshandle = fid->fsyshandle;
       +                fid->fsyshandle.len = 0;
       +        }
       +        memmove(h->h, n->handle, CnodeHandleSize);
       +        memmove(h->h+FsysHandleOffset, fid->fsyshandle.h, fid->fsyshandle.len);
       +        h->len = FsysHandleOffset+fid->fsyshandle.len;
       +}
       +
       +void
       +fidtohandle(Fid *fid, Nfs3Handle *h)
       +{
       +        _fidtohandle(fid, h);
       +        hencrypt(h);
       +}
       +
       +void
       +setrootfid(void)
       +{
       +        Fid fid;
       +        
       +        memset(&fid, 0, sizeof fid);
       +        fid.cnode = config.ctree->root;
       +        _fidtohandle(&fid, &root);
       +fprint(2, "handle %.*lH\n", root.len, root.h);
       +}
       +
       +void
       +fsgetroot(Nfs3Handle *h)
       +{
       +        *h = root;
       +        hencrypt(h);
       +}
       +
       +Nfs3Status
       +fsgetattr(SunAuthUnix *au, Nfs3Handle *h, Nfs3Attr *attr)
       +{
       +        Fid fid;
       +        Nfs3Status ok;
       +
       +        trace("getattr %.*lH\n", h->len, h->h);
       +        if((ok = handletofid(h, &fid, HAttr)) != Nfs3Ok)
       +                return ok;
       +        if(fid.fsys)
       +                return fsysgetattr(fid.fsys, au, &fid.fsyshandle, attr);
       +        else
       +                return cnodegetattr(fid.cnode, attr);
       +}
       +
       +/*
       + * Lookup is always the hard part.
       + */
       +Nfs3Status
       +fslookup(SunAuthUnix *au, Nfs3Handle *h, char *name, Nfs3Handle *nh)
       +{
       +        Fid fid;
       +        Cnode *n;
       +        Nfs3Status ok;
       +        Nfs3Handle xh;
       +        int mode;
       +        
       +        trace("lookup %.*lH %s\n", h->len, h->h, name);
       +
       +        mode = HWalk;
       +        if(strcmp(name, "..") == 0 || strcmp(name, ".") == 0)
       +                mode = HDotdot;
       +        if((ok = handletofid(h, &fid, mode)) != Nfs3Ok){
       +                nfs3errstr(ok);
       +                trace("lookup: handletofid %r\n");
       +                return ok;
       +        }
       +        
       +        if(strcmp(name, ".") == 0){
       +                fidtohandle(&fid, nh);
       +                return Nfs3Ok;
       +        }
       +
       +        /*
       +         * Walk down file system and cnode simultaneously.
       +         * If dotdot and file system doesn't move, need to walk
       +         * up cnode.  Save the corresponding fsys handles in
       +         * the cnode as we walk down so that we'll have them 
       +         * for dotdotting back up.
       +         */
       +        n = fid.cnode;
       +        if(mode == HWalk){
       +                /*
       +                 * Walk down config tree and file system simultaneously.
       +                 */
       +                if((ok = cnodelookup(config.ctree, &n, name)) != Nfs3Ok){
       +                        nfs3errstr(ok);
       +                        trace("lookup: cnodelookup: %r\n");
       +                        return ok;
       +                }
       +                fid.cnode = n;
       +                if(fid.fsys){
       +                        if((ok = fsyslookup(fid.fsys, au, &fid.fsyshandle, name, &xh)) != Nfs3Ok){
       +                                nfs3errstr(ok);
       +                                trace("lookup: fsyslookup: %r\n");
       +                                return ok;
       +                        }
       +                        fid.fsyshandle = xh;
       +                }                
       +        }else{
       +                /*
       +                 * Walking dotdot.  Ick.
       +                 */
       +                trace("lookup dotdot fsys=%p\n", fid.fsys);
       +                if(fid.fsys){
       +                        /*
       +                         * Walk up file system, then try up config tree.
       +                         */
       +                        if((ok = fsyslookup(fid.fsys, au, &fid.fsyshandle, "..", &xh)) != Nfs3Ok){
       +                                nfs3errstr(ok);
       +                                trace("lookup fsyslookup: %r\n");
       +                                return ok;
       +                        }
       +                        fid.fsyshandle = xh;
       +
       +                        /*
       +                         * Usually just go to n->parent.
       +                         * 
       +                         * If we're in a subtree of the mounted file system that
       +                         * isn't represented explicitly by the config tree (instead
       +                         * the black hole node represents the entire file tree),
       +                         * then we only go to n->parent when we've dotdotted back
       +                         * to the right handle.
       +                         */
       +                        if(n->parent == nil)
       +                                trace("lookup dotdot: no parent\n");
       +                        else{
       +                                trace("lookup dotdot: parent %.*lH, have %.*lH\n",
       +                                        n->parent->fsyshandle.len, n->parent->fsyshandle.h,
       +                                        xh.len, xh.h);
       +                        }
       +                        
       +                        if(n->isblackhole){
       +                                if(handlecmp(&n->parent->mfsyshandle, &xh) == 0)
       +                                        n = n->parent;
       +                        }else{
       +                                if(n->parent)
       +                                        n = n->parent;
       +                        }
       +                }else{
       +                        /*
       +                         * No file system, just walk up.
       +                         */
       +                        if(n->parent)
       +                                n = n->parent;
       +                }
       +                fid.fsys = n->fsys;
       +                fid.fsyshandle = n->fsyshandle;
       +                fid.cnode = n;
       +        }
       +        fidtohandle(&fid, nh);
       +        return Nfs3Ok;
       +}
       +
       +Nfs3Status
       +fsaccess(SunAuthUnix *au, Nfs3Handle *h, u32int want, u32int *got, Nfs3Attr *attr)
       +{
       +        Fid fid;
       +        Nfs3Status ok;
       +        
       +        trace("access %.*lH 0x%ux\n", h->len, h->h, want);
       +        if((ok = handletofid(h, &fid, HAccess)) != Nfs3Ok)
       +                return ok;
       +        if(fid.fsys)
       +                return fsysaccess(fid.fsys, au, &fid.fsyshandle, want, got, attr);
       +        *got = want & (Nfs3AccessRead|Nfs3AccessLookup|Nfs3AccessExecute);
       +        return cnodegetattr(fid.cnode, attr);
       +}
       +
       +Nfs3Status
       +fsreadlink(SunAuthUnix *au, Nfs3Handle *h, char **link)
       +{
       +        Fid fid;
       +        Nfs3Status ok;
       +        
       +        trace("readlink %.*lH\n", h->len, h->h);
       +        if((ok = handletofid(h, &fid, HRead)) != Nfs3Ok)
       +                return ok;
       +        if(fid.fsys)
       +                return fsysreadlink(fid.fsys, au, &fid.fsyshandle, link);
       +        *link = 0;
       +        return Nfs3ErrNotSupp;
       +}
       +
       +Nfs3Status
       +fsreadfile(SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
       +{
       +        Fid fid;
       +        Nfs3Status ok;
       +        
       +        trace("readfile %.*lH\n", h->len, h->h);
       +        if((ok = handletofid(h, &fid, HRead)) != Nfs3Ok)
       +                return ok;
       +        if(fid.cnode->read)
       +                return fid.cnode->read(fid.cnode, count, offset, data, pcount, peof);
       +        if(fid.fsys)
       +                return fsysreadfile(fid.fsys, au, &fid.fsyshandle, count, offset, data, pcount, peof);
       +        return Nfs3ErrNotSupp;
       +}
       +
       +Nfs3Status
       +fsreaddir(SunAuthUnix *au, Nfs3Handle *h, u32int len, u64int cookie, uchar **pdata, u32int *pcount, u1int *peof)
       +{
       +        Fid fid;
       +        Nfs3Status ok;
       +
       +        trace("readdir %.*lH\n", h->len, h->h);        
       +        if((ok = handletofid(h, &fid, HRead)) != Nfs3Ok)
       +                return ok;
       +        if(fid.fsys)
       +                return fsysreaddir(fid.fsys, au, &fid.fsyshandle, len, cookie, pdata, pcount, peof);
       +        return cnodereaddir(fid.cnode, len, cookie, pdata, pcount, peof);
       +}
       +
       +Nfs3Status
       +logread(Cnode *n, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
       +{
       +        *pcount = 0;
       +        *peof = 1;
       +        return Nfs3Ok;
       +}
       +
       +Nfs3Status
       +refreshdiskread(Cnode *n, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
       +{
       +        char buf[128];
       +        
       +        if(offset != 0){
       +                *pcount = 0;
       +                *peof = 1;
       +                return Nfs3Ok;
       +        }
       +        if(refreshdisk() < 0)
       +                snprint(buf, sizeof buf, "refreshdisk: %r\n");
       +        else
       +                strcpy(buf, "ok\n");
       +        *data = emalloc(strlen(buf));
       +        strcpy((char*)*data, buf);
       +        *pcount = strlen(buf);
       +        *peof = 1;
       +        return Nfs3Ok;
       +}
       +
       +Nfs3Status
       +refreshconfigread(Cnode *n, u32int count, u64int offset, uchar **data, u32int *pcount, u1int *peof)
       +{
       +        char buf[128];
       +        
       +        if(offset != 0){
       +                *pcount = 0;
       +                *peof = 1;
       +                return Nfs3Ok;
       +        }
       +        if(readconfigfile(&config) < 0)
       +                snprint(buf, sizeof buf, "readconfig: %r\n");
       +        else
       +                strcpy(buf, "ok\n");
       +        *data = emalloc(strlen(buf));
       +        strcpy((char*)*data, buf);
       +        *pcount = strlen(buf);
       +        *peof = 1;
       +        return Nfs3Ok;
       +}
       +
       +void
       +abort(void)
       +{
       +        for(;;)
       +                *(int*)0=0;
       +}
 (DIR) diff --git a/src/cmd/vbackup/yesterday.rc b/src/cmd/vbackup/yesterday.rc
       t@@ -0,0 +1,109 @@
       +#!/usr/local/plan9/bin/rc
       +
       +path=($path $PLAN9/bin)
       +
       +fn usage {
       +        echo 'usage: yesterday [-cd] [-[[yy]yy]mm]dd] [-n daysago] file ...' >[1=2]
       +        exit 1
       +}
       +
       +fn Xcp {
       +        echo cp $1 $2
       +        cp $1 $2
       +}
       +
       +fn Xcarefulcp {
       +        if(! cmp -s $1 $2) Xcp $1 $2
       +}
       +
       +fn Xdiff {
       +        echo diff -c $1 $2
       +        diff -c $1 $2
       +}
       +
       +fn Xecho {
       +        echo $1
       +}
       +
       +year=`{date|sed 's/.* //'}
       +copy=Xecho
       +last=()
       +while(! ~ $#* 0 && ~ $1 -* && ! ~ $1 --){
       +        switch($1){
       +        case -c
       +                copy=Xcp
       +                shift
       +        case -d
       +                copy=Xdiff
       +                shift
       +        case -C
       +                copy=Xcarefulcp
       +                shift
       +        case -n*
       +                if(~ $1 -n){
       +                        if(~ $#* 1)
       +                                usage
       +                        shift
       +                        days=$1
       +                }
       +                if not
       +                        days=`{echo $1 | sed 's/^-.//'}
       +                last=`{date -r `{perl -e 'print time() - '$days'*60*60*24'} | 
       +                                9 sed -e 's%... (...) (..) ..:..:.. ... (....)%\3/\1\2%' -e 'y/ /0/' -e $smon}
       +                shift
       +        case -[0-9]
       +                mon=`{date|9 sed 's/^....(...).*/\1/' -e $smon}
       +                last=$year/$mon ^`{echo $1|sed 's/^-/0/'}
       +                shift
       +        case -[0-9][0-9]
       +                mon=`{date|9 sed 's/^....(...).*/\1/' -e $smon}
       +                last=$year/$mon ^`{echo $1|9 sed 's/^-//'}
       +                shift
       +        case -[0-9][0-9][0-9][0-9]
       +                last=$year/ ^ `{echo $1|9 sed 's/^-//'}
       +                shift
       +        case -[0-9][0-9][0-9][0-9][0-9][0-9]
       +                last=`{echo $year|9 sed 's/..$//'} ^ `{echo $1|9 sed 's/^-(..)/\1\//'}
       +                shift
       +        case -[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]
       +                last=`{echo $1|9 sed 's/^-(....)/\1\//'}
       +                shift
       +        case *
       +                usage
       +        }
       +}
       +if(! ~ $#* 0 && ~ $1 --)
       +        shift
       +
       +if(~ $#* 0)
       +        usage
       +
       +dir=`{pwd}
       +if(! ~ $status ''){
       +        echo 'yesterday: can''t find directory' >[1=2]
       +        exit 'pwd failed'
       +}
       +
       +h=`{hostname}
       +switch($h){
       +case amsterdam
       +        xdump=/dump/am
       +case *
       +        if(! test -d /dump/$h){
       +                echo 'no dumps on '^`{hostname} >[1=2]
       +                exit 1
       +        }
       +        xdump=/dump/$h
       +}
       +
       +for(i){
       +        xpath=$i
       +        if(! ~ $xpath /*)
       +                xpath=`{9 cleanname -d `{pwd} $i}
       +        dumppath=$xpath
       +        if(~ $#last 0)
       +                xlast=`{9 ls -t $xdump/$year|sed 1q}
       +        if not
       +                xlast=$xdump/$last
       +        $copy $xlast^$dumppath $xpath
       +}