aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/vbackup/vbackup.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/vbackup/vbackup.c')
-rw-r--r--src/cmd/vbackup/vbackup.c524
1 files changed, 524 insertions, 0 deletions
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);
+}
+