vac: add -a and -x flags

Thanks to Michael Kaminsky for the suggestion.
This commit is contained in:
Russ Cox 2008-07-03 01:34:48 -04:00
parent d9841dc7ad
commit e05b0ff3eb
11 changed files with 551 additions and 83 deletions

View file

@ -6,6 +6,9 @@ vac, unvac \- create, extract a vac archive on Venti
[ [
.B -mqsv .B -mqsv
] [ ] [
.B -a
.I vacfile
] [
.B -b .B -b
.I blocksize .I blocksize
] [ ] [
@ -23,6 +26,9 @@ vac, unvac \- create, extract a vac archive on Venti
] [ ] [
.B -h .B -h
.I host .I host
] [
.B -x
.I excludefile
] ]
.I file ... .I file ...
.PP .PP
@ -66,6 +72,26 @@ vac:64daefaecc4df4b5cb48a368b361ef56012a4f46
.PP .PP
The options are: The options are:
.TP .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 .BI -b " blocksize
Specifies the block size that data will be broken into. Specifies the block size that data will be broken into.
The units for the size can be specified by appending The units for the size can be specified by appending
@ -86,6 +112,12 @@ file tree given by
Do not include the file or directory specified by Do not include the file or directory specified by
.IR exclude . .IR exclude .
This option may be repeated multiple times. 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 .TP
.BI -f " vacfile .BI -f " vacfile
The results of The results of
@ -123,8 +155,10 @@ the archive to be unpacked.
.TP .TP
.B -q .B -q
Increase the performance of the Increase the performance of the
.B -a
or
.B -d .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. rather than examining the contents of the files.
.TP .TP
.B -s .B -s
@ -133,6 +167,27 @@ Print out various statistics on standard error.
.B -v .B -v
Produce more verbose output on standard error, including the name of the files added to the archive 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. 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 .PP
.I Unvac .I Unvac
lists or extracts files stored in the vac archive lists or extracts files stored in the vac archive

8
src/cmd/vac/exc Normal file
View file

@ -0,0 +1,8 @@
exclude a/*
exclude b/...
exclude c/[~a]*
exclude d/[a]*
exclude e/[a-z]*
exclude f/?a*
exclude g/*/*/b
exclude h/.../b

26
src/cmd/vac/exc.in Normal file
View file

@ -0,0 +1,26 @@
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

26
src/cmd/vac/exc.out Normal file
View file

@ -0,0 +1,26 @@
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

View file

@ -974,6 +974,7 @@ filemetaalloc(VacFile *fp, VacDir *dir, u32int start)
vtblockput(b); vtblockput(b);
if((b = vtfileblock(ms, bo, VtORDWR)) == nil) if((b = vtfileblock(ms, bo, VtORDWR)) == nil)
goto Err; goto Err;
mbunpack(&mb, b->data, ms->dsize);
goto Found; goto Found;
} }
vtblockput(b); vtblockput(b);
@ -1002,7 +1003,6 @@ Found:
me.p = p; me.p = p;
me.size = n; me.size = n;
vdpack(dir, &me, VacDirVersion); vdpack(dir, &me, VacDirVersion);
vdunpack(dir, &me);
mbinsert(&mb, i, &me); mbinsert(&mb, i, &me);
mbpack(&mb); mbpack(&mb);
vtblockput(b); vtblockput(b);
@ -1166,6 +1166,7 @@ Err:
/* /*
* Flush all data associated with f out of the cache and onto venti. * Flush all data associated with f out of the cache and onto venti.
* If recursive is set, flush f's children too. * If recursive is set, flush f's children too.
* Vacfiledecref knows how to flush source and msource too.
*/ */
int int
vacfileflush(VacFile *f, int recursive) vacfileflush(VacFile *f, int recursive)
@ -1183,25 +1184,12 @@ vacfileflush(VacFile *f, int recursive)
ret = -1; ret = -1;
filemetaunlock(f); filemetaunlock(f);
/*
* Vacfiledecref knows how to flush source and msource too.
*/
if(filelock(f) < 0) if(filelock(f) < 0)
return -1; 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 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; nkids = 0;
kids = nil; kids = nil;
@ -1216,14 +1204,32 @@ vacfileflush(VacFile *f, int recursive)
p->ref++; p->ref++;
} }
} }
fileunlock(f); if(nkids > 0){
fileunlock(f);
for(i=0; i<nkids; i++){ for(i=0; i<nkids; i++){
if(vacfileflush(kids[i], 1) < 0) if(vacfileflush(kids[i], 1) < 0)
ret = -1; ret = -1;
vacfiledecref(kids[i]); vacfiledecref(kids[i]);
}
filelock(f);
} }
free(kids); 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; return ret;
} }
@ -1332,6 +1338,12 @@ vacfilecreate(VacFile *fp, char *elem, ulong mode)
vacfileincref(fp); vacfileincref(fp);
fileunlock(fp); fileunlock(fp);
filelock(ff);
vtfilelock(ff->source, -1);
vtfileunlock(ff->source);
fileunlock(ff);
return ff; return ff;
Err: Err:
@ -2031,7 +2043,7 @@ vacfssync(VacFs *fs)
return -1; return -1;
} }
vtfileclose(f); vtfileclose(f);
/* Build a root block. */ /* Build a root block. */
memset(&root, 0, sizeof root); memset(&root, 0, sizeof root);
strcpy(root.type, "vac"); strcpy(root.type, "vac");

