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");
38 int vacmerge(VacFile*, char*);
39 void vac(VacFile*, VacFile*, char*, Dir*);
40 void vacstdin(VacFile*, char*);
41 VacFile *recentarchive(VacFs*, char*);
43 static u64int unittoull(char*);
44 static void warn(char *fmt, ...);
45 static void removevacfile(void);
48 threadmain(int argc, char **argv)
50 int i, j, fd, n, printstats;
63 fmtinstall('F', vtfcallfmt);
64 fmtinstall('H', encodefmt);
65 fmtinstall('V', vtscorefmt);
67 blocksize = BlockSize;
78 archivefile = EARGF(usage());
81 u = unittoull(EARGF(usage()));
89 diffvac = EARGF(usage());
92 excludepattern(EARGF(usage()));
95 vacfile = EARGF(usage());
98 host = EARGF(usage());
101 stdinname = EARGF(usage());
116 loadexcludefile(EARGF(usage()));
122 if(argc == 0 && !stdinname)
125 if(archivefile && (vacfile || diffvac)){
126 fprint(2, "cannot use -a with -f, -d\n");
132 sysfatal("could not connect to server: %r");
134 sysfatal("vtconnect: %r");
137 // fs is the output vac file system
138 // f is directory in output vac to write new files
139 // fdiff is corresponding directory in existing vac
148 if((outfd = open(archivefile, ORDWR)) < 0){
149 if(access(archivefile, 0) >= 0)
150 sysfatal("open %s: %r", archivefile);
151 if((outfd = create(archivefile, OWRITE, 0666)) < 0)
152 sysfatal("create %s: %r", archivefile);
153 atexit(removevacfile); // because it is new
154 if((fs = vacfscreate(z, blocksize, 512)) == nil)
155 sysfatal("vacfscreate: %r");
157 if((fs = vacfsopen(z, archivefile, VtORDWR, 512)) == nil)
158 sysfatal("vacfsopen %s: %r", archivefile);
159 if((fdiff = recentarchive(fs, oldpath)) != nil){
161 fprint(2, "diff %s\n", oldpath);
164 fprint(2, "no recent archive to diff against\n");
168 tm = *localtime(time(0));
169 snprint(yyyy, sizeof yyyy, "%04d", tm.year+1900);
170 fp = vacfsgetroot(fs);
171 if((f = vacfilewalk(fp, yyyy)) == nil
172 && (f = vacfilecreate(fp, yyyy, ModeDir|0555)) == nil)
173 sysfatal("vacfscreate %s: %r", yyyy);
177 snprint(mmdd, sizeof mmdd, "%02d%02d", tm.mon+1, tm.mday);
179 while((f = vacfilewalk(fp, mmdd)) != nil){
182 snprint(mmdd+4, sizeof mmdd-4, ".%d", n);
184 f = vacfilecreate(fp, mmdd, ModeDir|0555);
186 sysfatal("vacfscreate %s/%s: %r", yyyy, mmdd);
190 fprint(2, "archive %s/%s\n", yyyy, mmdd);
194 else if((outfd = create(vacfile, OWRITE, 0666)) < 0)
195 sysfatal("create %s: %r", vacfile);
196 atexit(removevacfile);
197 if((fs = vacfscreate(z, blocksize, 512)) == nil)
198 sysfatal("vacfscreate: %r");
199 f = vacfsgetroot(fs);
203 if((fsdiff = vacfsopen(z, diffvac, VtOREAD, 128)) == nil)
204 warn("vacfsopen %s: %r", diffvac);
206 fdiff = vacfsgetroot(fsdiff);
211 vacstdin(f, stdinname);
212 for(i=0; i<argc; i++){
213 // We can't use / and . and .. and ../.. as valid archive
214 // names, so expand to the list of files in the directory.
216 warn("empty string given as command-line argument");
220 if(strcmp(argv[i], "/") == 0
221 || strcmp(argv[i], ".") == 0
222 || strcmp(argv[i], "..") == 0
223 || (strlen(argv[i]) > 3 && strcmp(argv[i]+strlen(argv[i])-3, "/..") == 0)){
224 if((fd = open(argv[i], OREAD)) < 0){
225 warn("open %s: %r", argv[i]);
228 while((n = dirread(fd, &d)) > 0){
230 s = vtmalloc(strlen(argv[i])+1+strlen(d[j].name)+1);
233 strcat(s, d[j].name);
235 vac(f, fdiff, s, &d[j]);
242 if((d = dirstat(argv[i])) == nil){
243 warn("stat %s: %r", argv[i]);
246 vac(f, fdiff, argv[i], d);
250 vacfiledecref(fdiff);
253 * Record the maximum qid so that vacs can be merged
254 * without introducing overlapping qids. Older versions
255 * of vac arranged that the root would have the largest
256 * qid in the file system, but we can't do that anymore
257 * (the root gets created first!).
259 if(_vacfsnextqid(fs, &qid) >= 0)
260 vacfilesetqidspace(f, 0, qid);
264 * Copy fsdiff's root block score into fs's slot for that,
265 * so that vacfssync will copy it into root.prev for us.
266 * Just nice documentation, no effect.
269 memmove(fs->score, fsdiff->score, VtScoreSize);
270 if(vacfssync(fs) < 0)
271 fprint(2, "vacfssync: %r\n");
273 fprint(outfd, "vac:%V\n", fs->score);
274 atexitdont(removevacfile);
280 "%d files, %d files skipped, %d directories\n"
281 "%lld data bytes written, %lld data bytes skipped\n",
282 stats.nfile, stats.skipfiles, stats.ndir, stats.data, stats.skipdata);
290 recentarchive(VacFs *fs, char *path)
296 int year, mmdd, nn, n, n1;
299 fp = vacfsgetroot(fs);
303 for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
304 if(strlen(vd.elem) != 4)
306 if((n = strtol(vd.elem, &p, 10)) < 1900 || *p != 0)
317 snprint(buf, sizeof buf, "%04d", year);
318 if((f = vacfilewalk(fp, buf)) == nil){
319 fprint(2, "warning: dirread %s but cannot walk", buf);
329 for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
330 if(strlen(vd.elem) < 4)
332 if((n = strtol(vd.elem, &p, 10)) < 100 || n > 1231 || p != vd.elem+4)
335 if(p[1] == '0' || (n1 = strtol(p+1, &p, 10)) == 0 || *p != 0)
342 if(n < mmdd || (n == mmdd && n1 < nn))
354 snprint(buf, sizeof buf, "%04d", mmdd);
356 snprint(buf, sizeof buf, "%04d.%d", mmdd, nn);
357 if((f = vacfilewalk(fp, buf)) == nil){
358 fprint(2, "warning: dirread %s but cannot walk", buf);
364 sprint(path, "%04d/%s", year, buf);
376 plan9tovacdir(VacDir *vd, Dir *dir)
378 memset(vd, 0, sizeof *vd);
380 vd->elem = dir->name;
386 vd->mtime = dir->mtime;
388 vd->ctime = dir->mtime; /* ctime: not available on plan 9 */
389 vd->atime = dir->atime;
390 vd->size = dir->length;
392 vd->mode = dir->mode & 0777;
393 if(dir->mode & DMDIR)
395 if(dir->mode & DMAPPEND)
396 vd->mode |= ModeAppend;
397 if(dir->mode & DMEXCL)
398 vd->mode |= ModeExclusive;
401 vd->p9path = dir->qid.path;
402 vd->p9version = dir->qid.vers;
407 * Archive the file named name, which has stat info d,
408 * into the vac directory fp (p = parent).
410 * If we're doing a vac -d against another archive, the
411 * equivalent directory to fp in that archive is diffp.
414 vac(VacFile *fp, VacFile *diffp, char *name, Dir *d)
417 static char buf[65536];
425 if(!includefile(name)){
426 warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : "");
435 if(merge && vacmerge(fp, name) >= 0)
439 fprint(2, "%s%s\n", name, (d->mode&DMDIR) ? "/" : "");
441 if((fd = open(name, OREAD)) < 0){
442 warn("open %s: %r", name);
446 elem = strrchr(name, '/');
452 plan9tovacdir(&vd, d);
453 if((f = vacfilecreate(fp, elem, vd.mode)) == nil){
454 warn("vacfilecreate %s: %r", name);
458 fdiff = vacfilewalk(diffp, elem);
462 if(vacfilesetdir(f, &vd) < 0)
463 warn("vacfilesetdir %s: %r", name);
466 while((n = dirread(fd, &dk)) > 0){
468 s = vtmalloc(strlen(name)+1+strlen(dk[i].name)+1);
471 strcat(s, dk[i].name);
472 vac(f, fdiff, s, &dk[i]);
482 * Copy fdiff's contents into f by moving the score.
483 * We'll diff and update below.
485 if(vacfilegetentries(fdiff, &e, nil) >= 0)
486 if(vacfilesetentries(f, &e, nil) >= 0){
490 * Or if -q is set, and the metadata looks the same,
491 * don't even bother reading the file.
493 if(qdiff && vacfilegetdir(fdiff, &vddiff) >= 0){
494 if(vddiff.mtime == vd.mtime)
495 if(vddiff.size == vd.size)
496 if(!vddiff.plan9 || (/* vddiff.p9path == vd.p9path && */ vddiff.p9version == vd.p9version)){
504 * Skip over presumably-unchanged prefix
505 * of an append-only file.
507 if(vd.mode&ModeAppend)
508 if(vddiff.size < vd.size)
509 if(vddiff.plan9 && vd.plan9)
510 if(vddiff.p9path == vd.p9path){
511 off = vd.size/bsize*bsize;
512 if(seek(fd, off, 0) >= 0)
513 stats.skipdata += off;
515 seek(fd, 0, 0); // paranoia
521 // XXX different verbose chatty prints for kaminsky?
526 fprint(2, "+%s\n", name);
527 while((n = readn(fd, buf, bsize)) > 0){
528 if(fdiff && sha1matches(f, off/bsize, (uchar*)buf, n)){
533 if(vacfilewrite(f, buf, n, off) < 0){
534 warn("venti write %s: %r", name);
541 * Since we started with fdiff's contents,
542 * set the size in case fdiff was bigger.
544 if(fdiff && vacfilesetsize(f, off) < 0)
545 warn("vtfilesetsize %s: %r", name);
552 vacfiledecref(fdiff);
557 vacstdin(VacFile *fp, char *name)
561 static char buf[8192];
564 if((f = vacfilecreate(fp, name, 0666)) == nil){
565 warn("vacfilecreate %s: %r", name);
570 while((n = read(0, buf, sizeof buf)) > 0){
571 if(vacfilewrite(f, buf, n, off) < 0){
572 warn("venti write %s: %r", name);
583 * fp is the directory we're writing.
584 * mp is the directory whose contents we're merging in.
585 * d is the directory entry of the file from mp that we want to add to fp.
586 * vacfile is the name of the .vac file, for error messages.
587 * offset is the qid that qid==0 in mp should correspond to.
588 * max is the maximum qid we expect to see (not really needed).
591 vacmergefile(VacFile *fp, VacFile *mp, VacDir *d, char *vacfile,
592 vlong offset, vlong max)
598 mf = vacfilewalk(mp, d->elem);
600 warn("could not walk %s in %s", d->elem, vacfile);
603 if(vacfilegetentries(mf, &ed, &em) < 0){
604 warn("could not get entries for %s in %s", d->elem, vacfile);
609 if((f = vacfilecreate(fp, d->elem, d->mode)) == nil){
610 warn("vacfilecreate %s: %r", d->elem);
615 d->qidoffset += offset;
619 d->qidoffset = offset;
622 if(vacfilesetdir(f, d) < 0
623 || vacfilesetentries(f, &ed, &em) < 0
624 || vacfilesetqidspace(f, d->qidoffset, d->qidmax) < 0){
625 warn("vacmergefile %s: %r", d->elem);
637 vacmerge(VacFile *fp, char *name)
643 uvlong maxqid, offset;
645 if(strlen(name) < 4 || strcmp(name+strlen(name)-4, ".vac") != 0)
647 if((mfs = vacfsopen(z, name, VtOREAD, 100)) == nil)
650 fprint(2, "merging %s\n", name);
652 mp = vacfsgetroot(mfs);
656 if(vacfsgetmaxqid(mfs, &maxqid) >= 0){
657 _vacfsnextqid(fs, &offset);
658 vacfsjumpqid(fs, maxqid+1);
660 while(vderead(de, &vd) > 0){
662 warn("vacmerge %s: maxqid=%lld but %s has %lld",
663 name, maxqid, vd.elem, vd.qid);
664 vacfsjumpqid(fs, vd.qid - maxqid);
667 vacmergefile(fp, mp, &vd, name,
678 #define TWID64 ((u64int)~(u64int)0)
688 n = strtoul(s, &es, 0);
689 if(*es == 'k' || *es == 'K'){
692 }else if(*es == 'm' || *es == 'M'){
695 }else if(*es == 'g' || *es == 'G'){
711 vfprint(2, fmt, arg);