aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/man1/hist.178
-rw-r--r--man/man1/vbackup.1263
-rw-r--r--man/man1/yesterday.199
-rw-r--r--src/cmd/vbackup/COPYRIGHT27
-rw-r--r--src/cmd/vbackup/config.c515
-rw-r--r--src/cmd/vbackup/diskcat.c54
-rw-r--r--src/cmd/vbackup/diskftp.c134
-rw-r--r--src/cmd/vbackup/disknfs.c126
-rw-r--r--src/cmd/vbackup/hist.c282
-rw-r--r--src/cmd/vbackup/mkfile28
-rw-r--r--src/cmd/vbackup/mount-FreeBSD.c52
-rw-r--r--src/cmd/vbackup/mount-Linux.c58
-rw-r--r--src/cmd/vbackup/mount-none.c12
-rw-r--r--src/cmd/vbackup/mountnfs.h1
-rw-r--r--src/cmd/vbackup/nfs3srv.c428
-rw-r--r--src/cmd/vbackup/nfs3srv.h16
-rw-r--r--src/cmd/vbackup/queue.c64
-rw-r--r--src/cmd/vbackup/queue.h22
-rw-r--r--src/cmd/vbackup/util.c23
-rw-r--r--src/cmd/vbackup/vbackup.c524
-rw-r--r--src/cmd/vbackup/vcat.c76
-rw-r--r--src/cmd/vbackup/vftp.c0
-rw-r--r--src/cmd/vbackup/vmount.c77
-rwxr-xr-xsrc/cmd/vbackup/vmount.rc19
-rw-r--r--src/cmd/vbackup/vmount0.c77
-rw-r--r--src/cmd/vbackup/vnfs.c1273
-rwxr-xr-xsrc/cmd/vbackup/yesterday.rc109
27 files changed, 4437 insertions, 0 deletions
diff --git a/man/man1/hist.1 b/man/man1/hist.1
new file mode 100644
index 00000000..15e8df09
--- /dev/null
+++ b/man/man1/hist.1
@@ -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).
diff --git a/man/man1/vbackup.1 b/man/man1/vbackup.1
new file mode 100644
index 00000000..72d93e73
--- /dev/null
+++ b/man/man1/vbackup.1
@@ -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
diff --git a/man/man1/yesterday.1 b/man/man1/yesterday.1
new file mode 100644
index 00000000..b8a198ba
--- /dev/null
+++ b/man/man1/yesterday.1
@@ -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.
diff --git a/src/cmd/vbackup/COPYRIGHT b/src/cmd/vbackup/COPYRIGHT
new file mode 100644
index 00000000..2af95d83
--- /dev/null
+++ b/src/cmd/vbackup/COPYRIGHT
@@ -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.
+
diff --git a/src/cmd/vbackup/config.c b/src/cmd/vbackup/config.c
new file mode 100644
index 00000000..0efeeee9
--- /dev/null
+++ b/src/cmd/vbackup/config.c
@@ -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;
+}
diff --git a/src/cmd/vbackup/diskcat.c b/src/cmd/vbackup/diskcat.c
new file mode 100644
index 00000000..92586890
--- /dev/null
+++ b/src/cmd/vbackup/diskcat.c
@@ -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;
+}
diff --git a/src/cmd/vbackup/diskftp.c b/src/cmd/vbackup/diskftp.c
new file mode 100644
index 00000000..b8c26ed0
--- /dev/null
+++ b/src/cmd/vbackup/diskftp.c
@@ -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);
+}
diff --git a/src/cmd/vbackup/disknfs.c b/src/cmd/vbackup/disknfs.c
new file mode 100644
index 00000000..edbcf3e4
--- /dev/null
+++ b/src/cmd/vbackup/disknfs.c
@@ -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);
+}
+
diff --git a/src/cmd/vbackup/hist.c b/src/cmd/vbackup/hist.c
new file mode 100644
index 00000000..39836b12
--- /dev/null
+++ b/src/cmd/vbackup/hist.c
@@ -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;
+}
diff --git a/src/cmd/vbackup/mkfile b/src/cmd/vbackup/mkfile
new file mode 100644
index 00000000..6f0aba7e
--- /dev/null
+++ b/src/cmd/vbackup/mkfile
@@ -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
+
diff --git a/src/cmd/vbackup/mount-FreeBSD.c b/src/cmd/vbackup/mount-FreeBSD.c
new file mode 100644
index 00000000..ac5cab2a
--- /dev/null
+++ b/src/cmd/vbackup/mount-FreeBSD.c
@@ -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");
+}
diff --git a/src/cmd/vbackup/mount-Linux.c b/src/cmd/vbackup/mount-Linux.c
new file mode 100644
index 00000000..39b96a31
--- /dev/null
+++ b/src/cmd/vbackup/mount-Linux.c
@@ -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");
+}
+
diff --git a/src/cmd/vbackup/mount-none.c b/src/cmd/vbackup/mount-none.c
new file mode 100644
index 00000000..db8e1e5b
--- /dev/null
+++ b/src/cmd/vbackup/mount-none.c
@@ -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");
+}
+
diff --git a/src/cmd/vbackup/mountnfs.h b/src/cmd/vbackup/mountnfs.h
new file mode 100644
index 00000000..b8aab4cb
--- /dev/null
+++ b/src/cmd/vbackup/mountnfs.h
@@ -0,0 +1 @@
+void mountnfs(int proto, struct sockaddr_in*, uchar*, int, char*);
diff --git a/src/cmd/vbackup/nfs3srv.c b/src/cmd/vbackup/nfs3srv.c
new file mode 100644
index 00000000..b2cdd9cf
--- /dev/null
+++ b/src/cmd/vbackup/nfs3srv.c
@@ -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);
+}
+
diff --git a/src/cmd/vbackup/nfs3srv.h b/src/cmd/vbackup/nfs3srv.h
new file mode 100644
index 00000000..329db782
--- /dev/null
+++ b/src/cmd/vbackup/nfs3srv.h
@@ -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,
+};
+
diff --git a/src/cmd/vbackup/queue.c b/src/cmd/vbackup/queue.c
new file mode 100644
index 00000000..91fa221d
--- /dev/null
+++ b/src/cmd/vbackup/queue.c
@@ -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);
+}
diff --git a/src/cmd/vbackup/queue.h b/src/cmd/vbackup/queue.h
new file mode 100644
index 00000000..053346ad
--- /dev/null
+++ b/src/cmd/vbackup/queue.h
@@ -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);
diff --git a/src/cmd/vbackup/util.c b/src/cmd/vbackup/util.c
new file mode 100644
index 00000000..0a46a7a0
--- /dev/null
+++ b/src/cmd/vbackup/util.c
@@ -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;
+}
diff --git a/src/cmd/vbackup/vbackup.c b/src/cmd/vbackup/vbackup.c
new file mode 100644
index 00000000..4fdaf3e4
--- /dev/null
+++ b/src/cmd/vbackup/vbackup.c
@@ -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);
+}
+
diff --git a/src/cmd/vbackup/vcat.c b/src/cmd/vbackup/vcat.c
new file mode 100644
index 00000000..581adcb8
--- /dev/null
+++ b/src/cmd/vbackup/vcat.c
@@ -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);
+}
diff --git a/src/cmd/vbackup/vftp.c b/src/cmd/vbackup/vftp.c
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/cmd/vbackup/vftp.c
diff --git a/src/cmd/vbackup/vmount.c b/src/cmd/vbackup/vmount.c
new file mode 100644
index 00000000..8d650586
--- /dev/null
+++ b/src/cmd/vbackup/vmount.c
@@ -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);
+}
diff --git a/src/cmd/vbackup/vmount.rc b/src/cmd/vbackup/vmount.rc
new file mode 100755
index 00000000..6d717f0b
--- /dev/null
+++ b/src/cmd/vbackup/vmount.rc
@@ -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
+}
+
diff --git a/src/cmd/vbackup/vmount0.c b/src/cmd/vbackup/vmount0.c
new file mode 100644
index 00000000..8d650586
--- /dev/null
+++ b/src/cmd/vbackup/vmount0.c
@@ -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);
+}
diff --git a/src/cmd/vbackup/vnfs.c b/src/cmd/vbackup/vnfs.c
new file mode 100644
index 00000000..6c6421c5
--- /dev/null
+++ b/src/cmd/vbackup/vnfs.c
@@ -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;
+}
diff --git a/src/cmd/vbackup/yesterday.rc b/src/cmd/vbackup/yesterday.rc
new file mode 100755
index 00000000..bf31dac6
--- /dev/null
+++ b/src/cmd/vbackup/yesterday.rc
@@ -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
+}