blob: 8c791f2a08038a390d01822bc372a7c55355e489 [file] [log] [blame]
#include <u.h>
#include <libc.h>
#include <venti.h>
#include <libsec.h>
#include <thread.h>
#include <avl.h>
#include <bin.h>
enum
{
// XXX What to do here?
VtMaxLumpSize = 65535,
};
int changes;
int rewrite;
int ignoreerrors;
int fast;
int verbose;
int nskip;
int nwrite;
VtConn *zsrc, *zdst;
uchar zeroscore[VtScoreSize]; /* all zeros */
typedef struct ScoreTree ScoreTree;
struct ScoreTree
{
Avl avl;
uchar score[VtScoreSize];
int type;
};
Avltree *scoretree;
Bin *scorebin;
static int
scoretreecmp(Avl *va, Avl *vb)
{
ScoreTree *a, *b;
int i;
a = (ScoreTree*)va;
b = (ScoreTree*)vb;
i = memcmp(a->score, b->score, VtScoreSize);
if(i != 0)
return i;
return a->type - b->type;
}
static int
havevisited(uchar score[VtScoreSize], int type)
{
ScoreTree a;
if(scoretree == nil)
return 0;
memmove(a.score, score, VtScoreSize);
a.type = type;
return lookupavl(scoretree, &a.avl) != nil;
}
static void
markvisited(uchar score[VtScoreSize], int type)
{
ScoreTree *a;
Avl *old;
if(scoretree == nil)
return;
a = binalloc(&scorebin, sizeof *a, 1);
memmove(a->score, score, VtScoreSize);
a->type = type;
insertavl(scoretree, &a->avl, &old);
}
void
usage(void)
{
fprint(2, "usage: copy [-fimrVv] [-t type] srchost dsthost score\n");
threadexitsall("usage");
}
void
walk(uchar score[VtScoreSize], uint type, int base, int depth)
{
int i, n;
uchar *buf;
uchar nscore[VtScoreSize];
VtEntry e;
VtRoot root;
if(verbose){
for(i = 0; i < depth; i++)
fprint(2, " ");
fprint(2, "-> %d %d %d %V\n", depth, type, base, score);
}
if(memcmp(score, vtzeroscore, VtScoreSize) == 0 || memcmp(score, zeroscore, VtScoreSize) == 0)
return;
if(havevisited(score, type)){
nskip++;
return;
}
buf = vtmallocz(VtMaxLumpSize);
if(fast && vtread(zdst, score, type, buf, VtMaxLumpSize) >= 0){
if(verbose)
fprint(2, "skip %V\n", score);
free(buf);
return;
}
n = vtread(zsrc, score, type, buf, VtMaxLumpSize);
if(n < 0){
if(rewrite){
changes++;
memmove(score, vtzeroscore, VtScoreSize);
}else if(!ignoreerrors)
sysfatal("reading block %V (type %d): %r", score, type);
return;
}
switch(type){
case VtRootType:
if(vtrootunpack(&root, buf) < 0){
fprint(2, "warning: could not unpack root in %V %d\n", score, type);
break;
}
walk(root.prev, VtRootType, 0, depth+1);
walk(root.score, VtDirType, 0, depth+1);
if(rewrite)
vtrootpack(&root, buf); /* walk might have changed score */
break;
case VtDirType:
for(i=0; i<n/VtEntrySize; i++){
if(vtentryunpack(&e, buf, i) < 0){
fprint(2, "warning: could not unpack entry #%d in %V %d\n", i, score, type);
continue;
}
if(!(e.flags & VtEntryActive))
continue;
walk(e.score, e.type, e.type&VtTypeBaseMask, depth+1);
/*
* Don't repack unless we're rewriting -- some old
* vac files have psize==0 and dsize==0, and these
* get rewritten by vtentryunpack to have less strange
* block sizes. So vtentryunpack; vtentrypack does not
* guarantee to preserve the exact bytes in buf.
*/
if(rewrite)
vtentrypack(&e, buf, i);
}
break;
case VtDataType:
break;
default: /* pointers */
for(i=0; i<n; i+=VtScoreSize)
if(memcmp(buf+i, vtzeroscore, VtScoreSize) != 0)
walk(buf+i, type-1, base, depth+1);
break;
}
nwrite++;
if(vtwrite(zdst, nscore, type, buf, n) < 0){
/* figure out score for better error message */
/* can't use input argument - might have changed contents */
n = vtzerotruncate(type, buf, n);
sha1(buf, n, score, nil);
sysfatal("writing block %V (type %d): %r", score, type);
}
if(!rewrite && memcmp(score, nscore, VtScoreSize) != 0)
sysfatal("not rewriting: wrote %V got %V", score, nscore);
if((type !=0 || base !=0) && verbose){
n = vtzerotruncate(type, buf, n);
sha1(buf, n, score, nil);
for(i = 0; i < depth; i++)
fprint(2, " ");
fprint(2, "<- %V\n", score);
}
markvisited(score, type);
free(buf);
}
void
threadmain(int argc, char *argv[])
{
int type, n;
uchar score[VtScoreSize];
uchar *buf;
char *prefix;
fmtinstall('F', vtfcallfmt);
fmtinstall('V', vtscorefmt);
type = -1;
ARGBEGIN{
case 'V':
chattyventi++;
break;
case 'f':
fast = 1;
break;
case 'i':
if(rewrite)
usage();
ignoreerrors = 1;
break;
case 'm':
scoretree = mkavltree(scoretreecmp);
break;
case 'r':
if(ignoreerrors)
usage();
rewrite = 1;
break;
case 't':
type = atoi(EARGF(usage()));
break;
case 'v':
verbose = 1;
break;
default:
usage();
break;
}ARGEND
if(argc != 3)
usage();
if(vtparsescore(argv[2], &prefix, score) < 0)
sysfatal("could not parse score: %r");
buf = vtmallocz(VtMaxLumpSize);
zsrc = vtdial(argv[0]);
if(zsrc == nil)
sysfatal("could not dial src server: %r");
if(vtconnect(zsrc) < 0)
sysfatal("vtconnect src: %r");
zdst = vtdial(argv[1]);
if(zdst == nil)
sysfatal("could not dial dst server: %r");
if(vtconnect(zdst) < 0)
sysfatal("vtconnect dst: %r");
if(type != -1){
n = vtread(zsrc, score, type, buf, VtMaxLumpSize);
if(n < 0)
sysfatal("could not read block: %r");
}else{
for(type=0; type<VtMaxType; type++){
n = vtread(zsrc, score, type, buf, VtMaxLumpSize);
if(n >= 0)
break;
}
if(type == VtMaxType)
sysfatal("could not find block %V of any type", score);
}
walk(score, type, VtDirType, 0);
if(changes)
print("%s:%V (%d pointers rewritten)\n", prefix, score, changes);
if(verbose)
print("%d skipped, %d written\n", nskip, nwrite);
if(vtsync(zdst) < 0)
sysfatal("could not sync dst server: %r");
threadexitsall(0);
}