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");
39 int vacmerge(VacFile*, char*);
40 void vac(VacFile*, VacFile*, char*, Dir*);
41 void vacstdin(VacFile*, char*);
42 VacFile *recentarchive(VacFs*, char*);
44 static u64int unittoull(char*);
45 static void warn(char *fmt, ...);
46 static void removevacfile(void);
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.
59 extern int _p9usepwlibrary;
60 int mainstacksize = 4*1024*1024;
64 threadmain(int argc, char **argv)
66 int i, j, fd, n, printstats;
79 /* see comment above */
83 fmtinstall('F', vtfcallfmt);
84 fmtinstall('H', encodefmt);
85 fmtinstall('V', vtscorefmt);
87 blocksize = BlockSize;
98 archivefile = EARGF(usage());
101 u = unittoull(EARGF(usage()));
107 diffvac = EARGF(usage());
110 excludepattern(EARGF(usage()));
113 vacfile = EARGF(usage());
116 host = EARGF(usage());
119 stdinname = EARGF(usage());
134 loadexcludefile(EARGF(usage()));
140 if(argc == 0 && !stdinname)
143 if(archivefile && (vacfile || diffvac)){
144 fprint(2, "cannot use -a with -f, -d\n");
150 sysfatal("could not connect to server: %r");
152 sysfatal("vtconnect: %r");
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
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");
175 if((fs = vacfsopen(z, archivefile, VtORDWR, CacheSize)) == nil)
176 sysfatal("vacfsopen %s: %r", archivefile);
177 if((fdiff = recentarchive(fs, oldpath)) != nil){
179 fprint(2, "diff %s\n", oldpath);
182 fprint(2, "no recent archive to diff against\n");
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);
195 snprint(mmdd, sizeof mmdd, "%02d%02d", tm.mon+1, tm.mday);
197 while((f = vacfilewalk(fp, mmdd)) != nil){
200 snprint(mmdd+4, sizeof mmdd-4, ".%d", n);
202 f = vacfilecreate(fp, mmdd, ModeDir|0555);
204 sysfatal("vacfscreate %s/%s: %r", yyyy, mmdd);
208 fprint(2, "archive %s/%s\n", yyyy, mmdd);
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);
221 if((fsdiff = vacfsopen(z, diffvac, VtOREAD, CacheSize)) == nil)
222 warn("vacfsopen %s: %r", diffvac);
224 fdiff = vacfsgetroot(fsdiff);
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.
234 warn("empty string given as command-line argument");
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]);
246 while((n = dirread(fd, &d)) > 0){
248 s = vtmalloc(strlen(argv[i])+1+strlen(d[j].name)+1);
251 strcat(s, d[j].name);
253 vac(f, fdiff, s, &d[j]);
260 if((d = dirstat(argv[i])) == nil){
261 warn("stat %s: %r", argv[i]);
264 vac(f, fdiff, argv[i], d);
268 vacfiledecref(fdiff);
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!).
277 if(_vacfsnextqid(fs, &qid) >= 0)
278 vacfilesetqidspace(f, 0, qid);
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.
287 memmove(fs->score, fsdiff->score, VtScoreSize);
288 if(vacfssync(fs) < 0)
289 fprint(2, "vacfssync: %r\n");
291 fprint(outfd, "vac:%V\n", fs->score);
292 atexitdont(removevacfile);
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);
308 recentarchive(VacFs *fs, char *path)
314 int year, mmdd, nn, n, n1;
317 fp = vacfsgetroot(fs);
321 for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
322 if(strlen(vd.elem) != 4)
324 if((n = strtol(vd.elem, &p, 10)) < 1900 || *p != 0)
335 snprint(buf, sizeof buf, "%04d", year);
336 if((f = vacfilewalk(fp, buf)) == nil){
337 fprint(2, "warning: dirread %s but cannot walk", buf);
347 for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
348 if(strlen(vd.elem) < 4)
350 if((n = strtol(vd.elem, &p, 10)) < 100 || n > 1231 || p != vd.elem+4)
353 if(p[1] == '0' || (n1 = strtol(p+1, &p, 10)) == 0 || *p != 0)
360 if(n < mmdd || (n == mmdd && n1 < nn))
372 snprint(buf, sizeof buf, "%04d", mmdd);
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);
382 sprint(path, "%04d/%s", year, buf);
394 plan9tovacdir(VacDir *vd, Dir *dir)
396 memset(vd, 0, sizeof *vd);
398 vd->elem = dir->name;
404 vd->mtime = dir->mtime;
406 vd->ctime = dir->mtime; /* ctime: not available on plan 9 */
407 vd->atime = dir->atime;
408 vd->size = dir->length;
410 vd->mode = dir->mode & 0777;
411 if(dir->mode & DMDIR)
413 if(dir->mode & DMAPPEND)
414 vd->mode |= ModeAppend;
415 if(dir->mode & DMEXCL)
416 vd->mode |= ModeExclusive;
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;
427 vd->p9path = dir->qid.path;
428 vd->p9version = dir->qid.vers;
442 * Archive the file named name, which has stat info d,
443 * into the vac directory fp (p = parent).
445 * If we're doing a vac -d against another archive, the
446 * equivalent directory to fp in that archive is diffp.
449 vac(VacFile *fp, VacFile *diffp, char *name, Dir *d)
460 if(!includefile(name)){
461 warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : "");
470 if(merge && vacmerge(fp, name) >= 0)
474 fprint(2, "%s%s\n", name, (d->mode&DMDIR) ? "/" : "");
481 if((fd = open(name, OREAD)) < 0){
482 warn("open %s: %r", name);
486 elem = strrchr(name, '/');
492 plan9tovacdir(&vd, d);
493 if((f = vacfilecreate(fp, elem, vd.mode)) == nil){
494 warn("vacfilecreate %s: %r", name);
498 fdiff = vacfilewalk(diffp, elem);
502 if(vacfilesetdir(f, &vd) < 0)
503 warn("vacfilesetdir %s: %r", name);
506 if(d->mode&(DMSOCKET|DMNAMEDPIPE)){
507 /* don't write anything */
509 else if(d->mode&DMSYMLINK){
510 n = readlink(name, buf, sizeof buf);
511 if(n > 0 && vacfilewrite(f, buf, n, 0) < 0){
512 warn("venti write %s: %r", name);
516 }else if(d->mode&DMDEVICE){
517 snprint(buf, sizeof buf, "%c %d %d",
518 (char)((d->qid.path >> 16) & 0xFF),
519 (int)(d->qid.path & 0xFF),
520 (int)((d->qid.path >> 8) & 0xFF));
521 if(vacfilewrite(f, buf, strlen(buf), 0) < 0){
522 warn("venti write %s: %r", name);
525 stats.data += strlen(buf);
529 while((n = dirread(fd, &dk)) > 0){
531 s = vtmalloc(strlen(name)+1+strlen(dk[i].name)+1);
534 strcat(s, dk[i].name);
535 vac(f, fdiff, s, &dk[i]);
544 buf = vtmallocz(bsize);
547 * Copy fdiff's contents into f by moving the score.
548 * We'll diff and update below.
550 if(vacfilegetentries(fdiff, &e, nil) >= 0)
551 if(vacfilesetentries(f, &e, nil) >= 0){
555 * Or if -q is set, and the metadata looks the same,
556 * don't even bother reading the file.
558 if(qdiff && vacfilegetdir(fdiff, &vddiff) >= 0){
559 if(vddiff.mtime == vd.mtime)
560 if(vddiff.size == vd.size)
561 if(!vddiff.plan9 || (/* vddiff.p9path == vd.p9path && */ vddiff.p9version == vd.p9version)){
569 * Skip over presumably-unchanged prefix
570 * of an append-only file.
572 if(vd.mode&ModeAppend)
573 if(vddiff.size < vd.size)
574 if(vddiff.plan9 && vd.plan9)
575 if(vddiff.p9path == vd.p9path){
576 off = vd.size/bsize*bsize;
577 if(seek(fd, off, 0) >= 0)
578 stats.skipdata += off;
580 seek(fd, 0, 0); // paranoia
586 // XXX different verbose chatty prints for kaminsky?
591 fprint(2, "+%s\n", name);
592 while((n = readn(fd, buf, bsize)) > 0){
593 if(fdiff && sha1matches(f, off/bsize, (uchar*)buf, n)){
598 if(vacfilewrite(f, buf, n, off) < 0){
599 warn("venti write %s: %r", name);
606 * Since we started with fdiff's contents,
607 * set the size in case fdiff was bigger.
609 if(fdiff && vacfilesetsize(f, off) < 0)
610 warn("vtfilesetsize %s: %r", name);
617 vacfiledecref(fdiff);
622 vacstdin(VacFile *fp, char *name)
626 static char buf[8192];
629 if((f = vacfilecreate(fp, name, 0666)) == nil){
630 warn("vacfilecreate %s: %r", name);
635 while((n = read(0, buf, sizeof buf)) > 0){
636 if(vacfilewrite(f, buf, n, off) < 0){
637 warn("venti write %s: %r", name);
648 * fp is the directory we're writing.
649 * mp is the directory whose contents we're merging in.
650 * d is the directory entry of the file from mp that we want to add to fp.
651 * vacfile is the name of the .vac file, for error messages.
652 * offset is the qid that qid==0 in mp should correspond to.
653 * max is the maximum qid we expect to see (not really needed).
656 vacmergefile(VacFile *fp, VacFile *mp, VacDir *d, char *vacfile,
657 vlong offset, vlong max)
663 mf = vacfilewalk(mp, d->elem);
665 warn("could not walk %s in %s", d->elem, vacfile);
668 if(vacfilegetentries(mf, &ed, &em) < 0){
669 warn("could not get entries for %s in %s", d->elem, vacfile);
674 if((f = vacfilecreate(fp, d->elem, d->mode)) == nil){
675 warn("vacfilecreate %s: %r", d->elem);
680 d->qidoffset += offset;
684 d->qidoffset = offset;
687 if(vacfilesetdir(f, d) < 0
688 || vacfilesetentries(f, &ed, &em) < 0
689 || vacfilesetqidspace(f, d->qidoffset, d->qidmax) < 0){
690 warn("vacmergefile %s: %r", d->elem);
702 vacmerge(VacFile *fp, char *name)
708 uvlong maxqid, offset;
710 if(strlen(name) < 4 || strcmp(name+strlen(name)-4, ".vac") != 0)
712 if((mfs = vacfsopen(z, name, VtOREAD, CacheSize)) == nil)
715 fprint(2, "merging %s\n", name);
717 mp = vacfsgetroot(mfs);
721 if(vacfsgetmaxqid(mfs, &maxqid) >= 0){
722 _vacfsnextqid(fs, &offset);
723 vacfsjumpqid(fs, maxqid+1);
725 while(vderead(de, &vd) > 0){
727 warn("vacmerge %s: maxqid=%lld but %s has %lld",
728 name, maxqid, vd.elem, vd.qid);
729 vacfsjumpqid(fs, vd.qid - maxqid);
732 vacmergefile(fp, mp, &vd, name,
743 #define TWID64 ((u64int)~(u64int)0)
753 n = strtoul(s, &es, 0);
754 if(*es == 'k' || *es == 'K'){
757 }else if(*es == 'm' || *es == 'M'){
760 }else if(*es == 'g' || *es == 'G'){
776 vfprint(2, fmt, arg);