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);
49 * We're between a rock and a hard place here.
50 * The pw library (getpwnam, etc.) reads the
51 * password and group files into an on-stack buffer,
52 * so if you have some huge groups, you overflow
53 * the stack. Because of this, the thread library turns
54 * it off by default, so that dirstat returns "14571" instead of "rsc".
55 * But for vac we want names. So cautiously turn the pwlibrary
56 * back on (see threadmain) and make the main thread stack huge.
58 extern int _p9usepwlibrary;
59 int mainstacksize = 4*1024*1024;
63 threadmain(int argc, char **argv)
65 int i, j, fd, n, printstats;
78 /* see comment above */
82 fmtinstall('F', vtfcallfmt);
83 fmtinstall('H', encodefmt);
84 fmtinstall('V', vtscorefmt);
86 blocksize = BlockSize;
97 archivefile = EARGF(usage());
100 u = unittoull(EARGF(usage()));
103 if(u > VtMaxLumpSize)
108 diffvac = EARGF(usage());
111 excludepattern(EARGF(usage()));
114 vacfile = EARGF(usage());
117 host = EARGF(usage());
120 stdinname = EARGF(usage());
135 loadexcludefile(EARGF(usage()));
141 if(argc == 0 && !stdinname)
144 if(archivefile && (vacfile || diffvac)){
145 fprint(2, "cannot use -a with -f, -d\n");
151 sysfatal("could not connect to server: %r");
153 sysfatal("vtconnect: %r");
156 // fs is the output vac file system
157 // f is directory in output vac to write new files
158 // fdiff is corresponding directory in existing vac
167 if((outfd = open(archivefile, ORDWR)) < 0){
168 if(access(archivefile, 0) >= 0)
169 sysfatal("open %s: %r", archivefile);
170 if((outfd = create(archivefile, OWRITE, 0666)) < 0)
171 sysfatal("create %s: %r", archivefile);
172 atexit(removevacfile); // because it is new
173 if((fs = vacfscreate(z, blocksize, 512)) == nil)
174 sysfatal("vacfscreate: %r");
176 if((fs = vacfsopen(z, archivefile, VtORDWR, 512)) == nil)
177 sysfatal("vacfsopen %s: %r", archivefile);
178 if((fdiff = recentarchive(fs, oldpath)) != nil){
180 fprint(2, "diff %s\n", oldpath);
183 fprint(2, "no recent archive to diff against\n");
187 tm = *localtime(time(0));
188 snprint(yyyy, sizeof yyyy, "%04d", tm.year+1900);
189 fp = vacfsgetroot(fs);
190 if((f = vacfilewalk(fp, yyyy)) == nil
191 && (f = vacfilecreate(fp, yyyy, ModeDir|0555)) == nil)
192 sysfatal("vacfscreate %s: %r", yyyy);
196 snprint(mmdd, sizeof mmdd, "%02d%02d", tm.mon+1, tm.mday);
198 while((f = vacfilewalk(fp, mmdd)) != nil){
201 snprint(mmdd+4, sizeof mmdd-4, ".%d", n);
203 f = vacfilecreate(fp, mmdd, ModeDir|0555);
205 sysfatal("vacfscreate %s/%s: %r", yyyy, mmdd);
209 fprint(2, "archive %s/%s\n", yyyy, mmdd);
213 else if((outfd = create(vacfile, OWRITE, 0666)) < 0)
214 sysfatal("create %s: %r", vacfile);
215 atexit(removevacfile);
216 if((fs = vacfscreate(z, blocksize, 512)) == nil)
217 sysfatal("vacfscreate: %r");
218 f = vacfsgetroot(fs);
222 if((fsdiff = vacfsopen(z, diffvac, VtOREAD, 128)) == nil)
223 warn("vacfsopen %s: %r", diffvac);
225 fdiff = vacfsgetroot(fsdiff);
230 vacstdin(f, stdinname);
231 for(i=0; i<argc; i++){
232 // We can't use / and . and .. and ../.. as valid archive
233 // names, so expand to the list of files in the directory.
235 warn("empty string given as command-line argument");
239 if(strcmp(argv[i], "/") == 0
240 || strcmp(argv[i], ".") == 0
241 || strcmp(argv[i], "..") == 0
242 || (strlen(argv[i]) > 3 && strcmp(argv[i]+strlen(argv[i])-3, "/..") == 0)){
243 if((fd = open(argv[i], OREAD)) < 0){
244 warn("open %s: %r", argv[i]);
247 while((n = dirread(fd, &d)) > 0){
249 s = vtmalloc(strlen(argv[i])+1+strlen(d[j].name)+1);
252 strcat(s, d[j].name);
254 vac(f, fdiff, s, &d[j]);
261 if((d = dirstat(argv[i])) == nil){
262 warn("stat %s: %r", argv[i]);
265 vac(f, fdiff, argv[i], d);
269 vacfiledecref(fdiff);
272 * Record the maximum qid so that vacs can be merged
273 * without introducing overlapping qids. Older versions
274 * of vac arranged that the root would have the largest
275 * qid in the file system, but we can't do that anymore
276 * (the root gets created first!).
278 if(_vacfsnextqid(fs, &qid) >= 0)
279 vacfilesetqidspace(f, 0, qid);
283 * Copy fsdiff's root block score into fs's slot for that,
284 * so that vacfssync will copy it into root.prev for us.
285 * Just nice documentation, no effect.
288 memmove(fs->score, fsdiff->score, VtScoreSize);
289 if(vacfssync(fs) < 0)
290 fprint(2, "vacfssync: %r\n");
292 fprint(outfd, "vac:%V\n", fs->score);
293 atexitdont(removevacfile);
299 "%d files, %d files skipped, %d directories\n"
300 "%lld data bytes written, %lld data bytes skipped\n",
301 stats.nfile, stats.skipfiles, stats.ndir, stats.data, stats.skipdata);
309 recentarchive(VacFs *fs, char *path)
315 int year, mmdd, nn, n, n1;
318 fp = vacfsgetroot(fs);
322 for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
323 if(strlen(vd.elem) != 4)
325 if((n = strtol(vd.elem, &p, 10)) < 1900 || *p != 0)
336 snprint(buf, sizeof buf, "%04d", year);
337 if((f = vacfilewalk(fp, buf)) == nil){
338 fprint(2, "warning: dirread %s but cannot walk", buf);
348 for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
349 if(strlen(vd.elem) < 4)
351 if((n = strtol(vd.elem, &p, 10)) < 100 || n > 1231 || p != vd.elem+4)
354 if(p[1] == '0' || (n1 = strtol(p+1, &p, 10)) == 0 || *p != 0)
361 if(n < mmdd || (n == mmdd && n1 < nn))
373 snprint(buf, sizeof buf, "%04d", mmdd);
375 snprint(buf, sizeof buf, "%04d.%d", mmdd, nn);
376 if((f = vacfilewalk(fp, buf)) == nil){
377 fprint(2, "warning: dirread %s but cannot walk", buf);
383 sprint(path, "%04d/%s", year, buf);
395 plan9tovacdir(VacDir *vd, Dir *dir)
397 memset(vd, 0, sizeof *vd);
399 vd->elem = dir->name;
405 vd->mtime = dir->mtime;
407 vd->ctime = dir->mtime; /* ctime: not available on plan 9 */
408 vd->atime = dir->atime;
409 vd->size = dir->length;
411 vd->mode = dir->mode & 0777;
412 if(dir->mode & DMDIR)
414 if(dir->mode & DMAPPEND)
415 vd->mode |= ModeAppend;
416 if(dir->mode & DMEXCL)
417 vd->mode |= ModeExclusive;
419 if(dir->mode & DMDEVICE)
420 vd->mode |= ModeDevice;
421 if(dir->mode & DMNAMEDPIPE)
422 vd->mode |= ModeNamedPipe;
423 if(dir->mode & DMSYMLINK)
424 vd->mode |= ModeLink;
428 vd->p9path = dir->qid.path;
429 vd->p9version = dir->qid.vers;
443 * Does block b of f have the same SHA1 hash as the n bytes at buf?
446 sha1matches(VacFile *f, ulong b, uchar *buf, int n)
448 uchar fscore[VtScoreSize];
449 uchar bufscore[VtScoreSize];
451 if(vacfileblockscore(f, b, fscore) < 0)
453 n = vtzerotruncate(VtDataType, buf, n);
454 sha1(buf, n, bufscore, nil);
455 if(memcmp(bufscore, fscore, VtScoreSize) == 0)
461 * Archive the file named name, which has stat info d,
462 * into the vac directory fp (p = parent).
464 * If we're doing a vac -d against another archive, the
465 * equivalent directory to fp in that archive is diffp.
468 vac(VacFile *fp, VacFile *diffp, char *name, Dir *d)
471 static char buf[65536];
479 if(!includefile(name)){
480 warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : "");
489 if(merge && vacmerge(fp, name) >= 0)
493 fprint(2, "%s%s\n", name, (d->mode&DMDIR) ? "/" : "");
500 if((fd = open(name, OREAD)) < 0){
501 warn("open %s: %r", name);
505 elem = strrchr(name, '/');
511 plan9tovacdir(&vd, d);
512 if((f = vacfilecreate(fp, elem, vd.mode)) == nil){
513 warn("vacfilecreate %s: %r", name);
517 fdiff = vacfilewalk(diffp, elem);
521 if(vacfilesetdir(f, &vd) < 0)
522 warn("vacfilesetdir %s: %r", name);
525 if(d->mode&(DMSOCKET|DMNAMEDPIPE)){
526 /* don't write anything */
528 else if(d->mode&DMSYMLINK){
529 n = readlink(name, buf, sizeof buf);
530 if(n > 0 && vacfilewrite(f, buf, n, 0) < 0){
531 warn("venti write %s: %r", name);
535 }else if(d->mode&DMDEVICE){
536 snprint(buf, sizeof buf, "%c %d %d",
537 (char)((d->qid.path >> 16) & 0xFF),
538 (int)(d->qid.path & 0xFF),
539 (int)((d->qid.path >> 8) & 0xFF));
540 if(vacfilewrite(f, buf, strlen(buf), 0) < 0){
541 warn("venti write %s: %r", name);
544 stats.data += strlen(buf);
548 while((n = dirread(fd, &dk)) > 0){
550 s = vtmalloc(strlen(name)+1+strlen(dk[i].name)+1);
553 strcat(s, dk[i].name);
554 vac(f, fdiff, s, &dk[i]);
564 * Copy fdiff's contents into f by moving the score.
565 * We'll diff and update below.
567 if(vacfilegetentries(fdiff, &e, nil) >= 0)
568 if(vacfilesetentries(f, &e, nil) >= 0){
572 * Or if -q is set, and the metadata looks the same,
573 * don't even bother reading the file.
575 if(qdiff && vacfilegetdir(fdiff, &vddiff) >= 0){
576 if(vddiff.mtime == vd.mtime)
577 if(vddiff.size == vd.size)
578 if(!vddiff.plan9 || (/* vddiff.p9path == vd.p9path && */ vddiff.p9version == vd.p9version)){
586 * Skip over presumably-unchanged prefix
587 * of an append-only file.
589 if(vd.mode&ModeAppend)
590 if(vddiff.size < vd.size)
591 if(vddiff.plan9 && vd.plan9)
592 if(vddiff.p9path == vd.p9path){
593 off = vd.size/bsize*bsize;
594 if(seek(fd, off, 0) >= 0)
595 stats.skipdata += off;
597 seek(fd, 0, 0); // paranoia
603 // XXX different verbose chatty prints for kaminsky?
608 fprint(2, "+%s\n", name);
609 while((n = readn(fd, buf, bsize)) > 0){
610 if(fdiff && sha1matches(f, off/bsize, (uchar*)buf, n)){
615 if(vacfilewrite(f, buf, n, off) < 0){
616 warn("venti write %s: %r", name);
623 * Since we started with fdiff's contents,
624 * set the size in case fdiff was bigger.
626 if(fdiff && vacfilesetsize(f, off) < 0)
627 warn("vtfilesetsize %s: %r", name);
634 vacfiledecref(fdiff);
639 vacstdin(VacFile *fp, char *name)
643 static char buf[8192];
646 if((f = vacfilecreate(fp, name, 0666)) == nil){
647 warn("vacfilecreate %s: %r", name);
652 while((n = read(0, buf, sizeof buf)) > 0){
653 if(vacfilewrite(f, buf, n, off) < 0){
654 warn("venti write %s: %r", name);
665 * fp is the directory we're writing.
666 * mp is the directory whose contents we're merging in.
667 * d is the directory entry of the file from mp that we want to add to fp.
668 * vacfile is the name of the .vac file, for error messages.
669 * offset is the qid that qid==0 in mp should correspond to.
670 * max is the maximum qid we expect to see (not really needed).
673 vacmergefile(VacFile *fp, VacFile *mp, VacDir *d, char *vacfile,
674 vlong offset, vlong max)
680 mf = vacfilewalk(mp, d->elem);
682 warn("could not walk %s in %s", d->elem, vacfile);
685 if(vacfilegetentries(mf, &ed, &em) < 0){
686 warn("could not get entries for %s in %s", d->elem, vacfile);
691 if((f = vacfilecreate(fp, d->elem, d->mode)) == nil){
692 warn("vacfilecreate %s: %r", d->elem);
697 d->qidoffset += offset;
701 d->qidoffset = offset;
704 if(vacfilesetdir(f, d) < 0
705 || vacfilesetentries(f, &ed, &em) < 0
706 || vacfilesetqidspace(f, d->qidoffset, d->qidmax) < 0){
707 warn("vacmergefile %s: %r", d->elem);
719 vacmerge(VacFile *fp, char *name)
725 uvlong maxqid, offset;
727 if(strlen(name) < 4 || strcmp(name+strlen(name)-4, ".vac") != 0)
729 if((mfs = vacfsopen(z, name, VtOREAD, 100)) == nil)
732 fprint(2, "merging %s\n", name);
734 mp = vacfsgetroot(mfs);
738 if(vacfsgetmaxqid(mfs, &maxqid) >= 0){
739 _vacfsnextqid(fs, &offset);
740 vacfsjumpqid(fs, maxqid+1);
742 while(vderead(de, &vd) > 0){
744 warn("vacmerge %s: maxqid=%lld but %s has %lld",
745 name, maxqid, vd.elem, vd.qid);
746 vacfsjumpqid(fs, vd.qid - maxqid);
749 vacmergefile(fp, mp, &vd, name,
760 #define TWID64 ((u64int)~(u64int)0)
770 n = strtoul(s, &es, 0);
771 if(*es == 'k' || *es == 'K'){
774 }else if(*es == 'm' || *es == 'M'){
777 }else if(*es == 'g' || *es == 'G'){
793 vfprint(2, fmt, arg);