blob: d5bb297bb5ac9550eb3f23b861d807240a84a432 [file] [log] [blame]
#include "stdinc.h"
#include "dat.h"
#include "fns.h"
#include "error.h"
#include "9.h"
static int sizeToDepth(uvlong s, int psize, int dsize);
static u32int tagGen(void);
static Block *sourceLoad(Source *r, Entry *e);
static int sourceShrinkDepth(Source*, Block*, Entry*, int);
static int sourceShrinkSize(Source*, Entry*, uvlong);
static int sourceGrowDepth(Source*, Block*, Entry*, int);
#define sourceIsLocked(r) ((r)->b != nil)
static Source *
sourceAlloc(Fs *fs, Block *b, Source *p, u32int offset, int mode, int issnapshot)
{
int epb;
u32int epoch;
char *pname = nil;
Source *r;
Entry e;
assert(p==nil || sourceIsLocked(p));
if(p == nil){
assert(offset == 0);
epb = 1;
}else
epb = p->dsize / VtEntrySize;
if(b->l.type != BtDir)
goto Bad;
/*
* a non-active entry is the only thing that
* can legitimately happen here. all the others
* get prints.
*/
if(!entryUnpack(&e, b->data, offset % epb)){
pname = sourceName(p);
consPrint("%s: %s %V: sourceAlloc: entryUnpack failed\n",
fs->name, pname, b->score);
goto Bad;
}
if(!(e.flags & VtEntryActive)){
pname = sourceName(p);
if(0) consPrint("%s: %s %V: sourceAlloc: not active\n",
fs->name, pname, e.score);
goto Bad;
}
if(e.psize < 256 || e.dsize < 256){
pname = sourceName(p);
consPrint("%s: %s %V: sourceAlloc: psize %ud or dsize %ud < 256\n",
fs->name, pname, e.score, e.psize, e.dsize);
goto Bad;
}
if(e.depth < sizeToDepth(e.size, e.psize, e.dsize)){
pname = sourceName(p);
consPrint("%s: %s %V: sourceAlloc: depth %ud size %llud "
"psize %ud dsize %ud\n", fs->name, pname,
e.score, e.depth, e.size, e.psize, e.dsize);
goto Bad;
}
if((e.flags & VtEntryLocal) && e.tag == 0){
pname = sourceName(p);
consPrint("%s: %s %V: sourceAlloc: flags %#ux tag %#ux\n",
fs->name, pname, e.score, e.flags, e.tag);
goto Bad;
}
if(e.dsize > fs->blockSize || e.psize > fs->blockSize){
pname = sourceName(p);
consPrint("%s: %s %V: sourceAlloc: psize %ud or dsize %ud "
"> blocksize %ud\n", fs->name, pname, e.score,
e.psize, e.dsize, fs->blockSize);
goto Bad;
}
epoch = b->l.epoch;
if(mode == OReadWrite){
if(e.snap != 0){
werrstr(ESnapRO);
return nil;
}
}else if(e.snap != 0){
if(e.snap < fs->elo){
werrstr(ESnapOld);
return nil;
}
if(e.snap >= fs->ehi)
goto Bad;
epoch = e.snap;
}
r = vtmallocz(sizeof(Source));
r->fs = fs;
r->mode = mode;
r->issnapshot = issnapshot;
r->dsize = e.dsize;
r->gen = e.gen;
r->dir = (e.flags & _VtEntryDir) != 0;
r->ref = 1;
r->parent = p;
if(p){
qlock(&p->lk);
assert(mode == OReadOnly || p->mode == OReadWrite);
p->ref++;
qunlock(&p->lk);
}
r->epoch = epoch;
// consPrint("sourceAlloc: have %V be.%d fse.%d %s\n", b->score,
// b->l.epoch, r->fs->ehi, mode == OReadWrite? "rw": "ro");
memmove(r->score, b->score, VtScoreSize);
r->scoreEpoch = b->l.epoch;
r->offset = offset;
r->epb = epb;
r->tag = b->l.tag;
// consPrint("%s: sourceAlloc: %p -> %V %d\n", r, r->score, r->offset);
return r;
Bad:
free(pname);
werrstr(EBadEntry);
return nil;
}
Source *
sourceRoot(Fs *fs, u32int addr, int mode)
{
Source *r;
Block *b;
b = cacheLocalData(fs->cache, addr, BtDir, RootTag, mode, 0);
if(b == nil)
return nil;
if(mode == OReadWrite && b->l.epoch != fs->ehi){
consPrint("sourceRoot: fs->ehi = %ud, b->l = %L\n",
fs->ehi, &b->l);
blockPut(b);
werrstr(EBadRoot);
return nil;
}
r = sourceAlloc(fs, b, nil, 0, mode, 0);
blockPut(b);
return r;
}
Source *
sourceOpen(Source *r, ulong offset, int mode, int issnapshot)
{
ulong bn;
Block *b;
assert(sourceIsLocked(r));
if(r->mode == OReadWrite)
assert(r->epoch == r->b->l.epoch);
if(!r->dir){
werrstr(ENotDir);
return nil;
}
bn = offset/(r->dsize/VtEntrySize);
b = sourceBlock(r, bn, mode);
if(b == nil)
return nil;
r = sourceAlloc(r->fs, b, r, offset, mode, issnapshot);
blockPut(b);
return r;
}
Source *
sourceCreate(Source *r, int dsize, int dir, u32int offset)
{
int i, epb, psize;
u32int bn, size;
Block *b;
Entry e;
Source *rr;
assert(sourceIsLocked(r));
if(!r->dir){
werrstr(ENotDir);
return nil;
}
epb = r->dsize/VtEntrySize;
psize = (dsize/VtScoreSize)*VtScoreSize;
size = sourceGetDirSize(r);
if(offset == 0){
/*
* look at a random block to see if we can find an empty entry
*/
offset = lnrand(size+1);
offset -= offset % epb;
}
/* try the given block and then try the last block */
for(;;){
bn = offset/epb;
b = sourceBlock(r, bn, OReadWrite);
if(b == nil)
return nil;
for(i=offset%r->epb; i<epb; i++){
entryUnpack(&e, b->data, i);
if((e.flags&VtEntryActive) == 0 && e.gen != ~0)
goto Found;
}
blockPut(b);
if(offset == size){
fprint(2, "sourceCreate: cannot happen\n");
werrstr("sourceCreate: cannot happen");
return nil;
}
offset = size;
}
Found:
/* found an entry - gen already set */
e.psize = psize;
e.dsize = dsize;
assert(psize && dsize);
e.flags = VtEntryActive;
if(dir)
e.flags |= _VtEntryDir;
e.depth = 0;
e.size = 0;
memmove(e.score, vtzeroscore, VtScoreSize);
e.tag = 0;
e.snap = 0;
e.archive = 0;
entryPack(&e, b->data, i);
blockDirty(b);
offset = bn*epb + i;
if(offset+1 > size){
if(!sourceSetDirSize(r, offset+1)){
blockPut(b);
return nil;
}
}
rr = sourceAlloc(r->fs, b, r, offset, OReadWrite, 0);
blockPut(b);
return rr;
}
static int
sourceKill(Source *r, int doremove)
{
Entry e;
Block *b;
u32int addr;
u32int tag;
int type;
assert(sourceIsLocked(r));
b = sourceLoad(r, &e);
if(b == nil)
return 0;
assert(b->l.epoch == r->fs->ehi);
if(doremove==0 && e.size == 0){
/* already truncated */
blockPut(b);
return 1;
}
/* remember info on link we are removing */
addr = globalToLocal(e.score);
type = entryType(&e);
tag = e.tag;
if(doremove){
if(e.gen != ~0)
e.gen++;
e.dsize = 0;
e.psize = 0;
e.flags = 0;
}else{
e.flags &= ~VtEntryLocal;
}
e.depth = 0;
e.size = 0;
e.tag = 0;
memmove(e.score, vtzeroscore, VtScoreSize);
entryPack(&e, b->data, r->offset % r->epb);
blockDirty(b);
if(addr != NilBlock)
blockRemoveLink(b, addr, type, tag, 1);
blockPut(b);
if(doremove){
sourceUnlock(r);
sourceClose(r);
}
return 1;
}
int
sourceRemove(Source *r)
{
return sourceKill(r, 1);
}
int
sourceTruncate(Source *r)
{
return sourceKill(r, 0);
}
uvlong
sourceGetSize(Source *r)
{
Entry e;
Block *b;
assert(sourceIsLocked(r));
b = sourceLoad(r, &e);
if(b == nil)
return 0;
blockPut(b);
return e.size;
}
static int
sourceShrinkSize(Source *r, Entry *e, uvlong size)
{
int i, type, ppb;
uvlong ptrsz;
u32int addr;
uchar score[VtScoreSize];
Block *b;
type = entryType(e);
b = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
if(b == nil)
return 0;
ptrsz = e->dsize;
ppb = e->psize/VtScoreSize;
for(i=0; i+1<e->depth; i++)
ptrsz *= ppb;
while(type&BtLevelMask){
if(b->addr == NilBlock || b->l.epoch != r->fs->ehi){
/* not worth copying the block just so we can zero some of it */
blockPut(b);
return 0;
}
/*
* 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++){
addr = globalToLocal(b->data+i*VtScoreSize);
memmove(b->data+i*VtScoreSize, vtzeroscore, VtScoreSize);
blockDirty(b);
if(addr != NilBlock)
blockRemoveLink(b, addr, type-1, e->tag, 1);
}
/* recurse (go around again) on the partially necessary block */
i = size/ptrsz;
size = size%ptrsz;
if(size == 0){
blockPut(b);
return 1;
}
ptrsz /= ppb;
type--;
memmove(score, b->data+i*VtScoreSize, VtScoreSize);
blockPut(b);
b = cacheGlobal(r->fs->cache, score, type, e->tag, OReadWrite);
if(b == nil)
return 0;
}
if(b->addr == NilBlock || b->l.epoch != r->fs->ehi){
blockPut(b);
return 0;
}
/*
* No one ever truncates BtDir blocks.
*/
if(type == BtData && e->dsize > size){
memset(b->data+size, 0, e->dsize-size);
blockDirty(b);
}
blockPut(b);
return 1;
}
int
sourceSetSize(Source *r, uvlong size)
{
int depth;
Entry e;
Block *b;
assert(sourceIsLocked(r));
if(size == 0)
return sourceTruncate(r);
if(size > VtMaxFileSize || size > ((uvlong)MaxBlock)*r->dsize){
werrstr(ETooBig);
return 0;
}
b = sourceLoad(r, &e);
if(b == nil)
return 0;
/* quick out */
if(e.size == size){
blockPut(b);
return 1;
}
depth = sizeToDepth(size, e.psize, e.dsize);
if(depth < e.depth){
if(!sourceShrinkDepth(r, b, &e, depth)){
blockPut(b);
return 0;
}
}else if(depth > e.depth){
if(!sourceGrowDepth(r, b, &e, depth)){
blockPut(b);
return 0;
}
}
if(size < e.size)
sourceShrinkSize(r, &e, size);
e.size = size;
entryPack(&e, b->data, r->offset % r->epb);
blockDirty(b);
blockPut(b);
return 1;
}
int
sourceSetDirSize(Source *r, ulong ds)
{
uvlong size;
int epb;
assert(sourceIsLocked(r));
epb = r->dsize/VtEntrySize;
size = (uvlong)r->dsize*(ds/epb);
size += VtEntrySize*(ds%epb);
return sourceSetSize(r, size);
}
ulong
sourceGetDirSize(Source *r)
{
ulong ds;
uvlong size;
int epb;
assert(sourceIsLocked(r));
epb = r->dsize/VtEntrySize;
size = sourceGetSize(r);
ds = epb*(size/r->dsize);
ds += (size%r->dsize)/VtEntrySize;
return ds;
}
int
sourceGetEntry(Source *r, Entry *e)
{
Block *b;
assert(sourceIsLocked(r));
b = sourceLoad(r, e);
if(b == nil)
return 0;
blockPut(b);
return 1;
}
/*
* Must be careful with this. Doesn't record
* dependencies, so don't introduce any!
*/
int
sourceSetEntry(Source *r, Entry *e)
{
Block *b;
Entry oe;
assert(sourceIsLocked(r));
b = sourceLoad(r, &oe);
if(b == nil)
return 0;
entryPack(e, b->data, r->offset%r->epb);
blockDirty(b);
blockPut(b);
return 1;
}
static Block *
blockWalk(Block *p, int index, int mode, Fs *fs, Entry *e)
{
Block *b;
Cache *c;
u32int addr;
int type;
uchar oscore[VtScoreSize], score[VtScoreSize];
Entry oe;
c = fs->cache;
if((p->l.type & BtLevelMask) == 0){
assert(p->l.type == BtDir);
type = entryType(e);
b = cacheGlobal(c, e->score, type, e->tag, mode);
}else{
type = p->l.type - 1;
b = cacheGlobal(c, p->data + index*VtScoreSize, type, e->tag, mode);
}
if(b)
b->pc = getcallerpc(&p);
if(b == nil || mode == OReadOnly)
return b;
if(p->l.epoch != fs->ehi){
fprint(2, "blockWalk: parent not writable\n");
abort();
}
if(b->l.epoch == fs->ehi)
return b;
oe = *e;
/*
* Copy on write.
*/
if(e->tag == 0){
assert(p->l.type == BtDir);
e->tag = tagGen();
e->flags |= VtEntryLocal;
}
addr = b->addr;
b = blockCopy(b, e->tag, fs->ehi, fs->elo);
if(b == nil)
return nil;
b->pc = getcallerpc(&p);
assert(b->l.epoch == fs->ehi);
blockDirty(b);
memmove(score, b->score, VtScoreSize);
if(p->l.type == BtDir){
memmove(e->score, b->score, VtScoreSize);
entryPack(e, p->data, index);
blockDependency(p, b, index, nil, &oe);
}else{
memmove(oscore, p->data+index*VtScoreSize, VtScoreSize);
memmove(p->data+index*VtScoreSize, b->score, VtScoreSize);
blockDependency(p, b, index, oscore, nil);
}
blockDirty(p);
if(addr != NilBlock)
blockRemoveLink(p, addr, type, e->tag, 0);
return b;
}
/*
* Change the depth of the source r.
* The entry e for r is contained in block p.
*/
static int
sourceGrowDepth(Source *r, Block *p, Entry *e, int depth)
{
Block *b, *bb;
u32int tag;
int type;
Entry oe;
assert(sourceIsLocked(r));
assert(depth <= VtPointerDepth);
type = entryType(e);
b = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
if(b == nil)
return 0;
tag = e->tag;
if(tag == 0)
tag = tagGen();
oe = *e;
/*
* Keep adding layers until we get to the right depth
* or an error occurs.
*/
while(e->depth < depth){
bb = cacheAllocBlock(r->fs->cache, type+1, tag, r->fs->ehi, r->fs->elo);
if(bb == nil)
break;
//fprint(2, "alloc %lux grow %V\n", bb->addr, b->score);
memmove(bb->data, b->score, VtScoreSize);
memmove(e->score, bb->score, VtScoreSize);
e->depth++;
type++;
e->tag = tag;
e->flags |= VtEntryLocal;
blockDependency(bb, b, 0, vtzeroscore, nil);
blockPut(b);
b = bb;
blockDirty(b);
}
entryPack(e, p->data, r->offset % r->epb);
blockDependency(p, b, r->offset % r->epb, nil, &oe);
blockPut(b);
blockDirty(p);
return e->depth == depth;
}
static int
sourceShrinkDepth(Source *r, Block *p, Entry *e, int depth)
{
Block *b, *nb, *ob, *rb;
u32int tag;
int type, d;
Entry oe;
assert(sourceIsLocked(r));
assert(depth <= VtPointerDepth);
type = entryType(e);
rb = cacheGlobal(r->fs->cache, e->score, type, e->tag, OReadWrite);
if(rb == nil)
return 0;
tag = e->tag;
if(tag == 0)
tag = tagGen();
/*
* Walk down to the new root block.
* We may stop early, but something is better than nothing.
*/
oe = *e;
ob = nil;
b = rb;
/* BUG: explain type++. i think it is a real bug */
for(d=e->depth; d > depth; d--, type++){
nb = cacheGlobal(r->fs->cache, b->data, type-1, tag, OReadWrite);
if(nb == nil)
break;
if(ob!=nil && ob!=rb)
blockPut(ob);
ob = b;
b = nb;
}
if(b == rb){
blockPut(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) */
e->depth = d;
/* might have been local and now global; reverse cannot happen */
if(globalToLocal(b->score) == NilBlock)
e->flags &= ~VtEntryLocal;
memmove(e->score, b->score, VtScoreSize);
entryPack(e, p->data, r->offset % r->epb);
blockDependency(p, b, r->offset % r->epb, nil, &oe);
blockDirty(p);
/* (ii) */
memmove(ob->data, vtzeroscore, VtScoreSize);
blockDependency(ob, p, 0, b->score, nil);
blockDirty(ob);
/* (iii) */
if(rb->addr != NilBlock)
blockRemoveLink(p, rb->addr, rb->l.type, rb->l.tag, 1);
blockPut(rb);
if(ob!=nil && ob!=rb)
blockPut(ob);
blockPut(b);
return d == depth;
}
/*
* Normally we return the block at the given number.
* If early is set, we stop earlier in the tree. Setting early
* to 1 gives us the block that contains the pointer to bn.
*/
Block *
_sourceBlock(Source *r, ulong bn, int mode, int early, ulong tag)
{
Block *b, *bb;
int index[VtPointerDepth+1];
Entry e;
int i, np;
int m;
assert(sourceIsLocked(r));
assert(bn != NilBlock);
/* mode for intermediate block */
m = mode;
if(m == OOverWrite)
m = OReadWrite;
b = sourceLoad(r, &e);
if(b == nil)
return nil;
if(r->issnapshot && (e.flags & VtEntryNoArchive)){
blockPut(b);
werrstr(ENotArchived);
return nil;
}
if(tag){
if(e.tag == 0)
e.tag = tag;
else if(e.tag != tag){
fprint(2, "tag mismatch\n");
werrstr("tag mismatch");
goto Err;
}
}
np = e.psize/VtScoreSize;
memset(index, 0, sizeof(index));
for(i=0; bn > 0; i++){
if(i >= VtPointerDepth){
werrstr(EBadAddr);
goto Err;
}
index[i] = bn % np;
bn /= np;
}
if(i > e.depth){
if(mode == OReadOnly){
werrstr(EBadAddr);
goto Err;
}
if(!sourceGrowDepth(r, b, &e, i))
goto Err;
}
index[e.depth] = r->offset % r->epb;
for(i=e.depth; i>=early; i--){
bb = blockWalk(b, index[i], m, r->fs, &e);
if(bb == nil)
goto Err;
blockPut(b);
b = bb;
}
b->pc = getcallerpc(&r);
return b;
Err:
blockPut(b);
return nil;
}
Block*
sourceBlock(Source *r, ulong bn, int mode)
{
Block *b;
b = _sourceBlock(r, bn, mode, 0, 0);
if(b)
b->pc = getcallerpc(&r);
return b;
}
void
sourceClose(Source *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)
sourceClose(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 sources (OReadWrite) and sources for the
* snapshot file system (OReadOnly).
*/
static Block*
sourceLoadBlock(Source *r, int mode)
{
u32int addr;
Block *b;
char e[ERRMAX];
switch(r->mode){
default:
assert(0);
case OReadWrite:
assert(r->mode == OReadWrite);
/*
* This needn't be true -- we might bump the low epoch
* to reclaim some old blocks, but since this score is
* OReadWrite, the blocks must all still be open, so none
* are reclaimed. Thus it's okay that the epoch is so low.
* Proceed.
assert(r->epoch >= r->fs->elo);
*/
if(r->epoch == r->fs->ehi){
b = cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, OReadWrite);
if(b == nil)
return nil;
assert(r->epoch == b->l.epoch);
return b;
}
assert(r->parent != nil);
if(!sourceLock(r->parent, OReadWrite))
return nil;
b = sourceBlock(r->parent, r->offset/r->epb, OReadWrite);
sourceUnlock(r->parent);
if(b == nil)
return nil;
assert(b->l.epoch == r->fs->ehi);
// fprint(2, "sourceLoadBlock %p %V => %V\n", r, r->score, b->score);
memmove(r->score, b->score, VtScoreSize);
r->scoreEpoch = b->l.epoch;
r->tag = b->l.tag;
r->epoch = r->fs->ehi;
return b;
case OReadOnly:
addr = globalToLocal(r->score);
if(addr == NilBlock)
return cacheGlobal(r->fs->cache, r->score, BtDir, r->tag, mode);
b = cacheLocalData(r->fs->cache, addr, BtDir, r->tag, mode, r->scoreEpoch);
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 OReadWrite case
* above because blocks in the current epoch don't get
* reclaimed. The fact that we're OReadOnly 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(!sourceLock(r->parent, OReadOnly))
return nil;
b = sourceBlock(r->parent, r->offset/r->epb, OReadOnly);
sourceUnlock(r->parent);
if(b){
fprint(2, "sourceAlloc: lost %V found %V\n",
r->score, b->score);
memmove(r->score, b->score, VtScoreSize);
r->scoreEpoch = b->l.epoch;
return b;
}
}
return nil;
}
}
int
sourceLock(Source *r, int mode)
{
Block *b;
if(mode == -1)
mode = r->mode;
b = sourceLoadBlock(r, mode);
if(b == nil)
return 0;
/*
* 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;
if(r->mode == OReadWrite)
assert(r->epoch == r->b->l.epoch);
return 1;
}
/*
* Lock two (usually sibling) sources. This needs special care
* because the Entries for both sources might be in the same block.
* We also try to lock blocks in left-to-right order within the tree.
*/
int
sourceLock2(Source *r, Source *rr, int mode)
{
Block *b, *bb;
if(rr == nil)
return sourceLock(r, mode);
if(mode == -1)
mode = r->mode;
if(r->parent==rr->parent && r->offset/r->epb == rr->offset/rr->epb){
b = sourceLoadBlock(r, mode);
if(b == nil)
return 0;
if(memcmp(r->score, rr->score, VtScoreSize) != 0){
memmove(rr->score, b->score, VtScoreSize);
rr->scoreEpoch = b->l.epoch;
rr->tag = b->l.tag;
rr->epoch = rr->fs->ehi;
}
blockDupLock(b);
bb = b;
}else if(r->parent==rr->parent || r->offset > rr->offset){
bb = sourceLoadBlock(rr, mode);
b = sourceLoadBlock(r, mode);
}else{
b = sourceLoadBlock(r, mode);
bb = sourceLoadBlock(rr, mode);
}
if(b == nil || bb == nil){
if(b)
blockPut(b);
if(bb)
blockPut(bb);
return 0;
}
/*
* 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;
return 1;
}
void
sourceUnlock(Source *r)
{
Block *b;
if(r->b == nil){
fprint(2, "sourceUnlock: already unlocked\n");
abort();
}
b = r->b;
r->b = nil;
blockPut(b);
}
static Block*
sourceLoad(Source *r, Entry *e)
{
Block *b;
assert(sourceIsLocked(r));
b = r->b;
if(!entryUnpack(e, b->data, r->offset % r->epb))
return nil;
if(e->gen != r->gen){
werrstr(ERemoved);
return nil;
}
blockDupLock(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;
}
static u32int
tagGen(void)
{
u32int tag;
for(;;){
tag = lrand();
if(tag >= UserTag)
break;
}
return tag;
}
char *
sourceName(Source *s)
{
return fileName(s->file);
}