View file

@ -23,3 +23,8 @@ VacFile *_vacfileroot(VacFs *fs, VtFile *file);
int _vacfsnextqid(VacFs *fs, uvlong *qid); int _vacfsnextqid(VacFs *fs, uvlong *qid);
void vacfsjumpqid(VacFs*, uvlong step); void vacfsjumpqid(VacFs*, uvlong step);
Reprog* glob2regexp(char*);
void loadexcludefile(char*);
int includefile(char*);
void excludepattern(char*);

180
src/cmd/vac/glob.c Normal file
View file

@ -0,0 +1,180 @@
#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;
}

View file

@ -4,6 +4,7 @@ LIBFILES=\
error\ error\
file\ file\
pack\ pack\
glob\
LIB=${LIBFILES:%=%.$O} $PLAN9/lib/libventi.a LIB=${LIBFILES:%=%.$O} $PLAN9/lib/libventi.a
@ -20,3 +21,10 @@ TARG=vac vacfs unvac
default:V: all default:V: all
<$PLAN9/src/mkmany <$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

View file

@ -1,5 +1,7 @@
#include <u.h> #include <u.h>
#include <libc.h> #include <libc.h>
#include <bio.h>
#include <thread.h> #include <thread.h>
#include <venti.h> #include <venti.h>
#include <libsec.h> #include <libsec.h>
#include <regexp.h>

31
src/cmd/vac/testinc.c Normal file
View file

@ -0,0 +1,31 @@
#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);
}

View file

