620 lines · 14.1 KB · C
RawDownload
/* * 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 *	-f	'fast' writes - skip write if block exists on server *	-i	read old scores incrementally *	-m	set mount name *	-M	set mount place *	-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 = 32768}; typedef struct WriteReq WriteReq;struct WriteReq{	Packet *p;	uint type;	uchar score[VtScoreSize];}; 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		fastwrites;		/* do not write blocks already on server */int		fsscanblock;	/* last block scanned */Fsys*	fsys;			/* file system being backed up */int		incremental;	/* use vscores rather than bscores */int		nchange;		/* number of changed blocks */int		nop;			/* don't actually send blocks to venti */int		nskip;		/* number of blocks skipped (already on server) */int		nwritethread;	/* 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 */VtFile*	vscores;		/* venti file with block scores */Channel*	writechan;	/* chan(WriteReq) */VtConn*	z;			/* connection to venti */VtCache*	zcache;		/* cache of venti blocks */uchar*	zero;			/* blocksize zero bytes */ int nsend, nrecv; void		cmpproc(void*);void		fsysproc(void*);void		statusproc(void*);void		ventiproc(void*);int		timefmt(Fmt*);char*	guessmountplace(char *dev); voidusage(void){	fprint(2, "usage: vbackup [-DVnv] [-m mtpt] [-M mtpl] [-s secs] [-w n] disk [score]\n");	threadexitsall("usage");} voidthreadmain(int argc, char **argv){	char *pref, *mountname, *mountplace;	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();	mountplace = nil;	ARGBEGIN{	default:		usage();		break;	case 'D':		debug++;		break;	case 'V':		chattyventi = 1;		break;	case 'f':		fastwrites = 1;		break;	case 'i':		incremental = 1;		break;	case 'm':		mountname = EARGF(usage());		break;	case 'M':		mountplace = EARGF(usage());		i = strlen(mountplace);		if(i > 0 && mountplace[i-1] == '/')			mountplace[i-1] = 0;		break;	case 'n':		nop = 1;		break;	case 's':		statustime = atoi(EARGF(usage()));		break;	case 'v':		verbose = 1;		break;	case 'w':		nwritethread = 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, 32768, 2*MAXQ+16)) == nil)		sysfatal("diskcache: %r");	if((fsys = fsysopen(disk)) == nil)		sysfatal("fsysopen: %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);	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, VtRootSize);		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, VtEntrySize);		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);	} 	tmpnam = nil;	if(incremental){		if(vtfilegetentry(vfile, &e) < 0)			sysfatal("vtfilegetentry: %r");		if((vscores = vtfileopenroot(c, &e)) == nil)			sysfatal("vtfileopenroot: %r");		vtfileunlock(vfile);	}else{		/*		 * 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, ORDWR|ORCLOSE)) < 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);		qfree(qcmp);	qfree(qventi); 	if(statustime)		print("# %T procs exited: %d of %d %d-byte blocks changed, "			"%d read, %d written, %d skipped, %d copied\n",			nchange, fsys->nblock, fsys->blocksize,			vtcachenread, vtcachenwrite, nskip, vtcachencopy); 	/*	 * prepare root block	 */	if(incremental)		vtfileclose(vscores);	vtfilelock(vfile, -1);	if(vtfileflush(vfile) < 0)		sysfatal("vtfileflush: %r");	if(vtfilegetentry(vfile, &e) < 0)		sysfatal("vtfilegetentry: %r");	vtfileunlock(vfile);	vtfileclose(vfile); 	b = vtcacheallocblock(c, VtDirType, VtEntrySize);	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, VtRootSize);	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++;	if(mountplace == nil)		mountplace = guessmountplace(argv[0]);	print("mount /%s/%d/%02d%02d%s %s:%V %d/%02d%02d/%02d%02d\n",		mountname, tm.year, tm.mon, tm.mday, 		mountplace,		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");		fsysclose(fsys);	diskclose(disk);	vtcachefree(zcache); 	// Vtgoodbye hangs on Linux - not sure why.	// Probably vtfcallrpc doesn't quite get the	// remote hangup right.  Also probably related	// to the vtrecvproc problem below.	// vtgoodbye(z); 	// Leak here, because I can't seem to make	// the vtrecvproc exit.	// vtfreeconn(z); 	free(tmpnam);	z = nil;	zcache = nil;	fsys = nil;	disk = nil;	threadexitsall(nil);} voidfsysproc(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); 	if(statustime)		print("# %T fsys proc exiting\n");	runlock(&endlk);} voidcmpproc(void *dummy){	uchar *data;	Block *db;	u32int bno, bsize;	uchar score[VtScoreSize];	uchar score1[VtScoreSize]; 	USED(dummy); 	if(incremental)		vtfilelock(vscores, VtOREAD);	bsize = fsys->blocksize;	while((db = qread(qcmp, &bno)) != nil){		data = db->data;		sha1(data, vtzerotruncate(VtDataType, data, bsize), score, nil);		if(incremental){			if(vtfileblockscore(vscores, bno, score1) < 0)				sysfatal("cmpproc vtfileblockscore %d: %r", bno);		}else{			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);	if(incremental)		vtfileunlock(vscores);	if(statustime)		print("# %T cmp proc exiting\n");	runlock(&endlk);} voidwritethread(void *v){	WriteReq wr;	char err[ERRMAX]; 	USED(v); 	while(recv(writechan, &wr) == 1){		nrecv++;		if(wr.p == nil)			break;				if(fastwrites && vtread(z, wr.score, wr.type, nil, 0) < 0){			rerrstr(err, sizeof err);			if(strstr(err, "read too small")){	/* already exists */				nskip++;				packetfree(wr.p);				continue;			}		}		if(vtwritepacket(z, wr.score, wr.type, wr.p) < 0)			sysfatal("vtwritepacket: %r");		packetfree(wr.p);	}} intmyvtwrite(VtConn *z, uchar score[VtScoreSize], uint type, uchar *buf, int n){	WriteReq wr; 	if(nwritethread == 0){		n = vtwrite(z, score, type, buf, n);		if(n < 0)			sysfatal("vtwrite: %r");		return n;	} 	wr.p = packetalloc();	packetappend(wr.p, buf, n);	packetsha1(wr.p, score);

Lines 1–500 of 620.