commit 004aa293f360ea0f63ec50f5042f8c0fb2831e4f from: rsc date: Wed Jul 13 03:49:41 2005 UTC Dump-like file system backup for Unix, built on Venti. commit - 0c98da8bf8ea51d0288222f6c6ba3c125cf20f46 commit + 004aa293f360ea0f63ec50f5042f8c0fb2831e4f blob - /dev/null blob + 15e8df09c842ec3fe769970249f1549ee729228d (mode 644) --- /dev/null +++ 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). blob - /dev/null blob + 72d93e73a9b8928f2e76b991006d4d9270dfb3fb (mode 644) --- /dev/null +++ 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 blob - /dev/null blob + b8a198ba1739c9eb783b290be70277a9f95519fd (mode 644) --- /dev/null +++ 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. blob - /dev/null blob + 2af95d83d9ac96aed1a71134c4b3e9ec6095b9b7 (mode 644) --- /dev/null +++ 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. + blob - /dev/null blob + 0efeeee92c8f47e6770d151f751717c6ddfef250 (mode 644) --- /dev/null +++ src/cmd/vbackup/config.c @@ -0,0 +1,515 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef stime +#define stime configstime /* sometimes in */ +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; ihash); 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; +} blob - /dev/null blob + 9258689067918a0addc27770c22d2525876b8209 (mode 644) --- /dev/null +++ src/cmd/vbackup/diskcat.c @@ -0,0 +1,54 @@ +#include +#include +#include + +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; inblock; 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; +} blob - /dev/null blob + b8c26ed0f36866205a891690ed6037dff6d4980f (mode 644) --- /dev/null +++ src/cmd/vbackup/diskftp.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include + +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); +} blob - /dev/null blob + edbcf3e421ddf7d7b483203e0f2f5b8e41a7a3e8 (mode 644) --- /dev/null +++ src/cmd/vbackup/disknfs.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#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); +} + blob - /dev/null blob + 39836b1269a13e3ba34dd0d80d6a0330f79c07e5 (mode 644) --- /dev/null +++ src/cmd/vbackup/hist.c @@ -0,0 +1,282 @@ +#include +#include +#include + +#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; imtime), 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; +} blob - /dev/null blob + 6f0aba7ee687363000007838a288066bc0b177ad (mode 644) --- /dev/null +++ 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 + blob - /dev/null blob + ac5cab2a9e03f6e1205b0935c280c490f50aa114 (mode 644) --- /dev/null +++ src/cmd/vbackup/mount-FreeBSD.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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"); +} blob - /dev/null blob + 39b96a31f1df43c78d85d07333cbb39a54b14345 (mode 644) --- /dev/null +++ src/cmd/vbackup/mount-Linux.c @@ -0,0 +1,58 @@ +#include +#include +#include +#ifdef __Linux24__ +# define __KERNEL__ +# include +# undef __KERNEL__ +#else +# include +#endif +#include +#include +#include +#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"); +} + blob - /dev/null blob + db8e1e5bc5e5e1d45a324be384081b52cfb0bd0a (mode 644) --- /dev/null +++ src/cmd/vbackup/mount-none.c @@ -0,0 +1,12 @@ +#include +#include +#include +#include +#include "mountnfs.h" + +void +mountnfs(int proto, struct sockaddr_in *addr, uchar *handle, int hlen, char *mtpt) +{ + sysfatal("mountnfs not implemented"); +} + blob - /dev/null blob + b8aab4cb6f06c0b31bcc7d503e424f0e0be69542 (mode 644) --- /dev/null +++ src/cmd/vbackup/mountnfs.h @@ -0,0 +1 @@ +void mountnfs(int proto, struct sockaddr_in*, uchar*, int, char*); blob - /dev/null blob + b2cdd9cfe321c89541676289c8f4865c1f18173b (mode 644) --- /dev/null +++ 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 +#include +#include +#include +#include +#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); +} + blob - /dev/null blob + 329db782d5a14214153f012ae0dcc248f9ed5b60 (mode 644) --- /dev/null +++ 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, +}; + blob - /dev/null blob + 91fa221d399afd070b6e4499f1c27b769a32552a (mode 644) --- /dev/null +++ src/cmd/vbackup/queue.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#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); +} blob - /dev/null blob + 053346ad88319adc0689872a3f14582f436762de (mode 644) --- /dev/null +++ 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); blob - /dev/null blob + 0a46a7a006952958ad86d1fcb6e031bb59531fd0 (mode 644) --- /dev/null +++ src/cmd/vbackup/util.c @@ -0,0 +1,23 @@ +#include +#include +#include + +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; +} blob - /dev/null blob + 4fdaf3e4d2fea0806bd00f276f170af7c1ea2c3d (mode 644) --- /dev/null +++ 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 +#include +#include +#include +#include +#include +#include +#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; inblock; 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; inblock; 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; iblocksize; + 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; iclosed && 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); +} + blob - /dev/null blob + 581adcb880ca78819ceac7371aafec22ae5d59ab (mode 644) --- /dev/null +++ src/cmd/vbackup/vcat.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +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; inblock; 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); +} blob - /dev/null blob + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 (mode 644) blob - /dev/null blob + 8d6505866d5b1c323f392464d915dac9dd045384 (mode 644) --- /dev/null +++ src/cmd/vbackup/vmount.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#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); +} blob - /dev/null blob + 6d717f0b7fa23ebfcdb39af66f6d6ebb21a7a8fc (mode 755) --- /dev/null +++ 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 +} + blob - /dev/null blob + 8d6505866d5b1c323f392464d915dac9dd045384 (mode 644) --- /dev/null +++ src/cmd/vbackup/vmount0.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#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); +} blob - /dev/null blob + 6c6421c538130f592af63a2ae8a317b1e2d76056 (mode 644) --- /dev/null +++ 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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>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; ihash); 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; ihash); 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; ihash); 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; imask, 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; +} blob - /dev/null blob + bf31dac62b026789d56b90f6c9c60925015fdad1 (mode 755) --- /dev/null +++ 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 +}