@ -8,14 +8,13 @@
void void
usage(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"); threadexitsall("usage");
} }
enum enum
{ {
BlockSize = 8*1024, BlockSize = 8*1024,
MaxExclude = 1000
}; };
struct struct
@ -33,17 +32,16 @@ int verbose;
char *host; char *host;
VtConn *z; VtConn *z;
VacFs *fs; VacFs *fs;
char *exclude[MaxExclude]; char *archivefile;
int nexclude;
char *vacfile; char *vacfile;
int vacmerge(VacFile*, char*); int vacmerge(VacFile*, char*);
void vac(VacFile*, VacFile*, char*, Dir*); void vac(VacFile*, VacFile*, char*, Dir*);
void vacstdin(VacFile*, char*); void vacstdin(VacFile*, char*);
VacFile *recentarchive(VacFs*, char*);
static u64int unittoull(char*); static u64int unittoull(char*);
static void warn(char *fmt, ...); static void warn(char *fmt, ...);
static int strpcmp(const void*, const void*);
static void removevacfile(void); static void removevacfile(void);
#ifdef PLAN9PORT #ifdef PLAN9PORT
@ -81,6 +79,7 @@ threadmain(int argc, char **argv)
_p9usepwlibrary = 1; _p9usepwlibrary = 1;
#endif #endif
fmtinstall('F', vtfcallfmt);
fmtinstall('H', encodefmt); fmtinstall('H', encodefmt);
fmtinstall('V', vtscorefmt); fmtinstall('V', vtscorefmt);
@ -89,7 +88,14 @@ threadmain(int argc, char **argv)
printstats = 0; printstats = 0;
fsdiff = nil; fsdiff = nil;
diffvac = nil; diffvac = nil;
ARGBEGIN{ ARGBEGIN{
case 'V':
chattyventi++;
break;
case 'a':
archivefile = EARGF(usage());
break;
case 'b': case 'b':
u = unittoull(EARGF(usage())); u = unittoull(EARGF(usage()));
if(u < 512) if(u < 512)
@ -102,12 +108,7 @@ threadmain(int argc, char **argv)
diffvac = EARGF(usage()); diffvac = EARGF(usage());
break; break;
case 'e': case 'e':
if(nexclude >= MaxExclude) excludepattern(EARGF(usage()));
sysfatal("too many exclusions\n");
exclude[nexclude] = ARGF();
if(exclude[nexclude] == nil)
usage();
nexclude++;
break; break;
case 'f': case 'f':
vacfile = EARGF(usage()); vacfile = EARGF(usage());
@ -130,40 +131,101 @@ threadmain(int argc, char **argv)
case 'v': case 'v':
verbose++; verbose++;
break; break;
case 'x':
loadexcludefile(EARGF(usage()));
break;
default: default:
usage(); usage();
}ARGEND }ARGEND
if(argc == 0 && !stdinname) if(argc == 0 && !stdinname)
usage(); usage();
if(vacfile == nil) if(archivefile && (vacfile || diffvac)){
outfd = 1; fprint(2, "cannot use -a with -f, -d\n");
else if((outfd = create(vacfile, OWRITE, 0666)) < 0) usage();
sysfatal("create %s: %r", vacfile); }
atexit(removevacfile);
qsort(exclude, nexclude, sizeof(char*), strpcmp);
z = vtdial(host); z = vtdial(host);
if(z == nil) if(z == nil)
sysfatal("could not connect to server: %r"); sysfatal("could not connect to server: %r");
if(vtconnect(z) < 0) if(vtconnect(z) < 0)
sysfatal("vtconnect: %r"); sysfatal("vtconnect: %r");
if(diffvac){ // Setup:
if((fsdiff = vacfsopen(z, diffvac, VtOREAD, 128)) == nil) // fs is the output vac file system
warn("vacfsopen %s: %r", diffvac); // 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;
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((fs = vacfscreate(z, blocksize, 512)) == nil)
sysfatal("vacfscreate: %r");
f = vacfsgetroot(fs);
if(fsdiff)
fdiff = vacfsgetroot(fsdiff);
else
fdiff = nil;
if(stdinname) if(stdinname)
vacstdin(f, stdinname); vacstdin(f, stdinname);
for(i=0; i<argc; i++){ for(i=0; i<argc; i++){
@ -228,8 +290,8 @@ threadmain(int argc, char **argv)
fprint(2, "vacfssync: %r\n"); fprint(2, "vacfssync: %r\n");
fprint(outfd, "vac:%V\n", fs->score); fprint(outfd, "vac:%V\n", fs->score);
vacfsclose(fs);
atexitdont(removevacfile); atexitdont(removevacfile);
vacfsclose(fs);
vthangup(z); vthangup(z);
if(printstats){ if(printstats){
@ -243,6 +305,85 @@ threadmain(int argc, char **argv)
threadexitsall(0); 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 static void
removevacfile(void) removevacfile(void)
{ {
@ -250,32 +391,6 @@ removevacfile(void)
remove(vacfile); 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 void
plan9tovacdir(VacDir *vd, Dir *dir) plan9tovacdir(VacDir *vd, Dir *dir)
{ {
@ -361,7 +476,7 @@ vac(VacFile *fp, VacFile *diffp, char *name, Dir *d)
VacFile *f, *fdiff; VacFile *f, *fdiff;
VtEntry e; VtEntry e;
if(isexcluded(name)){ if(!includefile(name)){
warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : ""); warn("excluding %s%s", name, (d->mode&DMDIR) ? "/" : "");
return; return;
} }