commit - d9841dc7adc0ad99e56cf508d5d6b6d2e59afbb5
commit + e05b0ff3ebd8086809714527a27b412345ff4d72
blob - 9243febb6b225ed0deaf86cbc0aced5311a2211b
blob + 7de41ee622c593018db745c48c99576f907efa0e
--- man/man1/vac.1
+++ man/man1/vac.1
[
.B -mqsv
] [
+.B -a
+.I vacfile
+] [
.B -b
.I blocksize
] [
] [
.B -h
.I host
+] [
+.B -x
+.I excludefile
]
.I file ...
.PP
.PP
The options are:
.TP
+.BI -a " vacfile
+Specifies that vac should create or update a backup archive, inserting
+the files under an extra two levels of directory hierarchy named
+.I yyyy/mmdd
+(year, month, day)
+in the style of the dump file system
+(see Plan 9's \fIfs\fR(4)).
+If
+.I vacfile
+already exists, an additional backup day is added to the
+existing hierarchy, behaving as though the
+.B -d
+flag was specified giving the most recent backup tree in the archive.
+Typically, this option
+is used as part of a nightly backup script.
+This option cannot be used with
+.B -d
+or
+.BR -f .
+.TP
.BI -b " blocksize
Specifies the block size that data will be broken into.
The units for the size can be specified by appending
Do not include the file or directory specified by
.IR exclude .
This option may be repeated multiple times.
+.I Exclude
+can be a shell pattern as accepted by
+.IR rc (1),
+with one extension:
+.B \&...
+matches any sequence of characters including slashes.
.TP
.BI -f " vacfile
The results of
.TP
.B -q
Increase the performance of the
+.B -a
+or
.B -d
-option by detecting unchanged files based on a match of the files name and other meta data,
+options by detecting unchanged files based on a match of the files name and other meta data,
rather than examining the contents of the files.
.TP
.B -s
.B -v
Produce more verbose output on standard error, including the name of the files added to the archive
and the vac archives that are expanded and merged.
+.TP
+.BI -x " excfile
+Read exclude patterns from the file
+.IR excfile .
+Blank lines and lines beginning with
+.B #
+are ignored.
+All other lines should be of the form
+.B include
+.I pattern
+or
+.B exclude
+.I pattern .
+When considering whether to include a directory or file
+in the vac archive,
+the earliest matching pattern in the file
+applies.
+The patterns are the same syntax accepted by the
+.B -e
+option.
+This option may be repeated multiple times.
.PP
.I Unvac
lists or extracts files stored in the vac archive
blob - f5f823d7c1d6c3d01452a0f20ed097b26553277b
blob + d7ad2056f95b9b784c16d54e57c377df435ff261
--- src/cmd/vac/file.c
+++ src/cmd/vac/file.c
vtblockput(b);
if((b = vtfileblock(ms, bo, VtORDWR)) == nil)
goto Err;
+ mbunpack(&mb, b->data, ms->dsize);
goto Found;
}
vtblockput(b);
me.p = p;
me.size = n;
vdpack(dir, &me, VacDirVersion);
-vdunpack(dir, &me);
mbinsert(&mb, i, &me);
mbpack(&mb);
vtblockput(b);
/*
* Flush all data associated with f out of the cache and onto venti.
* If recursive is set, flush f's children too.
+ * Vacfiledecref knows how to flush source and msource too.
*/
int
vacfileflush(VacFile *f, int recursive)
ret = -1;
filemetaunlock(f);
- /*
- * Vacfiledecref knows how to flush source and msource too.
- */
if(filelock(f) < 0)
return -1;
- vtfilelock(f->source, -1);
- if(vtfileflush(f->source) < 0)
- ret = -1;
- vtfileunlock(f->source);
- if(f->msource){
- vtfilelock(f->msource, -1);
- if(vtfileflush(f->msource) < 0)
- ret = -1;
- vtfileunlock(f->msource);
- }
-
+
/*
* Lock order prevents us from flushing kids while holding
- * lock, so make a list.
+ * lock, so make a list and then flush without the lock.
*/
nkids = 0;
kids = nil;
p->ref++;
}
}
- fileunlock(f);
-
- for(i=0; i<nkids; i++){
- if(vacfileflush(kids[i], 1) < 0)
- ret = -1;
- vacfiledecref(kids[i]);
+ if(nkids > 0){
+ fileunlock(f);
+ for(i=0; i<nkids; i++){
+ if(vacfileflush(kids[i], 1) < 0)
+ ret = -1;
+ vacfiledecref(kids[i]);
+ }
+ filelock(f);
}
free(kids);
+
+ /*
+ * Now we can flush our own data.
+ */
+ vtfilelock(f->source, -1);
+ if(vtfileflush(f->source) < 0)
+ ret = -1;
+ vtfileunlock(f->source);
+ if(f->msource){
+ vtfilelock(f->msource, -1);
+ if(vtfileflush(f->msource) < 0)
+ ret = -1;
+ vtfileunlock(f->msource);
+ }
+ fileunlock(f);
+
return ret;
}
vacfileincref(fp);
fileunlock(fp);
+
+ filelock(ff);
+ vtfilelock(ff->source, -1);
+ vtfileunlock(ff->source);
+ fileunlock(ff);
+
return ff;
Err:
return -1;
}
vtfileclose(f);
-
+
/* Build a root block. */
memset(&root, 0, sizeof root);
strcpy(root.type, "vac");
blob - /dev/null
blob + aa21206859e1224f0fbbf4cfcc6e2ccfc807a2a6 (mode 644)
--- /dev/null
+++ src/cmd/vac/exc
+exclude a/*
+exclude b/...
+exclude c/[~a]*
+exclude d/[a]*
+exclude e/[a-z]*
+exclude f/?a*
+exclude g/*/*/b
+exclude h/.../b
blob - 3cf8784025865790bde745bd494487cf910cf5c8
blob + 0f691b1e16476ac83d31a204d485bd228c18ff40
--- src/cmd/vac/fns.h
+++ src/cmd/vac/fns.h
int _vacfsnextqid(VacFs *fs, uvlong *qid);
void vacfsjumpqid(VacFs*, uvlong step);
+
+Reprog* glob2regexp(char*);
+void loadexcludefile(char*);
+int includefile(char*);
+void excludepattern(char*);
blob - /dev/null
blob + fd2a98b34f5004d3eb6a07539220b5fc5c3199e5 (mode 644)
--- /dev/null
+++ src/cmd/vac/exc.in
+a/abc
+a/foo
+a/.foo
+b/foo
+b/.foo
+c/abc
+c/def
+c/zab
+d/abc
+d/def
+d/zab
+e/abc
+e/.abc
+e/ABC
+f/a
+f/.abc
+f/az
+f/za
+f/zabc
+f/zza
+g/a/b
+g/a/c/b
+g/a/c/d/b
+h/a/b
+h/a/c/b
+h/a/c/d/b
blob - 98e122b8f95f0d3df3adea289de4743b97821d44
blob + 6ee651cb59f6c504033450409dc66020456ff0d0
--- src/cmd/vac/mkfile
+++ src/cmd/vac/mkfile
error\
file\
pack\
+ glob\
LIB=${LIBFILES:%=%.$O} $PLAN9/lib/libventi.a
default:V: all
<$PLAN9/src/mkmany
+
+testglob:V: $O.testinc
+ $O.testinc exc <exc.in >exc.test
+ diff exc.out exc.test
+ ls -l exc.out exc.test
+
+
blob - /dev/null
blob + 704b150e6ea48b0b9763bc6b91c630ca3de6e4a3 (mode 644)
--- /dev/null
+++ src/cmd/vac/exc.out
+0 a/abc
+0 a/foo
+1 a/.foo
+0 b/foo
+0 b/.foo
+1 c/abc
+0 c/def
+0 c/zab
+0 d/abc
+1 d/def
+1 d/zab
+0 e/abc
+1 e/.abc
+1 e/ABC
+1 f/a
+1 f/.abc
+1 f/az
+0 f/za
+0 f/zabc
+1 f/zza
+1 g/a/b
+0 g/a/c/b
+1 g/a/c/d/b
+0 h/a/b
+0 h/a/c/b
+0 h/a/c/d/b
blob - bf72d3fde9902ff355846e27dbf8b7f6af6d0d75
blob + aa3b5f7c61dd93a8a788439ce09111ca713a093a
--- src/cmd/vac/stdinc.h
+++ src/cmd/vac/stdinc.h
#include <u.h>
#include <libc.h>
+#include <bio.h>
#include <thread.h>
#include <venti.h>
#include <libsec.h>
+#include <regexp.h>
blob - /dev/null
blob + 863eb848619be881ba23e577da0c7675eb9ed7d0 (mode 644)
--- /dev/null
+++ src/cmd/vac/glob.c
+#include "stdinc.h"
+#include "vac.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+// Convert globbish pattern to regular expression
+// The wildcards are
+//
+// * any non-slash characters
+// ... any characters including /
+// ? any single character except /
+// [a-z] character class
+// [~a-z] negated character class
+//
+
+Reprog*
+glob2regexp(char *glob)
+{
+ char *s, *p, *w;
+ Reprog *re;
+ int boe; // beginning of path element
+
+ s = malloc(20*(strlen(glob)+1));
+ if(s == nil)
+ return nil;
+ w = s;
+ boe = 1;
+ *w++ = '^';
+ *w++ = '(';
+ for(p=glob; *p; p++){
+ if(p[0] == '.' && p[1] == '.' && p[2] == '.'){
+ strcpy(w, ".*");
+ w += strlen(w);
+ p += 3-1;
+ boe = 0;
+ continue;
+ }
+ if(p[0] == '*'){
+ if(boe)
+ strcpy(w, "([^./][^/]*)?");
+ else
+ strcpy(w, "[^/]*");
+ w += strlen(w);
+ boe = 0;
+ continue;
+ }
+ if(p[0] == '?'){
+ if(boe)
+ strcpy(w, "[^./]");
+ else
+ strcpy(w, "[^/]");
+ w += strlen(w);
+ boe = 0;
+ continue;
+ }
+ if(p[0] == '['){
+ *w++ = '[';
+ if(*++p == '~'){
+ *w++ = '^';
+ p++;
+ }
+ while(*p != ']'){
+ if(*p == '/')
+ goto syntax;
+ if(*p == '^' || *p == '\\')
+ *w++ = '\\';
+ *w++ = *p++;
+ }
+ *w++ = ']';
+ boe = 0;
+ continue;
+ }
+ if(strchr("()|^$[]*?+\\.", *p)){
+ *w++ = '\\';
+ *w++ = *p;
+ boe = 0;
+ continue;
+ }
+ if(*p == '/'){
+ *w++ = '/';
+ boe = 1;
+ continue;
+ }
+ *w++ = *p;
+ boe = 0;
+ continue;
+ }
+ *w++ = ')';
+ *w++ = '$';
+ *w = 0;
+
+ re = regcomp(s);
+ if(re == nil){
+ syntax:
+ free(s);
+ werrstr("glob syntax error");
+ return nil;
+ }
+ free(s);
+ return re;
+}
+
+typedef struct Pattern Pattern;
+struct Pattern
+{
+ Reprog *re;
+ int include;
+};
+
+Pattern *pattern;
+int npattern;
+
+void
+loadexcludefile(char *file)
+{
+ Biobuf *b;
+ char *p, *q;
+ int n, inc;
+ Reprog *re;
+
+ if((b = Bopen(file, OREAD)) == nil)
+ sysfatal("open %s: %r", file);
+ for(n=1; (p=Brdstr(b, '\n', 1)) != nil; free(p), n++){
+ q = p+strlen(p);
+ while(q > p && isspace((uchar)*(q-1)))
+ *--q = 0;
+ switch(p[0]){
+ case '\0':
+ case '#':
+ continue;
+ }
+
+ inc = 0;
+ if(strncmp(p, "include ", 8) == 0){
+ inc = 1;
+ }else if(strncmp(p, "exclude ", 8) == 0){
+ inc = 0;
+ }else
+ sysfatal("%s:%d: line does not begin with include or exclude", file, n);
+
+ if(strchr(p+8, ' '))
+ fprint(2, "%s:%d: warning: space in pattern\n", file, n);
+
+ if((re = glob2regexp(p+8)) == nil)
+ sysfatal("%s:%d: bad glob pattern", file, n);
+
+ pattern = vtrealloc(pattern, (npattern+1)*sizeof pattern[0]);
+ pattern[npattern].re = re;
+ pattern[npattern].include = inc;
+ npattern++;
+ }
+ Bterm(b);
+}
+
+void
+excludepattern(char *p)
+{
+ Reprog *re;
+
+ if((re = glob2regexp(p)) == nil)
+ sysfatal("bad glob pattern %s", p);
+
+ pattern = vtrealloc(pattern, (npattern+1)*sizeof pattern[0]);
+ pattern[npattern].re = re;
+ pattern[npattern].include = 0;
+ npattern++;
+}
+
+int
+includefile(char *file)
+{
+ Pattern *p, *ep;
+
+ for(p=pattern, ep=p+npattern; p<ep; p++)
+ if(regexec(p->re, file, nil, 0))
+ return p->include;
+ return 1;
+}
+
blob - 183fa380fab0121b3bc1a34adfddfce1cace5794
blob + 99651981b13c321e25a4fa5fc52f597a27203882
--- src/cmd/vac/vac.c
+++ src/cmd/vac/vac.c
void
usage(void)
{
- fprint(2, "vac [-imqsv] [-b bsize] [-d old.vac] [-e exclude]... [-f new.vac] [-h host] file...\n");
+ fprint(2, "vac [-imqsv] [-a archive.vac] [-b bsize] [-d old.vac] [-f new.vac] [-e exclude]... [-h host] file...\n");
threadexitsall("usage");
}
enum
{
BlockSize = 8*1024,
- MaxExclude = 1000
};
struct
char *host;
VtConn *z;
VacFs *fs;
-char *exclude[MaxExclude];
-int nexclude;
+char *archivefile;
char *vacfile;
int vacmerge(VacFile*, char*);
void vac(VacFile*, VacFile*, char*, Dir*);
void vacstdin(VacFile*, char*);
+VacFile *recentarchive(VacFs*, char*);
static u64int unittoull(char*);
static void warn(char *fmt, ...);
-static int strpcmp(const void*, const void*);
static void removevacfile(void);
#ifdef PLAN9PORT
_p9usepwlibrary = 1;
#endif
+ fmtinstall('F', vtfcallfmt);
fmtinstall('H', encodefmt);
fmtinstall('V', vtscorefmt);
printstats = 0;
fsdiff = nil;
diffvac = nil;
+
ARGBEGIN{
+ case 'V':
+ chattyventi++;
+ break;
+ case 'a':
+ archivefile = EARGF(usage());
+ break;
case 'b':
u = unittoull(EARGF(usage()));
if(u < 512)
diffvac = EARGF(usage());
break;
case 'e':
- if(nexclude >= MaxExclude)
- sysfatal("too many exclusions\n");
- exclude[nexclude] = ARGF();
- if(exclude[nexclude] == nil)
- usage();
- nexclude++;
+ excludepattern(EARGF(usage()));
break;
case 'f':
vacfile = EARGF(usage());
case 'v':
verbose++;
break;
+ case 'x':
+ loadexcludefile(EARGF(usage()));
+ break;
default:
usage();
}ARGEND
if(argc == 0 && !stdinname)
usage();
+
+ if(archivefile && (vacfile || diffvac)){
+ fprint(2, "cannot use -a with -f, -d\n");
+ usage();
+ }
- if(vacfile == nil)
- outfd = 1;
- else if((outfd = create(vacfile, OWRITE, 0666)) < 0)
- sysfatal("create %s: %r", vacfile);
- atexit(removevacfile);
-
- qsort(exclude, nexclude, sizeof(char*), strpcmp);
-
z = vtdial(host);
if(z == nil)
sysfatal("could not connect to server: %r");
if(vtconnect(z) < 0)
sysfatal("vtconnect: %r");
-
- if(diffvac){
- if((fsdiff = vacfsopen(z, diffvac, VtOREAD, 128)) == nil)
- warn("vacfsopen %s: %r", diffvac);
- }
- if((fs = vacfscreate(z, blocksize, 512)) == nil)
- sysfatal("vacfscreate: %r");
+ // Setup:
+ // fs is the output vac file system
+ // f is directory in output vac to write new files
+ // fdiff is corresponding directory in existing vac
+ if(archivefile){
+ VacFile *fp;
+ char yyyy[5];
+ char mmdd[10];
+ char oldpath[40];
+ Tm tm;
- f = vacfsgetroot(fs);
- if(fsdiff)
- fdiff = vacfsgetroot(fsdiff);
- else
fdiff = nil;
+ if((outfd = open(archivefile, ORDWR)) < 0){
+ if(access(archivefile, 0) >= 0)
+ sysfatal("open %s: %r", archivefile);
+ if((outfd = create(archivefile, OWRITE, 0666)) < 0)
+ sysfatal("create %s: %r", archivefile);
+ atexit(removevacfile); // because it is new
+ if((fs = vacfscreate(z, blocksize, 512)) == nil)
+ sysfatal("vacfscreate: %r");
+ }else{
+ if((fs = vacfsopen(z, archivefile, VtORDWR, 512)) == nil)
+ sysfatal("vacfsopen %s: %r", archivefile);
+ if((fdiff = recentarchive(fs, oldpath)) != nil){
+ if(verbose)
+ fprint(2, "diff %s\n", oldpath);
+ }else
+ if(verbose)
+ fprint(2, "no recent archive to diff against\n");
+ }
+
+ // Create yyyy/mmdd.
+ tm = *localtime(time(0));
+ snprint(yyyy, sizeof yyyy, "%04d", tm.year+1900);
+ fp = vacfsgetroot(fs);
+ if((f = vacfilewalk(fp, yyyy)) == nil
+ && (f = vacfilecreate(fp, yyyy, ModeDir|0555)) == nil)
+ sysfatal("vacfscreate %s: %r", yyyy);
+ vacfiledecref(fp);
+ fp = f;
+
+ snprint(mmdd, sizeof mmdd, "%02d%02d", tm.mon+1, tm.mday);
+ n = 0;
+ while((f = vacfilewalk(fp, mmdd)) != nil){
+ vacfiledecref(f);
+ n++;
+ snprint(mmdd+4, sizeof mmdd-4, ".%d", n);
+ }
+ f = vacfilecreate(fp, mmdd, ModeDir|0555);
+ if(f == nil)
+ sysfatal("vacfscreate %s/%s: %r", yyyy, mmdd);
+ vacfiledecref(fp);
+
+ if(verbose)
+ fprint(2, "archive %s/%s\n", yyyy, mmdd);
+ }else{
+ if(vacfile == nil)
+ outfd = 1;
+ else if((outfd = create(vacfile, OWRITE, 0666)) < 0)
+ sysfatal("create %s: %r", vacfile);
+ atexit(removevacfile);
+ if((fs = vacfscreate(z, blocksize, 512)) == nil)
+ sysfatal("vacfscreate: %r");
+ f = vacfsgetroot(fs);
+
+ fdiff = nil;
+ if(diffvac){
+ if((fsdiff = vacfsopen(z, diffvac, VtOREAD, 128)) == nil)
+ warn("vacfsopen %s: %r", diffvac);
+ else
+ fdiff = vacfsgetroot(fsdiff);
+ }
+ }
+
if(stdinname)
vacstdin(f, stdinname);
for(i=0; i<argc; i++){
fprint(2, "vacfssync: %r\n");
fprint(outfd, "vac:%V\n", fs->score);
- vacfsclose(fs);
atexitdont(removevacfile);
+ vacfsclose(fs);
vthangup(z);
if(printstats){
threadexitsall(0);
}
+VacFile*
+recentarchive(VacFs *fs, char *path)
+{
+ VacFile *fp, *f;
+ VacDirEnum *de;
+ VacDir vd;
+ char buf[10];
+ int year, mmdd, nn, n, n1;
+ char *p;
+
+ fp = vacfsgetroot(fs);
+ de = vdeopen(fp);
+ year = 0;
+ if(de){
+ for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
+ if(strlen(vd.elem) != 4)
+ continue;
+ if((n = strtol(vd.elem, &p, 10)) < 1900 || *p != 0)
+ continue;
+ if(year < n)
+ year = n;
+ }
+ }
+ vdeclose(de);
+ if(year == 0){
+ vacfiledecref(fp);
+ return nil;
+ }
+ snprint(buf, sizeof buf, "%04d", year);
+ if((f = vacfilewalk(fp, buf)) == nil){
+ fprint(2, "warning: dirread %s but cannot walk", buf);
+ vacfiledecref(fp);
+ return nil;
+ }
+ fp = f;
+
+ de = vdeopen(fp);
+ mmdd = 0;
+ nn = 0;
+ if(de){
+ for(; vderead(de, &vd) > 0; vdcleanup(&vd)){
+ if(strlen(vd.elem) < 4)
+ continue;
+ if((n = strtol(vd.elem, &p, 10)) < 100 || n > 1231 || p != vd.elem+4)
+ continue;
+ if(*p == '.'){
+ if(p[1] == '0' || (n1 = strtol(p+1, &p, 10)) == 0 || *p != 0)
+ continue;
+ }else{
+ if(*p != 0)
+ continue;
+ n1 = 0;
+ }
+ if(n < mmdd || (n == mmdd && n1 < nn))
+ continue;
+ mmdd = n;
+ nn = n1;
+ }
+ }
+ vdeclose(de);
+ if(mmdd == 0){
+ vacfiledecref(fp);
+ return nil;
+ }
+ if(nn == 0)
+ snprint(buf, sizeof buf, "%04d", mmdd);
+ else
+ snprint(buf, sizeof buf, "%04d.%d", mmdd, nn);
+ if((f = vacfilewalk(fp, buf)) == nil){
+ fprint(2, "warning: dirread %s but cannot walk", buf);
+ vacfiledecref(fp);
+ return nil;
+ }
+ vacfiledecref(fp);
+
+ sprint(path, "%04d/%s", year, buf);
+ return f;
+}
+
static void
removevacfile(void)
{
remove(vacfile);
}
-static int
-strpcmp(const void *p0, const void *p1)
-{
- return strcmp(*(char**)p0, *(char**)p1);
-}
-
-static int
-isexcluded(char *name)
-{
- int bot, top, i, x;
-
- bot = 0;
- top = nexclude;
- while(bot < top) {
- i = (bot+top)>>1;
- x = strcmp(exclude[i], name);
- if(x == 0)
- return 1;
- if(x < 0)
- bot = i + 1;
- else /* x > 0 */
- top = i;
- }
- return 0;
-}
-
void
plan9tovacdir(VacDir *vd, Dir *dir)
{
VacFile *f, *fdiff;
VtEntry e;
- if(isexcluded(name)){
+ if(!includefile(name)){
warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : "");
return;
}
blob - /dev/null
blob + 0cd431434a80438995760e2410611d4b4d8b163e (mode 644)
--- /dev/null
+++ src/cmd/vac/testinc.c
+#include "stdinc.h"
+#include "vac.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+void
+threadmain(int argc, char **argv)
+{
+ Biobuf b;
+ char *p;
+
+ ARGBEGIN{
+ default:
+ goto usage;
+ }ARGEND
+
+ if(argc != 1){
+ usage:
+ fprint(2, "usage: testinc includefile\n");
+ threadexitsall("usage");
+ }
+
+ loadexcludefile(argv[0]);
+ Binit(&b, 0, OREAD);
+ while((p = Brdline(&b, '\n')) != nil){
+ p[Blinelen(&b)-1] = 0;
+ print("%d %s\n", includefile(p), p);
+ }
+ threadexitsall(0);
+}