plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

vac.c (16320B)


      1 #include "stdinc.h"
      2 #include "vac.h"
      3 #include "dat.h"
      4 #include "fns.h"
      5 
      6 // TODO: qids
      7 
      8 void
      9 usage(void)
     10 {
     11 	fprint(2, "vac [-imqsv] [-a archive.vac] [-b bsize] [-d old.vac] [-f new.vac] [-e exclude]... [-h host] file...\n");
     12 	threadexitsall("usage");
     13 }
     14 
     15 enum
     16 {
     17 	BlockSize = 8*1024,
     18 	CacheSize = 128<<20,
     19 };
     20 
     21 struct
     22 {
     23 	int nfile;
     24 	int ndir;
     25 	vlong data;
     26 	vlong skipdata;
     27 	int skipfiles;
     28 } stats;
     29 
     30 int qdiff;
     31 int merge;
     32 int verbose;
     33 char *host;
     34 VtConn *z;
     35 VacFs *fs;
     36 char *archivefile;
     37 char *vacfile;
     38 
     39 int vacmerge(VacFile*, char*);
     40 void vac(VacFile*, VacFile*, char*, Dir*);
     41 void vacstdin(VacFile*, char*);
     42 VacFile *recentarchive(VacFs*, char*);
     43 
     44 static u64int unittoull(char*);
     45 static void warn(char *fmt, ...);
     46 static void removevacfile(void);
     47 
     48 #ifdef PLAN9PORT
     49 /*
     50  * We're between a rock and a hard place here.
     51  * The pw library (getpwnam, etc.) reads the
     52  * password and group files into an on-stack buffer,
     53  * so if you have some huge groups, you overflow
     54  * the stack.  Because of this, the thread library turns
     55  * it off by default, so that dirstat returns "14571" instead of "rsc".
     56  * But for vac we want names.  So cautiously turn the pwlibrary
     57  * back on (see threadmain) and make the main thread stack huge.
     58  */
     59 extern int _p9usepwlibrary;
     60 int mainstacksize = 4*1024*1024;
     61 
     62 #endif
     63 void
     64 threadmain(int argc, char **argv)
     65 {
     66 	int i, j, fd, n, printstats;
     67 	Dir *d;
     68 	char *s;
     69 	uvlong u;
     70 	VacFile *f, *fdiff;
     71 	VacFs *fsdiff;
     72 	int blocksize;
     73 	int outfd;
     74 	char *stdinname;
     75 	char *diffvac;
     76 	uvlong qid;
     77 
     78 #ifdef PLAN9PORT
     79 	/* see comment above */
     80 	_p9usepwlibrary = 1;
     81 #endif
     82 
     83 	fmtinstall('F', vtfcallfmt);
     84 	fmtinstall('H', encodefmt);
     85 	fmtinstall('V', vtscorefmt);
     86 
     87 	blocksize = BlockSize;
     88 	stdinname = nil;
     89 	printstats = 0;
     90 	fsdiff = nil;
     91 	diffvac = nil;
     92 
     93 	ARGBEGIN{
     94 	case 'V':
     95 		chattyventi++;
     96 		break;
     97 	case 'a':
     98 		archivefile = EARGF(usage());
     99 		break;
    100 	case 'b':
    101 		u = unittoull(EARGF(usage()));
    102 		if(u < 512)
    103 			u = 512;
    104 		blocksize = u;
    105 		break;
    106 	case 'd':
    107 		diffvac = EARGF(usage());
    108 		break;
    109 	case 'e':
    110 		excludepattern(EARGF(usage()));
    111 		break;
    112 	case 'f':
    113 		vacfile = EARGF(usage());
    114 		break;
    115 	case 'h':
    116 		host = EARGF(usage());
    117 		break;
    118 	case 'i':
    119 		stdinname = EARGF(usage());
    120 		break;
    121 	case 'm':
    122 		merge++;
    123 		break;
    124 	case 'q':
    125 		qdiff++;
    126 		break;
    127 	case 's':
    128 		printstats++;
    129 		break;
    130 	case 'v':
    131 		verbose++;
    132 		break;
    133 	case 'x':
    134 		loadexcludefile(EARGF(usage()));
    135 		break;
    136 	default:
    137 		usage();
    138 	}ARGEND
    139 
    140 	if(argc == 0 && !stdinname)
    141 		usage();
    142 
    143 	if(archivefile && (vacfile || diffvac)){
    144 		fprint(2, "cannot use -a with -f, -d\n");
    145 		usage();
    146 	}
    147 
    148 	z = vtdial(host);
    149 	if(z == nil)
    150 		sysfatal("could not connect to server: %r");
    151 	if(vtconnect(z) < 0)
    152 		sysfatal("vtconnect: %r");
    153 
    154 	// Setup:
    155 	//	fs is the output vac file system
    156 	//	f is directory in output vac to write new files
    157 	//	fdiff is corresponding directory in existing vac
    158 	if(archivefile){
    159 		VacFile *fp;
    160 		char yyyy[5];
    161 		char mmdd[10];
    162 		char oldpath[40];
    163 		Tm tm;
    164 
    165 		fdiff = nil;
    166 		if((outfd = open(archivefile, ORDWR)) < 0){
    167 			if(access(archivefile, 0) >= 0)
    168 				sysfatal("open %s: %r", archivefile);
    169 			if((outfd = create(archivefile, OWRITE, 0666)) < 0)
    170 				sysfatal("create %s: %r", archivefile);
    171 			atexit(removevacfile);	// because it is new
    172 			if((fs = vacfscreate(z, blocksize, CacheSize)) == nil)
    173 				sysfatal("vacfscreate: %r");
    174 		}else{
    175 			if((fs = vacfsopen(z, archivefile, VtORDWR, CacheSize)) == nil)
    176 				sysfatal("vacfsopen %s: %r", archivefile);
    177 			if((fdiff = recentarchive(fs, oldpath)) != nil){
    178 				if(verbose)
    179 					fprint(2, "diff %s\n", oldpath);
    180 			}else
    181 				if(verbose)
    182 					fprint(2, "no recent archive to diff against\n");
    183 		}
    184 
    185 		// Create yyyy/mmdd.
    186 		tm = *localtime(time(0));
    187 		snprint(yyyy, sizeof yyyy, "%04d", tm.year+1900);
    188 		fp = vacfsgetroot(fs);
    189 		if((f = vacfilewalk(fp, yyyy)) == nil
    190 		&& (f = vacfilecreate(fp, yyyy, ModeDir|0555)) == nil)
    191 			sysfatal("vacfscreate %s: %r", yyyy);
    192 		vacfiledecref(fp);
    193 		fp = f;
    194 
    195 		snprint(mmdd, sizeof mmdd, "%02d%02d", tm.mon+1, tm.mday);
    196 		n = 0;
    197 		while((f = vacfilewalk(fp, mmdd)) != nil){
    198 			vacfiledecref(f);
    199 			n++;
    200 			snprint(mmdd+4, sizeof mmdd-4, ".%d", n);
    201 		}
    202 		f = vacfilecreate(fp, mmdd, ModeDir|0555);
    203 		if(f == nil)
    204 			sysfatal("vacfscreate %s/%s: %r", yyyy, mmdd);
    205 		vacfiledecref(fp);
    206 
    207 		if(verbose)
    208 			fprint(2, "archive %s/%s\n", yyyy, mmdd);
    209 	}else{
    210 		if(vacfile == nil)
    211 			outfd = 1;
    212 		else if((outfd = create(vacfile, OWRITE, 0666)) < 0)
    213 			sysfatal("create %s: %r", vacfile);
    214 		atexit(removevacfile);
    215 		if((fs = vacfscreate(z, blocksize, CacheSize)) == nil)
    216 			sysfatal("vacfscreate: %r");
    217 		f = vacfsgetroot(fs);
    218 
    219 		fdiff = nil;
    220 		if(diffvac){
    221 			if((fsdiff = vacfsopen(z, diffvac, VtOREAD, CacheSize)) == nil)
    222 				warn("vacfsopen %s: %r", diffvac);
    223 			else
    224 				fdiff = vacfsgetroot(fsdiff);
    225 		}
    226 	}
    227 
    228 	if(stdinname)
    229 		vacstdin(f, stdinname);
    230 	for(i=0; i<argc; i++){
    231 		// We can't use / and . and .. and ../.. as valid archive
    232 		// names, so expand to the list of files in the directory.
    233 		if(argv[i][0] == 0){
    234 			warn("empty string given as command-line argument");
    235 			continue;
    236 		}
    237 		cleanname(argv[i]);
    238 		if(strcmp(argv[i], "/") == 0
    239 		|| strcmp(argv[i], ".") == 0
    240 		|| strcmp(argv[i], "..") == 0
    241 		|| (strlen(argv[i]) > 3 && strcmp(argv[i]+strlen(argv[i])-3, "/..") == 0)){
    242 			if((fd = open(argv[i], OREAD)) < 0){
    243 				warn("open %s: %r", argv[i]);
    244 				continue;
    245 			}
    246 			while((n = dirread(fd, &d)) > 0){
    247 				for(j=0; j<n; j++){
    248 					s = vtmalloc(strlen(argv[i])+1+strlen(d[j].name)+1);
    249 					strcpy(s, argv[i]);
    250 					strcat(s, "/");
    251 					strcat(s, d[j].name);
    252 					cleanname(s);
    253 					vac(f, fdiff, s, &d[j]);
    254 				}
    255 				free(d);
    256 			}
    257 			close(fd);
    258 			continue;
    259 		}
    260 		if((d = dirstat(argv[i])) == nil){
    261 			warn("stat %s: %r", argv[i]);
    262 			continue;
    263 		}
    264 		vac(f, fdiff, argv[i], d);
    265 		free(d);
    266 	}
    267 	if(fdiff)
    268 		vacfiledecref(fdiff);
    269 
    270 	/*
    271 	 * Record the maximum qid so that vacs can be merged
    272 	 * without introducing overlapping qids.  Older versions
    273 	 * of vac arranged that the root would have the largest
    274 	 * qid in the file system, but we can't do that anymore
    275 	 * (the root gets created first!).
    276 	 */
    277 	if(_vacfsnextqid(fs, &qid) >= 0)
    278 		vacfilesetqidspace(f, 0, qid);
    279 	vacfiledecref(f);
    280 
    281 	/*
    282 	 * Copy fsdiff's root block score into fs's slot for that,
    283 	 * so that vacfssync will copy it into root.prev for us.
    284 	 * Just nice documentation, no effect.
    285 	 */
    286 	if(fsdiff)
    287 		memmove(fs->score, fsdiff->score, VtScoreSize);
    288 	if(vacfssync(fs) < 0)
    289 		fprint(2, "vacfssync: %r\n");
    290 
    291 	fprint(outfd, "vac:%V\n", fs->score);
    292 	atexitdont(removevacfile);
    293 	vacfsclose(fs);
    294 	vthangup(z);
    295 
    296 	if(printstats){
    297 		fprint(2,
    298 			"%d files, %d files skipped, %d directories\n"
    299 			"%lld data bytes written, %lld data bytes skipped\n",
    300 			stats.nfile, stats.skipfiles, stats.ndir, stats.data, stats.skipdata);
    301 		dup(2, 1);
    302 		packetstats();
    303 	}
    304 	threadexitsall(0);
    305 }
    306 
    307 VacFile*
    308 recentarchive(VacFs *fs, char *path)
    309 {
    310 	VacFile *fp, *f;
    311 	VacDirEnum *de;
    312 	VacDir vd;
    313 	char buf[10];
    314 	int year, mmdd, nn, n, n1;
    315 	char *p;
    316 
    317 	fp = vacfsgetroot(fs);
    318 	de = vdeopen(fp);
    319 	year = 0;
    320 	if(de){
    321 		for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
    322 			if(strlen(vd.elem) != 4)
    323 				continue;
    324 			if((n = strtol(vd.elem, &p, 10)) < 1900 || *p != 0)
    325 				continue;
    326 			if(year < n)
    327 				year = n;
    328 		}
    329 	}
    330 	vdeclose(de);
    331 	if(year == 0){
    332 		vacfiledecref(fp);
    333 		return nil;
    334 	}
    335 	snprint(buf, sizeof buf, "%04d", year);
    336 	if((f = vacfilewalk(fp, buf)) == nil){
    337 		fprint(2, "warning: dirread %s but cannot walk", buf);
    338 		vacfiledecref(fp);
    339 		return nil;
    340 	}
    341 	fp = f;
    342 
    343 	de = vdeopen(fp);
    344 	mmdd = 0;
    345 	nn = 0;
    346 	if(de){
    347 		for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
    348 			if(strlen(vd.elem) < 4)
    349 				continue;
    350 			if((n = strtol(vd.elem, &p, 10)) < 100 || n > 1231 || p != vd.elem+4)
    351 				continue;
    352 			if(*p == '.'){
    353 				if(p[1] == '0' || (n1 = strtol(p+1, &p, 10)) == 0 || *p != 0)
    354 					continue;
    355 			}else{
    356 				if(*p != 0)
    357 					continue;
    358 				n1 = 0;
    359 			}
    360 			if(n < mmdd || (n == mmdd && n1 < nn))
    361 				continue;
    362 			mmdd = n;
    363 			nn = n1;
    364 		}
    365 	}
    366 	vdeclose(de);
    367 	if(mmdd == 0){
    368 		vacfiledecref(fp);
    369 		return nil;
    370 	}
    371 	if(nn == 0)
    372 		snprint(buf, sizeof buf, "%04d", mmdd);
    373 	else
    374 		snprint(buf, sizeof buf, "%04d.%d", mmdd, nn);
    375 	if((f = vacfilewalk(fp, buf)) == nil){
    376 		fprint(2, "warning: dirread %s but cannot walk", buf);
    377 		vacfiledecref(fp);
    378 		return nil;
    379 	}
    380 	vacfiledecref(fp);
    381 
    382 	sprint(path, "%04d/%s", year, buf);
    383 	return f;
    384 }
    385 
    386 static void
    387 removevacfile(void)
    388 {
    389 	if(vacfile)
    390 		remove(vacfile);
    391 }
    392 
    393 void
    394 plan9tovacdir(VacDir *vd, Dir *dir)
    395 {
    396 	memset(vd, 0, sizeof *vd);
    397 
    398 	vd->elem = dir->name;
    399 	vd->uid = dir->uid;
    400 	vd->gid = dir->gid;
    401 	vd->mid = dir->muid;
    402 	if(vd->mid == nil)
    403 		vd->mid = "";
    404 	vd->mtime = dir->mtime;
    405 	vd->mcount = 0;
    406 	vd->ctime = dir->mtime;		/* ctime: not available on plan 9 */
    407 	vd->atime = dir->atime;
    408 	vd->size = dir->length;
    409 
    410 	vd->mode = dir->mode & 0777;
    411 	if(dir->mode & DMDIR)
    412 		vd->mode |= ModeDir;
    413 	if(dir->mode & DMAPPEND)
    414 		vd->mode |= ModeAppend;
    415 	if(dir->mode & DMEXCL)
    416 		vd->mode |= ModeExclusive;
    417 #ifdef PLAN9PORT
    418 	if(dir->mode & DMDEVICE)
    419 		vd->mode |= ModeDevice;
    420 	if(dir->mode & DMNAMEDPIPE)
    421 		vd->mode |= ModeNamedPipe;
    422 	if(dir->mode & DMSYMLINK)
    423 		vd->mode |= ModeLink;
    424 #endif
    425 
    426 	vd->plan9 = 1;
    427 	vd->p9path = dir->qid.path;
    428 	vd->p9version = dir->qid.vers;
    429 }
    430 
    431 #ifdef PLAN9PORT
    432 enum {
    433 	Special =
    434 		DMSOCKET |
    435 		DMSYMLINK |
    436 		DMNAMEDPIPE |
    437 		DMDEVICE
    438 };
    439 #endif
    440 
    441 /*
    442  * Archive the file named name, which has stat info d,
    443  * into the vac directory fp (p = parent).
    444  *
    445  * If we're doing a vac -d against another archive, the
    446  * equivalent directory to fp in that archive is diffp.
    447  */
    448 void
    449 vac(VacFile *fp, VacFile *diffp, char *name, Dir *d)
    450 {
    451 	char *elem, *s;
    452 	static char *buf;
    453 	int fd, i, n, bsize;
    454 	vlong off;
    455 	Dir *dk;	// kids
    456 	VacDir vd, vddiff;
    457 	VacFile *f, *fdiff;
    458 	VtEntry e;
    459 
    460 	if(!includefile(name)){
    461 		warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : "");
    462 		return;
    463 	}
    464 
    465 	if(d->mode&DMDIR)
    466 		stats.ndir++;
    467 	else
    468 		stats.nfile++;
    469 
    470 	if(merge && vacmerge(fp, name) >= 0)
    471 		return;
    472 
    473 	if(verbose)
    474 		fprint(2, "%s%s\n", name, (d->mode&DMDIR) ? "/" : "");
    475 
    476 #ifdef PLAN9PORT
    477 	if(d->mode&Special)
    478 		fd = -1;
    479 	else
    480 #endif
    481 	if((fd = open(name, OREAD)) < 0){
    482 		warn("open %s: %r", name);
    483 		return;
    484 	}
    485 
    486 	elem = strrchr(name, '/');
    487 	if(elem)
    488 		elem++;
    489 	else
    490 		elem = name;
    491 
    492 	plan9tovacdir(&vd, d);
    493 	if((f = vacfilecreate(fp, elem, vd.mode)) == nil){
    494 		warn("vacfilecreate %s: %r", name);
    495 		return;
    496 	}
    497 	if(diffp)
    498 		fdiff = vacfilewalk(diffp, elem);
    499 	else
    500 		fdiff = nil;
    501 
    502 	if(vacfilesetdir(f, &vd) < 0)
    503 		warn("vacfilesetdir %s: %r", name);
    504 
    505 	bsize = fs->bsize;
    506 	if(buf == nil)
    507 		buf = vtmallocz(bsize);
    508 
    509 #ifdef PLAN9PORT
    510 	if(d->mode&(DMSOCKET|DMNAMEDPIPE)){
    511 		/* don't write anything */
    512 	}
    513 	else if(d->mode&DMSYMLINK){
    514 		n = readlink(name, buf, sizeof buf);
    515 		if(n > 0 && vacfilewrite(f, buf, n, 0) < 0){
    516 			warn("venti write %s: %r", name);
    517 			goto Out;
    518 		}
    519 		stats.data += n;
    520 	}else if(d->mode&DMDEVICE){
    521 		snprint(buf, sizeof buf, "%c %d %d",
    522 			(char)((d->qid.path >> 16) & 0xFF),
    523 			(int)(d->qid.path & 0xFF),
    524 			(int)((d->qid.path >> 8) & 0xFF));
    525 		if(vacfilewrite(f, buf, strlen(buf), 0) < 0){
    526 			warn("venti write %s: %r", name);
    527 			goto Out;
    528 		}
    529 		stats.data += strlen(buf);
    530 	}else
    531 #endif
    532 	if(d->mode&DMDIR){
    533 		while((n = dirread(fd, &dk)) > 0){
    534 			for(i=0; i<n; i++){
    535 				s = vtmalloc(strlen(name)+1+strlen(dk[i].name)+1);
    536 				strcpy(s, name);
    537 				strcat(s, "/");
    538 				strcat(s, dk[i].name);
    539 				vac(f, fdiff, s, &dk[i]);
    540 				free(s);
    541 			}
    542 			free(dk);
    543 		}
    544 	}else{
    545 		off = 0;
    546 		if(fdiff){
    547 			/*
    548 			 * Copy fdiff's contents into f by moving the score.
    549 			 * We'll diff and update below.
    550 			 */
    551 			if(vacfilegetentries(fdiff, &e, nil) >= 0)
    552 			if(vacfilesetentries(f, &e, nil) >= 0){
    553 				bsize = e.dsize;
    554 
    555 				/*
    556 				 * Or if -q is set, and the metadata looks the same,
    557 				 * don't even bother reading the file.
    558 				 */
    559 				if(qdiff && vacfilegetdir(fdiff, &vddiff) >= 0){
    560 					if(vddiff.mtime == vd.mtime)
    561 					if(vddiff.size == vd.size)
    562 					if(!vddiff.plan9 || (/* vddiff.p9path == vd.p9path && */ vddiff.p9version == vd.p9version)){
    563 						stats.skipfiles++;
    564 						stats.nfile--;
    565 						vdcleanup(&vddiff);
    566 						goto Out;
    567 					}
    568 
    569 					/*
    570 					 * Skip over presumably-unchanged prefix
    571 					 * of an append-only file.
    572 					 */
    573 					if(vd.mode&ModeAppend)
    574 					if(vddiff.size < vd.size)
    575 					if(vddiff.plan9 && vd.plan9)
    576 					if(vddiff.p9path == vd.p9path){
    577 						off = vd.size/bsize*bsize;
    578 						if(seek(fd, off, 0) >= 0)
    579 							stats.skipdata += off;
    580 						else{
    581 							seek(fd, 0, 0);	// paranoia
    582 							off = 0;
    583 						}
    584 					}
    585 
    586 					vdcleanup(&vddiff);
    587 					// XXX different verbose chatty prints for kaminsky?
    588 				}
    589 			}
    590 		}
    591 		if(qdiff && verbose)
    592 			fprint(2, "+%s\n", name);
    593 		while((n = readn(fd, buf, bsize)) > 0){
    594 			if(fdiff && sha1matches(f, off/bsize, (uchar*)buf, n)){
    595 				off += n;
    596 				stats.skipdata += n;
    597 				continue;
    598 			}
    599 			if(vacfilewrite(f, buf, n, off) < 0){
    600 				warn("venti write %s: %r", name);
    601 				goto Out;
    602 			}
    603 			stats.data += n;
    604 			off += n;
    605 		}
    606 		/*
    607 		 * Since we started with fdiff's contents,
    608 		 * set the size in case fdiff was bigger.
    609 		 */
    610 		if(fdiff && vacfilesetsize(f, off) < 0)
    611 			warn("vtfilesetsize %s: %r", name);
    612 	}
    613 
    614 Out:
    615 	vacfileflush(f, 1);
    616 	vacfiledecref(f);
    617 	if(fdiff)
    618 		vacfiledecref(fdiff);
    619 	close(fd);
    620 }
    621 
    622 void
    623 vacstdin(VacFile *fp, char *name)
    624 {
    625 	vlong off;
    626 	VacFile *f;
    627 	static char buf[8192];
    628 	int n;
    629 
    630 	if((f = vacfilecreate(fp, name, 0666)) == nil){
    631 		warn("vacfilecreate %s: %r", name);
    632 		return;
    633 	}
    634 
    635 	off = 0;
    636 	while((n = read(0, buf, sizeof buf)) > 0){
    637 		if(vacfilewrite(f, buf, n, off) < 0){
    638 			warn("venti write %s: %r", name);
    639 			vacfiledecref(f);
    640 			return;
    641 		}
    642 		off += n;
    643 	}
    644 	vacfileflush(f, 1);
    645 	vacfiledecref(f);
    646 }
    647 
    648 /*
    649  * fp is the directory we're writing.
    650  * mp is the directory whose contents we're merging in.
    651  * d is the directory entry of the file from mp that we want to add to fp.
    652  * vacfile is the name of the .vac file, for error messages.
    653  * offset is the qid that qid==0 in mp should correspond to.
    654  * max is the maximum qid we expect to see (not really needed).
    655  */
    656 int
    657 vacmergefile(VacFile *fp, VacFile *mp, VacDir *d, char *vacfile,
    658 	vlong offset, vlong max)
    659 {
    660 	VtEntry ed, em;
    661 	VacFile *mf;
    662 	VacFile *f;
    663 
    664 	mf = vacfilewalk(mp, d->elem);
    665 	if(mf == nil){
    666 		warn("could not walk %s in %s", d->elem, vacfile);
    667 		return -1;
    668 	}
    669 	if(vacfilegetentries(mf, &ed, &em) < 0){
    670 		warn("could not get entries for %s in %s", d->elem, vacfile);
    671 		vacfiledecref(mf);
    672 		return -1;
    673 	}
    674 
    675 	if((f = vacfilecreate(fp, d->elem, d->mode)) == nil){
    676 		warn("vacfilecreate %s: %r", d->elem);
    677 		vacfiledecref(mf);
    678 		return -1;
    679 	}
    680 	if(d->qidspace){
    681 		d->qidoffset += offset;
    682 		d->qidmax += offset;
    683 	}else{
    684 		d->qidspace = 1;
    685 		d->qidoffset = offset;
    686 		d->qidmax = max;
    687 	}
    688 	if(vacfilesetdir(f, d) < 0
    689 	|| vacfilesetentries(f, &ed, &em) < 0
    690 	|| vacfilesetqidspace(f, d->qidoffset, d->qidmax) < 0){
    691 		warn("vacmergefile %s: %r", d->elem);
    692 		vacfiledecref(mf);
    693 		vacfiledecref(f);
    694 		return -1;
    695 	}
    696 
    697 	vacfiledecref(mf);
    698 	vacfiledecref(f);
    699 	return 0;
    700 }
    701 
    702 int
    703 vacmerge(VacFile *fp, char *name)
    704 {
    705 	VacFs *mfs;
    706 	VacDir vd;
    707 	VacDirEnum *de;
    708 	VacFile *mp;
    709 	uvlong maxqid, offset;
    710 
    711 	if(strlen(name) < 4 || strcmp(name+strlen(name)-4, ".vac") != 0)
    712 		return -1;
    713 	if((mfs = vacfsopen(z, name, VtOREAD, CacheSize)) == nil)
    714 		return -1;
    715 	if(verbose)
    716 		fprint(2, "merging %s\n", name);
    717 
    718 	mp = vacfsgetroot(mfs);
    719 	de = vdeopen(mp);
    720 	if(de){
    721 		offset = 0;
    722 		if(vacfsgetmaxqid(mfs, &maxqid) >= 0){
    723 			_vacfsnextqid(fs, &offset);
    724 			vacfsjumpqid(fs, maxqid+1);
    725 		}
    726 		while(vderead(de, &vd) > 0){
    727 			if(vd.qid > maxqid){
    728 				warn("vacmerge %s: maxqid=%lld but %s has %lld",
    729 					name, maxqid, vd.elem, vd.qid);
    730 				vacfsjumpqid(fs, vd.qid - maxqid);
    731 				maxqid = vd.qid;
    732 			}
    733 			vacmergefile(fp, mp, &vd, name,
    734 				offset, maxqid);
    735 			vdcleanup(&vd);
    736 		}
    737 		vdeclose(de);
    738 	}
    739 	vacfiledecref(mp);
    740 	vacfsclose(mfs);
    741 	return 0;
    742 }
    743 
    744 #define TWID64	((u64int)~(u64int)0)
    745 
    746 static u64int
    747 unittoull(char *s)
    748 {
    749 	char *es;
    750 	u64int n;
    751 
    752 	if(s == nil)
    753 		return TWID64;
    754 	n = strtoul(s, &es, 0);
    755 	if(*es == 'k' || *es == 'K'){
    756 		n *= 1024;
    757 		es++;
    758 	}else if(*es == 'm' || *es == 'M'){
    759 		n *= 1024*1024;
    760 		es++;
    761 	}else if(*es == 'g' || *es == 'G'){
    762 		n *= 1024*1024*1024;
    763 		es++;
    764 	}
    765 	if(*es != '\0')
    766 		return TWID64;
    767 	return n;
    768 }
    769 
    770 static void
    771 warn(char *fmt, ...)
    772 {
    773 	va_list arg;
    774 
    775 	va_start(arg, fmt);
    776 	fprint(2, "vac: ");
    777 	vfprint(2, fmt, arg);
    778 	fprint(2, "\n");
    779 	va_end(arg);
    780 }