blob: e97df39c6ab8e6e03769cbd27829faac5b934f3f [file] [log] [blame]
/*
* Manage tree of VtFiles stored in the block cache.
*
* The single point of truth for the info about the VtFiles themselves
* is the block data. Because of this, there is no explicit locking of
* VtFile structures, and indeed there may be more than one VtFile
* structure for a given Venti file. They synchronize through the
* block cache.
*
* This is a bit simpler than fossil because there are no epochs
* or tags or anything else. Just mutable local blocks and immutable
* Venti blocks.
*/
#include <u.h>
#include <libc.h>
#include <venti.h>
#define MaxBlock (1UL<<31)
static char ENotDir[] = "walk in non-directory";
static char ETooBig[] = "file too big";
/* static char EBadAddr[] = "bad address"; */
static char ELabelMismatch[] = "label mismatch";
static int sizetodepth(uvlong s, int psize, int dsize);
static VtBlock *fileload(VtFile *r, VtEntry *e);
static int shrinkdepth(VtFile*, VtBlock*, VtEntry*, int);
static int shrinksize(VtFile*, VtEntry*, uvlong);
static int growdepth(VtFile*, VtBlock*, VtEntry*, int);
#define ISLOCKED(r) ((r)->b != nil)
#define DEPTH(t) ((t)&VtTypeDepthMask)
static VtFile *
vtfilealloc(VtCache *c, VtBlock *b, VtFile *p, u32int offset, int mode)
{
int epb;
VtEntry e;
VtFile *r;
assert(p==nil || ISLOCKED(p));
if(p == nil){
assert(offset == 0);
epb = 1;
}else
epb = p->dsize / VtEntrySize;
if(b->type != VtDirType){
werrstr("bad block type %#uo", b->type);
return nil;
}
/*
* a non-active entry is the only thing that
* can legitimately happen here. all the others
* get prints.
*/
if(vtentryunpack(&e, b->data, offset % epb) < 0){
fprint(2, "vtentryunpack failed: %r (%.*H)\n", VtEntrySize, b->data+VtEntrySize*(offset%epb));
return nil;
}
if(!(e.flags & VtEntryActive)){
werrstr("entry not active");
return nil;
}
if(DEPTH(e.type) < sizetodepth(e.size, e.psize, e.dsize)){
fprint(2, "depth %ud size %llud psize %lud dsize %lud\n",
DEPTH(e.type), e.size, e.psize, e.dsize);
werrstr("bad depth");
return nil;
}
r = vtmallocz(sizeof(VtFile));
r->c = c;
r->bsize = b->size;
r->mode = mode;
r->dsize = e.dsize;
r->psize = e.psize;
r->gen = e.gen;
r->dir = (e.type & VtTypeBaseMask) == VtDirType;
r->ref = 1;
r->parent = p;
if(p){
qlock(&p->lk);
assert(mode == VtOREAD || p->mode == VtORDWR);
p->ref++;
qunlock(&p->lk);
}else{
assert(b->addr != NilBlock);
r->local = 1;
}
memmove(r->score, b->score, VtScoreSize);
r->offset = offset;
r->epb = epb;
return r;
}
VtFile *
vtfileroot(VtCache *c, u32int addr, int mode)
{
VtFile *r;
VtBlock *b;
b = vtcachelocal(c, addr, VtDirType);
if(b == nil)
return nil;
r = vtfilealloc(c, b, nil, 0, mode);
vtblockput(b);
return r;
}
VtFile*
vtfileopenroot(VtCache *c, VtEntry *e)
{
VtBlock *b;
VtFile *f;
b = vtcacheallocblock(c, VtDirType, VtEntrySize);
if(b == nil)
return nil;
vtentrypack(e, b->data, 0);
f = vtfilealloc(c, b, nil, 0, VtORDWR);
vtblockput(b);
return f;
}
VtFile *
vtfilecreateroot(VtCache *c, int psize, int dsize, int type)
{
VtEntry e;
memset(&e, 0, sizeof e);
e.flags = VtEntryActive;
e.psize = psize;
e.dsize = dsize;
e.type = type;
memmove(e.score, vtzeroscore, VtScoreSize);
return vtfileopenroot(c, &e);
}
VtFile *
vtfileopen(VtFile *r, u32int offset, int mode)
{
ulong bn;
VtBlock *b;
assert(ISLOCKED(r));
if(!r->dir){
werrstr(ENotDir);
return nil;
}
bn = offset/(r->dsize/VtEntrySize);
b = vtfileblock(r, bn, mode);
if(b == nil)
return nil;
r = vtfilealloc(r->c, b, r, offset, mode);
vtblockput(b);
return r;
}
VtFile*
vtfilecreate(VtFile *r, int psize, int dsize, int type)
{
return _vtfilecreate(r, -1, psize, dsize, type);
}
VtFile*
_vtfilecreate(VtFile *r, int o, int psize, int dsize, int type)
{
int i;
VtBlock *b;
u32int bn, size;
VtEntry e;
int epb;
VtFile *rr;
u32int offset;
assert(ISLOCKED(r));
assert(type == VtDirType || type == VtDataType);
if(!r->dir){
werrstr(ENotDir);
return nil;
}
epb = r->dsize/VtEntrySize;
size = vtfilegetdirsize(r);
/*
* look at a random block to see if we can find an empty entry
*/
if(o == -1){
offset = lnrand(size+1);
offset -= offset % epb;
}else
offset = o;
/* try the given block and then try the last block */
for(;;){
bn = offset/epb;
b = vtfileblock(r, bn, VtORDWR);
if(b == nil)
return nil;
for(i=offset%r->epb; i<epb; i++){
if(vtentryunpack(&e, b->data, i) < 0)
continue;
if((e.flags&VtEntryActive) == 0 && e.gen != ~0)
goto Found;
}
vtblockput(b);
if(offset == size){
fprint(2, "vtfilecreate: cannot happen\n");
werrstr("vtfilecreate: cannot happen");
return nil;
}
offset = size;
}
Found:
/* found an entry - gen already set */
e.psize = psize;
e.dsize = dsize;
e.flags = VtEntryActive;
e.type = type;
e.size = 0;
memmove(e.score, vtzeroscore, VtScoreSize);
vtentrypack(&e, b->data, i);
offset = bn*epb + i;
if(offset+1 > size){
if(vtfilesetdirsize(r, offset+1) < 0){
vtblockput(b);
return nil;
}
}
rr = vtfilealloc(r->c, b, r, offset, VtORDWR);
vtblockput(b);
return rr;
}
static int
vtfilekill(VtFile *r, int doremove)
{
VtEntry e;
VtBlock *b;
assert(ISLOCKED(r));
b = fileload(r, &e);
if(b == nil)
return -1;
if(doremove==0 && e.size == 0){
/* already truncated */
vtblockput(b);
return 0;
}
if(doremove){
if(e.gen != ~0)
e.gen++;
e.dsize = 0;
e.psize = 0;
e.flags = 0;
}else
e.flags &= ~VtEntryLocal;
e.type = 0;
e.size = 0;
memmove(e.score, vtzeroscore, VtScoreSize);
vtentrypack(&e, b->data, r->offset % r->epb);
vtblockput(b);
if(doremove){
vtfileunlock(r);
vtfileclose(r);
}
return 0;
}
int
vtfileremove(VtFile *r)
{
return vtfilekill(r, 1);
}
int
vtfiletruncate(VtFile *r)
{
return vtfilekill(r, 0);
}
uvlong
vtfilegetsize(VtFile *r)
{
VtEntry e;
VtBlock *b;
assert(ISLOCKED(r));
b = fileload(r, &e);
if(b == nil)
return ~(uvlong)0;
vtblockput(b);
return e.size;
}
static int
shrinksize(VtFile *r, VtEntry *e, uvlong size)
{
int i, depth, bsiz, type, isdir, ppb;
uvlong ptrsz;
uchar score[VtScoreSize];
VtBlock *b;
b = vtcacheglobal(r->c, e->score, e->type, r->dsize);
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(type) > 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);
if(type == VtDataType || type == VtDirType)
bsiz = r->dsize;
else
bsiz = r->psize;
b = vtcacheglobal(r->c, score, type, bsiz);
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;
}
int
vtfilesetsize(VtFile *r, u64int size)
{
int depth, edepth;
VtEntry e;
VtBlock *b;
assert(ISLOCKED(r));
if(size == 0)
return vtfiletruncate(r);
if(size > VtMaxFileSize || size > ((uvlong)MaxBlock)*r->dsize){
werrstr(ETooBig);
return -1;
}
b = fileload(r, &e);
if(b == nil)
return -1;
/* quick out */
if(e.size == size){
vtblockput(b);
return 0;
}
depth = sizetodepth(size, e.psize, e.dsize);
edepth = DEPTH(e.type);
if(depth < edepth){
if(shrinkdepth(r, b, &e, depth) < 0){
vtblockput(b);
return -1;
}
}else if(depth > edepth){
if(growdepth(r, b, &e, depth) < 0){
vtblockput(b);
return -1;
}
}
if(size < e.size)
shrinksize(r, &e, size);
e.size = size;
vtentrypack(&e, b->data, r->offset % r->epb);
vtblockput(b);
return 0;
}
int
vtfilesetdirsize(VtFile *r, u32int ds)
{
uvlong size;
int epb;
assert(ISLOCKED(r));
epb = r->dsize/VtEntrySize;
size = (uvlong)r->dsize*(ds/epb);
size += VtEntrySize*(ds%epb);
return vtfilesetsize(r, size);
}
u32int
vtfilegetdirsize(VtFile *r)
{
ulong ds;
uvlong size;
int epb;
assert(ISLOCKED(r));
epb = r->dsize/VtEntrySize;
size = vtfilegetsize(r);
ds = epb*(size/r->dsize);
ds += (size%r->dsize)/VtEntrySize;
return ds;
}
int
vtfilegetentry(VtFile *r, VtEntry *e)
{
VtBlock *b;
assert(ISLOCKED(r));
b = fileload(r, e);
if(b == nil)
return -1;
vtblockput(b);
return 0;
}
int
vtfilesetentry(VtFile *r, VtEntry *e)
{
VtBlock *b;
VtEntry ee;
assert(ISLOCKED(r));
b = fileload(r, &ee);
if(b == nil)
return -1;
vtentrypack(e, b->data, r->offset % r->epb);
vtblockput(b);
return 0;
}
static VtBlock *
blockwalk(VtFile *r, VtBlock *p, int index, VtCache *c, int mode, VtEntry *e)
{
VtBlock *b;
int type, size;
uchar *score;
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(type == VtDirType || type == VtDataType)
size = r->dsize;
else
size = r->psize;
if(mode == VtOWRITE && vtglobaltolocal(score) == NilBlock){
b = vtcacheallocblock(c, type, size);
if(b)
goto HaveCopy;
}else
b = vtcacheglobal(c, score, type, size);
if(b == nil || mode == VtOREAD)
return b;
if(vtglobaltolocal(b->score) != NilBlock)
return b;
/*
* 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;
}
/*
* 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, r->dsize);
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, r->psize);
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 int
shrinkdepth(VtFile *r, VtBlock *p, VtEntry *e, int depth)
{
VtBlock *b, *nb, *ob, *rb;
assert(ISLOCKED(r));
assert(depth <= VtPointerDepth);
rb = vtcacheglobal(r->c, e->score, e->type, r->psize);
if(rb == nil)
return -1;
/*
* Walk down to the new root block.
* We may stop early, but something is better than nothing.
*/
ob = nil;
b = rb;
for(; DEPTH(e->type) > depth; e->type--){
nb = vtcacheglobal(r->c, b->data, e->type-1, r->psize);
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
mkindices(VtEntry *e, u32int bn, int *index)
{
int i, np;
memset(index, 0, (VtPointerDepth+1)*sizeof(int));
np = e->psize/VtScoreSize;
for(i=0; bn > 0; i++){
if(i >= VtPointerDepth){
werrstr("bad address 0x%lux", (ulong)bn);
return -1;
}
index[i] = bn % np;
bn /= np;
}
return i;
}
VtBlock *
vtfileblock(VtFile *r, u32int bn, int mode)
{
VtBlock *b, *bb;
int index[VtPointerDepth+1];
VtEntry e;
int i;
int m;
assert(ISLOCKED(r));
assert(bn != NilBlock);
b = fileload(r, &e);
if(b == nil)
return nil;
i = mkindices(&e, bn, index);
if(i < 0)
goto Err;
if(i > DEPTH(e.type)){
if(mode == VtOREAD){
werrstr("bad address 0x%lux", (ulong)bn);
goto Err;
}
index[i] = 0;
if(growdepth(r, b, &e, i) < 0)
goto Err;
}
assert(b->type == VtDirType);
index[DEPTH(e.type)] = r->offset % r->epb;
/* mode for intermediate block */
m = mode;
if(m == VtOWRITE)
m = VtORDWR;
for(i=DEPTH(e.type); i>=0; i--){
bb = blockwalk(r, b, index[i], r->c, i==0 ? mode : m, &e);
if(bb == nil)
goto Err;
vtblockput(b);
b = bb;
}
b->pc = getcallerpc(&r);
return b;
Err:
vtblockput(b);
return nil;
}
int
vtfileblockscore(VtFile *r, u32int bn, uchar score[VtScoreSize])
{
VtBlock *b, *bb;
int index[VtPointerDepth+1];
VtEntry e;
int i;
assert(ISLOCKED(r));
assert(bn != NilBlock);
b = fileload(r, &e);
if(b == nil)
return -1;
if(DEPTH(e.type) == 0){
memmove(score, e.score, VtScoreSize);
vtblockput(b);
return 0;
}
i = mkindices(&e, bn, index);
if(i < 0){
vtblockput(b);
return -1;
}
if(i > DEPTH(e.type)){
memmove(score, vtzeroscore, VtScoreSize);
vtblockput(b);
return 0;
}
index[DEPTH(e.type)] = r->offset % r->epb;
for(i=DEPTH(e.type); i>=1; i--){
bb = blockwalk(r, b, index[i], r->c, VtOREAD, &e);
if(bb == nil)
goto Err;
vtblockput(b);
b = bb;
if(memcmp(b->score, vtzeroscore, VtScoreSize) == 0)
break;
}
memmove(score, b->data+index[0]*VtScoreSize, VtScoreSize);
vtblockput(b);
return 0;
Err:
vtblockput(b);
return -1;
}
void
vtfileincref(VtFile *r)
{
qlock(&r->lk);
r->ref++;
qunlock(&r->lk);
}
void
vtfileclose(VtFile *r)
{
if(r == nil)
return;
qlock(&r->lk);
r->ref--;
if(r->ref){
qunlock(&r->lk);
return;
}
assert(r->ref == 0);
qunlock(&r->lk);
if(r->parent)
vtfileclose(r->parent);
memset(r, ~0, sizeof(*r));
vtfree(r);
}
/*
* 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 VtORDWR:
assert(r->mode == VtORDWR);
if(r->local == 1){
b = vtcacheglobal(r->c, r->score, VtDirType, r->bsize);
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, r->bsize);
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;
}
}
int
vtfilelock(VtFile *r, int mode)
{
VtBlock *b;
if(mode == -1)
mode = r->mode;
b = fileloadblock(r, mode);
if(b == nil)
return -1;
/*
* The fact that we are holding b serves as the
* lock entitling us to write to r->b.
*/
assert(r->b == nil);
r->b = b;
b->pc = getcallerpc(&r);
return 0;
}
/*
* Lock two (usually sibling) VtFiles. This needs special care
* because the Entries for both vtFiles might be in the same block.
* We also try to lock blocks in left-to-right order within the tree.
*/
int
vtfilelock2(VtFile *r, VtFile *rr, int mode)
{
VtBlock *b, *bb;
if(rr == nil)
return vtfilelock(r, mode);
if(mode == -1)
mode = r->mode;
if(r->parent==rr->parent && r->offset/r->epb == rr->offset/rr->epb){
b = fileloadblock(r, mode);
if(b == nil)
return -1;
vtblockduplock(b);
bb = b;
}else if(r->parent==rr->parent || r->offset > rr->offset){
bb = fileloadblock(rr, mode);
b = fileloadblock(r, mode);
}else{
b = fileloadblock(r, mode);
bb = fileloadblock(rr, mode);
}
if(b == nil || bb == nil){
if(b)
vtblockput(b);
if(bb)
vtblockput(bb);
return -1;
}
/*
* The fact that we are holding b and bb serves
* as the lock entitling us to write to r->b and rr->b.
*/
r->b = b;
rr->b = bb;
b->pc = getcallerpc(&r);
bb->pc = getcallerpc(&r);
return 0;
}
void
vtfileunlock(VtFile *r)
{
VtBlock *b;
if(r->b == nil){
fprint(2, "vtfileunlock: already unlocked\n");
abort();
}
b = r->b;
r->b = nil;
vtblockput(b);
}
static VtBlock*
fileload(VtFile *r, VtEntry *e)
{
VtBlock *b;
assert(ISLOCKED(r));
b = r->b;
if(vtentryunpack(e, b->data, r->offset % r->epb) < 0)
return nil;
vtblockduplock(b);
return b;
}
static int
sizetodepth(uvlong s, int psize, int dsize)
{
int np;
int d;
/* determine pointer depth */
np = psize/VtScoreSize;
s = (s + dsize - 1)/dsize;
for(d = 0; s > 1; d++)
s = (s + np - 1)/np;
return d;
}
long
vtfileread(VtFile *f, void *data, long count, vlong offset)
{
int frag;
VtBlock *b;
VtEntry e;
assert(ISLOCKED(f));
vtfilegetentry(f, &e);
if(count == 0)
return 0;
if(count < 0 || offset < 0){
werrstr("vtfileread: bad offset or count");
return -1;
}
if(offset >= e.size)
return 0;
if(offset+count > e.size)
count = e.size - offset;
frag = offset % e.dsize;
if(frag+count > e.dsize)
count = e.dsize - frag;
b = vtfileblock(f, offset/e.dsize, VtOREAD);
if(b == nil)
return -1;
memmove(data, b->data+frag, count);
vtblockput(b);
return count;
}
static long
filewrite1(VtFile *f, void *data, long count, vlong offset)
{
int frag, m;
VtBlock *b;
VtEntry e;
vtfilegetentry(f, &e);
if(count < 0 || offset < 0){
werrstr("vtfilewrite: bad offset or count");
return -1;
}
frag = offset % e.dsize;
if(frag+count > e.dsize)
count = e.dsize - frag;
m = VtORDWR;
if(frag == 0 && count == e.dsize)
m = VtOWRITE;
b = vtfileblock(f, offset/e.dsize, m);
if(b == nil)
return -1;
memmove(b->data+frag, data, count);
if(m == VtOWRITE && frag+count < e.dsize)
memset(b->data+frag+count, 0, e.dsize-frag-count);
if(offset+count > e.size){
vtfilegetentry(f, &e);
e.size = offset+count;
vtfilesetentry(f, &e);
}
vtblockput(b);
return count;
}
long
vtfilewrite(VtFile *f, void *data, long count, vlong offset)
{
long tot, m;
assert(ISLOCKED(f));
tot = 0;
m = 0;
while(tot < count){
m = filewrite1(f, (char*)data+tot, count-tot, offset+tot);
if(m <= 0)
break;
tot += m;
}
if(tot==0)
return m;
return tot;
}
static int
flushblock(VtCache *c, VtBlock *bb, uchar score[VtScoreSize], int ppb, int epb,
int type)
{
u32int addr;
VtBlock *b;
VtEntry e;
int i;
addr = vtglobaltolocal(score);
if(addr == NilBlock)
return 0;
if(bb){
b = bb;
if(memcmp(b->score, score, VtScoreSize) != 0)
abort();
}else
if((b = vtcachelocal(c, addr, type)) == nil)
return -1;
switch(type){
case VtDataType:
break;
case VtDirType:
for(i=0; i<epb; i++){
if(vtentryunpack(&e, b->data, i) < 0)
goto Err;
if(!(e.flags&VtEntryActive))
continue;
if(flushblock(c, nil, e.score, e.psize/VtScoreSize, e.dsize/VtEntrySize,
e.type) < 0)
goto Err;
vtentrypack(&e, b->data, i);
}
break;
default: /* VtPointerTypeX */
for(i=0; i<ppb; i++){
if(flushblock(c, nil, b->data+VtScoreSize*i, ppb, epb, type-1) < 0)
goto Err;
}
break;
}
if(vtblockwrite(b) < 0)
goto Err;
memmove(score, b->score, VtScoreSize);
if(b != bb)
vtblockput(b);
return 0;
Err:
if(b != bb)
vtblockput(b);
return -1;
}
int
vtfileflush(VtFile *f)
{
int ret;
VtBlock *b;
VtEntry e;
assert(ISLOCKED(f));
b = fileload(f, &e);
if(!(e.flags&VtEntryLocal)){
vtblockput(b);
return 0;
}
ret = flushblock(f->c, nil, e.score, e.psize/VtScoreSize, e.dsize/VtEntrySize,
e.type);
if(ret < 0){
vtblockput(b);
return -1;
}
vtentrypack(&e, b->data, f->offset % f->epb);
vtblockput(b);
return 0;
}
int
vtfileflushbefore(VtFile *r, u64int offset)
{
VtBlock *b, *bb;
VtEntry e;
int i, base, depth, ppb, epb, doflush;
int index[VtPointerDepth+1], j, ret;
VtBlock *bi[VtPointerDepth+2];
uchar *score;
assert(ISLOCKED(r));
if(offset == 0)
return 0;
b = fileload(r, &e);
if(b == nil)
return -1;
/*
* compute path through tree for the last written byte and the next one.
*/
ret = -1;
memset(bi, 0, sizeof bi);
depth = DEPTH(e.type);
bi[depth+1] = b;
i = mkindices(&e, (offset-1)/e.dsize, index);
if(i < 0)
goto Err;
if(i > depth)
goto Err;
ppb = e.psize / VtScoreSize;
epb = e.dsize / VtEntrySize;
/*
* load the blocks along the last written byte
*/
index[depth] = r->offset % r->epb;
for(i=depth; i>=0; i--){
bb = blockwalk(r, b, index[i], r->c, VtORDWR, &e);
if(bb == nil)
goto Err;
bi[i] = bb;
b = bb;
}
ret = 0;
/*
* walk up the path from leaf to root, flushing anything that
* has been finished.
*/
base = e.type&~VtTypeDepthMask;
for(i=0; i<=depth; i++){
doflush = 0;
if(i == 0){
/* leaf: data or dir block */
if(offset%e.dsize == 0)
doflush = 1;
}else{
/*
* interior node: pointer blocks.
* specifically, b = bi[i] is a block whose index[i-1]'th entry
* points at bi[i-1].
*/
b = bi[i];
/*
* the index entries up to but not including index[i-1] point at
* finished blocks, so flush them for sure.
*/
for(j=0; j<index[i-1]; j++)
if(flushblock(r->c, nil, b->data+j*VtScoreSize, ppb, epb, base+i-1) < 0)
goto Err;
/*
* if index[i-1] is the last entry in the block and is global
* (i.e. the kid is flushed), then we can flush this block.
*/
if(j==ppb-1 && vtglobaltolocal(b->data+j*VtScoreSize)==NilBlock)
doflush = 1;
}
if(doflush){
if(i == depth)
score = e.score;
else
score = bi[i+1]->data+index[i]*VtScoreSize;
if(flushblock(r->c, bi[i], score, ppb, epb, base+i) < 0)
goto Err;
}
}
Err:
/* top: entry. do this always so that the score is up-to-date */
vtentrypack(&e, bi[depth+1]->data, index[depth]);
for(i=0; i<nelem(bi); i++)
if(bi[i])
vtblockput(bi[i]);
return ret;
}