/* * Change the depth of the VtFile r. * The entry e for r is contained in block p. */ static int growdepth(VtFile *r, VtBlock *p, VtEntry *e, int depth) { VtBlock *b, *bb; assert(ISLOCKED(r)); assert(depth <= VtPointerDepth); b = vtcacheglobal(r->c, e->score, e->type); if(b == nil) return -1; /* * Keep adding layers until we get to the right depth * or an error occurs. */ while(DEPTH(e->type) < depth){ bb = vtcacheallocblock(r->c, e->type+1); if(bb == nil) break; memmove(bb->data, b->score, VtScoreSize); memmove(e->score, bb->score, VtScoreSize); e->type++; e->flags |= VtEntryLocal; vtblockput(b); b = bb; } vtentrypack(e, p->data, r->offset % r->epb); vtblockput(b); if(DEPTH(e->type) == depth) return 0; return -1; }
static VtBlock * blockwalk(VtBlock *p, int index, VtCache *c, int mode, VtEntry *e) { VtBlock *b; int type; uchar *score; VtEntry oe; switch(p->type){ case VtDataType: assert(0); case VtDirType: type = e->type; score = e->score; break; default: type = p->type - 1; score = p->data+index*VtScoreSize; break; } /*print("walk from %V/%d ty %d to %V ty %d\n", p->score, index, p->type, score, type); */ if(mode == VtOWRITE && vtglobaltolocal(score) == NilBlock){ print("VtOWRITE && NilBlock\n"); b = vtcacheallocblock(c, type); print("vtcachealloblcok returns %p\n", b); if(b) goto HaveCopy; }else b = vtcacheglobal(c, score, type); if(b == nil || mode == VtOREAD) return b; if(vtglobaltolocal(b->score) != NilBlock) return b; oe = *e; /* * Copy on write. */ e->flags |= VtEntryLocal; b = vtblockcopy(b/*, e->tag, fs->ehi, fs->elo*/); if(b == nil) return nil; HaveCopy: if(p->type == VtDirType){ memmove(e->score, b->score, VtScoreSize); vtentrypack(e, p->data, index); }else{ memmove(p->data+index*VtScoreSize, b->score, VtScoreSize); } return b; }
void threadmain(int argc, char **argv) { char *pref, *mountname, *mountplace; uchar score[VtScoreSize], prev[VtScoreSize]; int i, fd, csize; vlong bsize; Tm tm; VtEntry e; VtBlock *b; VtCache *c; VtRoot root; char *tmp, *tmpnam; fmtinstall('F', vtfcallfmt); fmtinstall('H', encodefmt); fmtinstall('T', timefmt); fmtinstall('V', vtscorefmt); mountname = sysname(); mountplace = nil; ARGBEGIN{ default: usage(); break; case 'D': debug++; break; case 'V': chattyventi = 1; break; case 'f': fastwrites = 1; break; case 'i': incremental = 1; break; case 'm': mountname = EARGF(usage()); break; case 'M': mountplace = EARGF(usage()); i = strlen(mountplace); if(i > 0 && mountplace[i-1] == '/') mountplace[i-1] = 0; break; case 'n': nop = 1; break; case 's': statustime = atoi(EARGF(usage())); break; case 'v': verbose = 1; break; case 'w': nwritethread = atoi(EARGF(usage())); break; }ARGEND if(argc != 1 && argc != 2) usage(); if(statustime) print("# %T vbackup %s %s\n", argv[0], argc>=2 ? argv[1] : ""); /* * open fs */ if((disk = diskopenfile(argv[0])) == nil) sysfatal("diskopen: %r"); if((disk = diskcache(disk, 32768, 2*MAXQ+16)) == nil) sysfatal("diskcache: %r"); if((fsys = fsysopen(disk)) == nil) sysfatal("fsysopen: %r"); /* * connect to venti */ if((z = vtdial(nil)) == nil) sysfatal("vtdial: %r"); if(vtconnect(z) < 0) sysfatal("vtconnect: %r"); /* * set up venti block cache */ zero = vtmallocz(fsys->blocksize); bsize = fsys->blocksize; csize = 50; /* plenty; could probably do with 5 */ if(verbose) fprint(2, "cache %d blocks\n", csize); c = vtcachealloc(z, bsize*csize); zcache = c; /* * parse starting score */ memset(prev, 0, sizeof prev); if(argc == 1){ vfile = vtfilecreateroot(c, (fsys->blocksize/VtScoreSize)*VtScoreSize, fsys->blocksize, VtDataType); if(vfile == nil) sysfatal("vtfilecreateroot: %r"); vtfilelock(vfile, VtORDWR); if(vtfilewrite(vfile, zero, 1, bsize*fsys->nblock-1) != 1) sysfatal("vtfilewrite: %r"); if(vtfileflush(vfile) < 0) sysfatal("vtfileflush: %r"); }else{ if(vtparsescore(argv[1], &pref, score) < 0) sysfatal("bad score: %r"); if(pref!=nil && strcmp(pref, fsys->type) != 0) sysfatal("score is %s but fsys is %s", pref, fsys->type); b = vtcacheglobal(c, score, VtRootType, VtRootSize); if(b){ if(vtrootunpack(&root, b->data) < 0) sysfatal("bad root: %r"); if(strcmp(root.type, fsys->type) != 0) sysfatal("root is %s but fsys is %s", root.type, fsys->type); memmove(prev, score, VtScoreSize); memmove(score, root.score, VtScoreSize); vtblockput(b); } b = vtcacheglobal(c, score, VtDirType, VtEntrySize); if(b == nil) sysfatal("vtcacheglobal %V: %r", score); if(vtentryunpack(&e, b->data, 0) < 0) sysfatal("%V: vtentryunpack failed", score); if(verbose) fprint(2, "entry: size %llud psize %d dsize %d\n", e.size, e.psize, e.dsize); vtblockput(b); if((vfile = vtfileopenroot(c, &e)) == nil) sysfatal("vtfileopenroot: %r"); vtfilelock(vfile, VtORDWR); if(e.dsize != bsize) sysfatal("file system block sizes don't match %d %lld", e.dsize, bsize); if(e.size != fsys->nblock*bsize) sysfatal("file system block counts don't match %lld %lld", e.size, fsys->nblock*bsize); } tmpnam = nil; if(incremental){ if(vtfilegetentry(vfile, &e) < 0) sysfatal("vtfilegetentry: %r"); if((vscores = vtfileopenroot(c, &e)) == nil) sysfatal("vtfileopenroot: %r"); vtfileunlock(vfile); }else{ /* * write scores of blocks into temporary file */ if((tmp = getenv("TMP")) != nil){ /* okay, good */ }else if(access("/var/tmp", 0) >= 0) tmp = "/var/tmp"; else tmp = "/tmp"; tmpnam = smprint("%s/vbackup.XXXXXX", tmp); if(tmpnam == nil) sysfatal("smprint: %r"); if((fd = opentemp(tmpnam, ORDWR|ORCLOSE)) < 0) sysfatal("opentemp %s: %r", tmpnam); if(statustime) print("# %T reading scores into %s\n", tmpnam); if(verbose) fprint(2, "read scores into %s...\n", tmpnam); Binit(&bscores, fd, OWRITE); for(i=0; i<fsys->nblock; i++){ if(vtfileblockscore(vfile, i, score) < 0) sysfatal("vtfileblockhash %d: %r", i); if(Bwrite(&bscores, score, VtScoreSize) != VtScoreSize) sysfatal("Bwrite: %r"); } Bterm(&bscores); vtfileunlock(vfile); /* * prep scores for rereading */ seek(fd, 0, 0); Binit(&bscores, fd, OREAD); } /* * start the main processes */ if(statustime) print("# %T starting procs\n"); qcmp = qalloc(); qventi = qalloc(); rlock(&endlk); proccreate(fsysproc, nil, STACK); rlock(&endlk); proccreate(ventiproc, nil, STACK); rlock(&endlk); proccreate(cmpproc, nil, STACK); if(statustime){ rlock(&endlk); proccreate(statusproc, nil, STACK); } /* * wait for processes to finish */ wlock(&endlk); qfree(qcmp); qfree(qventi); if(statustime) print("# %T procs exited: %d of %d %d-byte blocks changed, " "%d read, %d written, %d skipped, %d copied\n", nchange, fsys->nblock, fsys->blocksize, vtcachenread, vtcachenwrite, nskip, vtcachencopy); /* * prepare root block */ if(incremental) vtfileclose(vscores); vtfilelock(vfile, -1); if(vtfileflush(vfile) < 0) sysfatal("vtfileflush: %r"); if(vtfilegetentry(vfile, &e) < 0) sysfatal("vtfilegetentry: %r"); vtfileunlock(vfile); vtfileclose(vfile); b = vtcacheallocblock(c, VtDirType, VtEntrySize); if(b == nil) sysfatal("vtcacheallocblock: %r"); vtentrypack(&e, b->data, 0); if(vtblockwrite(b) < 0) sysfatal("vtblockwrite: %r"); memset(&root, 0, sizeof root); strecpy(root.name, root.name+sizeof root.name, argv[0]); strecpy(root.type, root.type+sizeof root.type, fsys->type); memmove(root.score, b->score, VtScoreSize); root.blocksize = fsys->blocksize; memmove(root.prev, prev, VtScoreSize); vtblockput(b); b = vtcacheallocblock(c, VtRootType, VtRootSize); if(b == nil) sysfatal("vtcacheallocblock: %r"); vtrootpack(&root, b->data); if(vtblockwrite(b) < 0) sysfatal("vtblockwrite: %r"); tm = *localtime(time(0)); tm.year += 1900; tm.mon++; if(mountplace == nil) mountplace = guessmountplace(argv[0]); print("mount /%s/%d/%02d%02d%s %s:%V %d/%02d%02d/%02d%02d\n", mountname, tm.year, tm.mon, tm.mday, mountplace, root.type, b->score, tm.year, tm.mon, tm.mday, tm.hour, tm.min); print("# %T %s %s:%V\n", argv[0], root.type, b->score); if(statustime) print("# %T venti sync\n"); vtblockput(b); if(vtsync(z) < 0) sysfatal("vtsync: %r"); if(statustime) print("# %T synced\n"); fsysclose(fsys); diskclose(disk); vtcachefree(zcache); // Vtgoodbye hangs on Linux - not sure why. // Probably vtfcallrpc doesn't quite get the // remote hangup right. Also probably related // to the vtrecvproc problem below. // vtgoodbye(z); // Leak here, because I can't seem to make // the vtrecvproc exit. // vtfreeconn(z); free(tmpnam); z = nil; zcache = nil; fsys = nil; disk = nil; threadexitsall(nil); }
/* * Retrieve the block containing the entry for r. * If a snapshot has happened, we might need * to get a new copy of the block. We avoid this * in the common case by caching the score for * the block and the last epoch in which it was valid. * * We use r->mode to tell the difference between active * file system VtFiles (VtORDWR) and VtFiles for the * snapshot file system (VtOREAD). */ static VtBlock* fileloadblock(VtFile *r, int mode) { char e[ERRMAX]; u32int addr; VtBlock *b; switch(r->mode){ default: assert(0); case VtOWRITE: assert(r->mode == VtORDWR); if(r->local == 1){ b = vtcacheglobal(r->c, r->score, VtDirType); if(b == nil) return nil; b->pc = getcallerpc(&r); return b; } assert(r->parent != nil); if(vtfilelock(r->parent, VtORDWR) < 0) return nil; b = vtfileblock(r->parent, r->offset/r->epb, VtORDWR); if(b == nil) b = vtfileblock(r->parent, r->offset/r->epb, VtOWRITE); vtfileunlock(r->parent); if(b == nil) return nil; memmove(r->score, b->score, VtScoreSize); r->local = 1; return b; case VtORDWR: assert(r->mode == VtORDWR); if(r->local == 1){ b = vtcacheglobal(r->c, r->score, VtDirType); if(b == nil) return nil; b->pc = getcallerpc(&r); return b; } assert(r->parent != nil); if(vtfilelock(r->parent, VtORDWR) < 0) return nil; b = vtfileblock(r->parent, r->offset/r->epb, VtORDWR); vtfileunlock(r->parent); if(b == nil) return nil; memmove(r->score, b->score, VtScoreSize); r->local = 1; return b; case VtOREAD: if(mode == VtORDWR){ werrstr("read/write lock of read-only file"); return nil; } addr = vtglobaltolocal(r->score); if(addr == NilBlock) return vtcacheglobal(r->c, r->score, VtDirType); b = vtcachelocal(r->c, addr, VtDirType); if(b) return b; /* * If it failed because the epochs don't match, the block has been * archived and reclaimed. Rewalk from the parent and get the * new pointer. This can't happen in the VtORDWR case * above because blocks in the current epoch don't get * reclaimed. The fact that we're VtOREAD means we're * a snapshot. (Or else the file system is read-only, but then * the archiver isn't going around deleting blocks.) */ rerrstr(e, sizeof e); if(strcmp(e, ELabelMismatch) == 0){ if(vtfilelock(r->parent, VtOREAD) < 0) return nil; b = vtfileblock(r->parent, r->offset/r->epb, VtOREAD); vtfileunlock(r->parent); if(b){ fprint(2, "vtfilealloc: lost %V found %V\n", r->score, b->score); memmove(r->score, b->score, VtScoreSize); return b; } } return nil; } }
static int shrinkdepth(VtFile *r, VtBlock *p, VtEntry *e, int depth) { VtBlock *b, *nb, *ob, *rb; VtEntry oe; assert(ISLOCKED(r)); assert(depth <= VtPointerDepth); rb = vtcacheglobal(r->c, e->score, e->type); if(rb == nil) return -1; /* * Walk down to the new root block. * We may stop early, but something is better than nothing. */ oe = *e; ob = nil; b = rb; for(; DEPTH(e->type) > depth; e->type--){ nb = vtcacheglobal(r->c, b->data, e->type-1); if(nb == nil) break; if(ob!=nil && ob!=rb) vtblockput(ob); ob = b; b = nb; } if(b == rb){ vtblockput(rb); return 0; } /* * Right now, e points at the root block rb, b is the new root block, * and ob points at b. To update: * * (i) change e to point at b * (ii) zero the pointer ob -> b * (iii) free the root block * * p (the block containing e) must be written before * anything else. */ /* (i) */ memmove(e->score, b->score, VtScoreSize); vtentrypack(e, p->data, r->offset % r->epb); /* (ii) */ memmove(ob->data, vtzeroscore, VtScoreSize); /* (iii) */ vtblockput(rb); if(ob!=nil && ob!=rb) vtblockput(ob); vtblockput(b); if(DEPTH(e->type) == depth) return 0; return -1; }
static int shrinksize(VtFile *r, VtEntry *e, uvlong size) { int i, depth, type, isdir, ppb; uvlong ptrsz; uchar score[VtScoreSize]; VtBlock *b; b = vtcacheglobal(r->c, e->score, e->type); if(b == nil) return -1; ptrsz = e->dsize; ppb = e->psize/VtScoreSize; type = e->type; depth = DEPTH(type); for(i=0; i+1<depth; i++) ptrsz *= ppb; isdir = r->dir; while(depth > 0){ if(b->addr == NilBlock){ /* not worth copying the block just so we can zero some of it */ vtblockput(b); return -1; } /* * invariant: each pointer in the tree rooted at b accounts for ptrsz bytes */ /* zero the pointers to unnecessary blocks */ i = (size+ptrsz-1)/ptrsz; for(; i<ppb; i++) memmove(b->data+i*VtScoreSize, vtzeroscore, VtScoreSize); /* recurse (go around again) on the partially necessary block */ i = size/ptrsz; size = size%ptrsz; if(size == 0){ vtblockput(b); return 0; } ptrsz /= ppb; type--; memmove(score, b->data+i*VtScoreSize, VtScoreSize); vtblockput(b); b = vtcacheglobal(r->c, score, type); if(b == nil) return -1; } if(b->addr == NilBlock){ vtblockput(b); return -1; } /* * No one ever truncates BtDir blocks. */ if(depth==0 && !isdir && e->dsize > size) memset(b->data+size, 0, e->dsize-size); vtblockput(b); return 0; }