blob: fc34d6886d088e7a66cf8aab7e2b621fefc99519 [file] [log] [blame]
#include "stdinc.h"
#include "vac.h"
#include "dat.h"
#include "fns.h"
typedef struct Label Label;
enum {
BadHeap = ~0,
};
/*
* the plan is to store data to the cache in c->size blocks
* with the block zero extended to fill it out. When writing to
* venti, the block will be zero truncated. The walker will also check
* that the block fits within psize or dsize as the case may be.
*/
struct Cache
{
VtLock *lk;
VtSession *z;
u32int now; /* ticks for usage timestamps */
int size; /* max. size of any block; allocated to each block */
Lump **heads; /* hash table for finding address */
int nheap; /* number of available victims */
Lump **heap; /* heap for locating victims */
long nblocks; /* number of blocks allocated */
Lump *blocks; /* array of block descriptors */
u8int *mem; /* memory for all block descriptors */
Lump *free; /* free list of lumps */
long hashSize;
};
/*
* the tag for a block is hash(index, parent tag)
*/
struct Label {
uchar gen[4];
uchar state;
uchar type; /* top bit indicates it is part of a directory */
uchar tag[4]; /* tag of file it is in */
};
static char ENoDir[] = "directory entry is not allocated";
static void fixHeap(int si, Lump *b);
static int upHeap(int i, Lump *b);
static int downHeap(int i, Lump *b);
static char *lumpState(int);
static void lumpSetState(Lump *u, int state);
Cache *
cacheAlloc(VtSession *z, int blockSize, long nblocks)
{
int i;
Cache *c;
Lump *b;
c = vtMemAllocZ(sizeof(Cache));
c->lk = vtLockAlloc();
c->z = z;
c->size = blockSize;
c->nblocks = nblocks;
c->hashSize = nblocks;
c->heads = vtMemAllocZ(c->hashSize*sizeof(Lump*));
c->heap = vtMemAllocZ(nblocks*sizeof(Lump*));
c->blocks = vtMemAllocZ(nblocks*sizeof(Lump));
c->mem = vtMemAllocZ(nblocks * blockSize);
for(i = 0; i < nblocks; i++){
b = &c->blocks[i];
b->lk = vtLockAlloc();
b->c = c;
b->data = &c->mem[i * blockSize];
b->addr = i+1;
b->state = LumpFree;
b->heap = BadHeap;
b->next = c->free;
c->free = b;
}
c->nheap = 0;
return c;
}
long
cacheGetSize(Cache *c)
{
return c->nblocks;
}
int
cacheGetBlockSize(Cache *c)
{
return c->size;
}
int
cacheSetSize(Cache *c, long nblocks)
{
USED(c);
USED(nblocks);
return 0;
}
void
cacheFree(Cache *c)
{
int i;
for(i = 0; i < c->nblocks; i++){
assert(c->blocks[i].ref == 0);
vtLockFree(c->blocks[i].lk);
}
vtMemFree(c->heads);
vtMemFree(c->blocks);
vtMemFree(c->mem);
vtMemFree(c);
}
static u32int
hash(Cache *c, uchar score[VtScoreSize], int type)
{
u32int h;
uchar *p = score + VtScoreSize-4;
h = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
h += type;
return h % c->hashSize;
}
static void
findLump(Cache *c, Lump *bb)
{
Lump *b, *last;
int h;
last = nil;
h = hash(c, bb->score, bb->type);
for(b = c->heads[h]; b != nil; b = b->next){
if(last != b->prev)
vtFatal("bad prev link");
if(b == bb)
return;
last = b;
}
vtFatal("block missing from hash table");
}
void
cacheCheck(Cache *c)
{
u32int size, now;
int i, k, refed, free;
static uchar zero[VtScoreSize];
Lump *p;
size = c->size;
now = c->now;
free = 0;
for(p=c->free; p; p=p->next)
free++;
for(i = 0; i < c->nheap; i++){
if(c->heap[i]->heap != i)
vtFatal("mis-heaped at %d: %d", i, c->heap[i]->heap);
if(i > 0 && c->heap[(i - 1) >> 1]->used2 - now > c->heap[i]->used2 - now)
vtFatal("bad heap ordering");
k = (i << 1) + 1;
if(k < c->nheap && c->heap[i]->used2 - now > c->heap[k]->used2 - now)
vtFatal("bad heap ordering");
k++;
if(k < c->nheap && c->heap[i]->used2 - now > c->heap[k]->used2 - now)
vtFatal("bad heap ordering");
}
refed = 0;
for(i = 0; i < c->nblocks; i++){
if(c->blocks[i].data != &c->mem[i * size])
vtFatal("mis-blocked at %d", i);
if(c->blocks[i].ref && c->blocks[i].heap == BadHeap){
refed++;
}
if(memcmp(zero, c->blocks[i].score, VtScoreSize))
findLump(c, &c->blocks[i]);
}
if(refed > 0)fprint(2, "cacheCheck: nheap %d refed %d free %d\n", c->nheap, refed, free);
assert(c->nheap + refed + free == c->nblocks);
refed = 0;
for(i = 0; i < c->nblocks; i++){
if(c->blocks[i].ref) {
if(1)fprint(2, "%d %V %d %s\n", c->blocks[i].type, c->blocks[i].score, c->blocks[i].ref, lumpState(c->blocks[i].state));
refed++;
}
}
if(refed > 0)fprint(2, "cacheCheck: in used %d\n", refed);
}
/*
* delete an arbitrary block from the heap
*/
static void
delHeap(Lump *db)
{
fixHeap(db->heap, db->c->heap[--db->c->nheap]);
db->heap = BadHeap;
}
static void
fixHeap(int si, Lump *b)
{
int i;
i = upHeap(si, b);
if(i == si)
downHeap(i, b);
}
static int
upHeap(int i, Lump *b)
{
Lump *bb;
u32int now;
int p;
Cache *c;
c = b->c;
now = c->now;
for(; i != 0; i = p){
p = (i - 1) >> 1;
bb = c->heap[p];
if(b->used2 - now >= bb->used2 - now)
break;
c->heap[i] = bb;
bb->heap = i;
}
c->heap[i] = b;
b->heap = i;
return i;
}
static int
downHeap(int i, Lump *b)
{
Lump *bb;
u32int now;
int k;
Cache *c;
c = b->c;
now = c->now;
for(; ; i = k){
k = (i << 1) + 1;
if(k >= c->nheap)
break;
if(k + 1 < c->nheap && c->heap[k]->used2 - now > c->heap[k + 1]->used2 - now)
k++;
bb = c->heap[k];
if(b->used2 - now <= bb->used2 - now)
break;
c->heap[i] = bb;
bb->heap = i;
}
c->heap[i] = b;
b->heap = i;
return i;
}
/* called with c->lk held */
Lump *
cacheBumpLump(Cache *c)
{
Lump *b;
/*
* missed: locate the block with the oldest second to last use.
* remove it from the heap, and fix up the heap.
*/
if(c->free) {
b = c->free;
c->free = b->next;
} else {
for(;;){
if(c->nheap == 0) {
cacheCheck(c);
assert(0);
return nil;
}
b = c->heap[0];
delHeap(b);
if(b->ref == 0)
break;
}
/*
* unchain the block from hash chain
*/
if(b->prev == nil)
c->heads[hash(c, b->score, b->type)] = b->next;
else
b->prev->next = b->next;
if(b->next != nil)
b->next->prev = b->prev;
}
/*
* the new block has no last use, so assume it happens sometime in the middle
*/
b->used = (b->used2 + c->now) / 2;
b->asize = 0;
return b;
}
Lump *
cacheAllocLump(Cache *c, int type, int size, int dir)
{
Lump *b;
ulong h;
assert(size <= c->size);
again:
vtLock(c->lk);
b = cacheBumpLump(c);
if(b == nil) {
vtUnlock(c->lk);
fprint(2, "cache is full\n");
/* XXX should be better */
sleep(100);
goto again;
}
vtLock(b->lk);
assert(b->ref == 0);
b->ref++;
b->used2 = b->used;
b->used = c->now++;
/* convert addr into score */
memset(b->score, 0, VtScoreSize-4);
b->score[VtScoreSize-4] = b->addr>>24;
b->score[VtScoreSize-3] = b->addr>>16;
b->score[VtScoreSize-2] = b->addr>>8;
b->score[VtScoreSize-1] = b->addr;
b->dir = dir;
b->type = type;
b->gen = 0;
b->asize = size;
b->state = LumpFree;
h = hash(c, b->score, b->type);
/* chain onto correct hash */
b->next = c->heads[h];
c->heads[h] = b;
if(b->next != nil)
b->next->prev = b;
b->prev = nil;
vtUnlock(c->lk);
vtZeroExtend(type, b->data, 0, size);
lumpSetState(b, LumpActive);
return b;
}
int
scoreIsLocal(uchar score[VtScoreSize])
{
static uchar zero[VtScoreSize];
return memcmp(score, zero, VtScoreSize-4) == 0;
}
Lump *
cacheGetLump(Cache *c, uchar score[VtScoreSize], int type, int size)
{
Lump *b;
ulong h;
int n;
static uchar zero[VtScoreSize];
assert(size <= c->size);
h = hash(c, score, type);
again:
/*
* look for the block in the cache
*/
vtLock(c->lk);
for(b = c->heads[h]; b != nil; b = b->next){
if(memcmp(b->score, score, VtScoreSize) == 0 && b->type == type)
goto found;
}
/* should not be looking for a temp block */
if(scoreIsLocal(score)) {
if(memcmp(score, zero, VtScoreSize) == 0)
vtSetError("looking for zero score");
else
vtSetError("missing local block");
vtUnlock(c->lk);
return nil;
}
b = cacheBumpLump(c);
if(b == nil) {
vtUnlock(c->lk);
sleep(100);
goto again;
}
/* chain onto correct hash */
b->next = c->heads[h];
c->heads[h] = b;
if(b->next != nil)
b->next->prev = b;
b->prev = nil;
memmove(b->score, score, VtScoreSize);
b->type = type;
b->state = LumpFree;
found:
b->ref++;
b->used2 = b->used;
b->used = c->now++;
if(b->heap != BadHeap)
fixHeap(b->heap, b);
vtUnlock(c->lk);
vtLock(b->lk);
if(b->state != LumpFree)
return b;
n = vtRead(c->z, score, type, b->data, size);
if(n < 0) {
lumpDecRef(b, 1);
return nil;
}
if(!vtSha1Check(score, b->data, n)) {
vtSetError("vtSha1Check failed");
lumpDecRef(b, 1);
return nil;
}
vtZeroExtend(type, b->data, n, size);
b->asize = size;
lumpSetState(b, LumpVenti);
return b;
}
static char *
lumpState(int state)
{
switch(state) {
default:
return "Unknown!!";
case LumpFree:
return "Free";
case LumpActive:
return "Active";
case LumpSnap:
return "Snap";
case LumpZombie:
return "Zombie";
case LumpVenti:
return "Venti";
}
}
static void
lumpSetState(Lump *u, int state)
{
// if(u->state != LumpFree)
// fprint(2, "%V: %s -> %s\n", u->score, lumpState(u->state), lumpState(state));
u->state = state;
}
int
lumpGetScore(Lump *u, int offset, uchar score[VtScoreSize])
{
uchar *sp;
VtRoot root;
VtEntry dir;
vtLock(u->lk);
switch(u->type) {
default:
vtSetError("bad type");
goto Err;
case VtPointerType0:
case VtPointerType1:
case VtPointerType2:
case VtPointerType3:
case VtPointerType4:
case VtPointerType5:
case VtPointerType6:
if((offset+1)*VtScoreSize > u->asize)
sp = nil;
else
sp = u->data + offset*VtScoreSize;
break;
case VtRootType:
if(u->asize < VtRootSize) {
vtSetError("runt root block");
goto Err;
}
if(!vtRootUnpack(&root, u->data))
goto Err;
sp = root.score;
break;
case VtDirType:
if((offset+1)*VtEntrySize > u->asize) {
vtSetError(ENoDir);
goto Err;
}
if(!vtEntryUnpack(&dir, u->data, offset))
goto Err;
if(!dir.flags & VtEntryActive) {
vtSetError(ENoDir);
goto Err;
}
sp = dir.score;
break;
}
if(sp == nil)
memmove(score, vtZeroScore, VtScoreSize);
else
memmove(score, sp, VtScoreSize);
vtUnlock(u->lk);
return !scoreIsLocal(score);
Err:
vtUnlock(u->lk);
return 0;
}
Lump *
lumpWalk(Lump *u, int offset, int type, int size, int readOnly, int lock)
{
Lump *v, *vv;
Cache *c;
uchar score[VtScoreSize], *sp;
VtRoot root;
VtEntry dir;
int split, isdir;
c = u->c;
vtLock(u->lk);
Again:
v = nil;
vv = nil;
isdir = u->dir;
switch(u->type) {
default:
vtSetError("bad type");
goto Err;
case VtPointerType0:
case VtPointerType1:
case VtPointerType2:
case VtPointerType3:
case VtPointerType4:
case VtPointerType5:
case VtPointerType6:
if((offset+1)*VtScoreSize > u->asize)
sp = nil;
else
sp = u->data + offset*VtScoreSize;
break;
case VtRootType:
if(u->asize < VtRootSize) {
vtSetError("runt root block");
goto Err;
}
if(!vtRootUnpack(&root, u->data))
goto Err;
sp = root.score;
break;
case VtDirType:
if((offset+1)*VtEntrySize > u->asize) {
vtSetError(ENoDir);
goto Err;
}
if(!vtEntryUnpack(&dir, u->data, offset))
goto Err;
if(!(dir.flags & VtEntryActive)) {
vtSetError(ENoDir);
goto Err;
}
isdir = (dir.flags & VtEntryDir) != 0;
// sp = dir.score;
sp = u->data + offset*VtEntrySize + 20;
break;
}
if(sp == nil)
memmove(score, vtZeroScore, VtScoreSize);
else
memmove(score, sp, VtScoreSize);
vtUnlock(u->lk);
if(0)fprint(2, "lumpWalk: %V:%s %d:%d-> %V:%d\n", u->score, lumpState(u->state), u->type, offset, score, type);
v = cacheGetLump(c, score, type, size);
if(v == nil)
return nil;
split = 1;
if(readOnly)
split = 0;
switch(v->state) {
default:
assert(0);
case LumpFree:
fprint(2, "block is free %V!\n", v->score);
vtSetError("phase error");
goto Err2;
case LumpActive:
if(v->gen < u->gen) {
print("LumpActive gen\n");
lumpSetState(v, LumpSnap);
v->gen = u->gen;
} else
split = 0;
break;
case LumpSnap:
case LumpVenti:
break;
}
/* easy case */
if(!split) {
if(!lock)
vtUnlock(v->lk);
return v;
}
if(sp == nil) {
vtSetError("bad offset");
goto Err2;
}
vv = cacheAllocLump(c, v->type, size, isdir);
/* vv is locked */
vv->gen = u->gen;
memmove(vv->data, v->data, v->asize);
if(0)fprint(2, "split %V into %V\n", v->score, vv->score);
lumpDecRef(v, 1);
v = nil;
vtLock(u->lk);
if(u->state != LumpActive) {
vtSetError("bad parent state: can not happen");
goto Err;
}
/* check that nothing changed underfoot */
if(memcmp(sp, score, VtScoreSize) != 0) {
lumpDecRef(vv, 1);
fprint(2, "lumpWalk: parent changed under foot\n");
goto Again;
}
/* XXX - hold Active blocks up - will go eventually */
lumpIncRef(vv);
/* change the parent */
memmove(sp, vv->score, VtScoreSize);
vtUnlock(u->lk);
if(!lock)
vtUnlock(vv->lk);
return vv;
Err:
vtUnlock(u->lk);
lumpDecRef(v, 0);
lumpDecRef(vv, 1);
return nil;
Err2:
lumpDecRef(v, 1);
return nil;
}
void
lumpFreeEntry(Lump *u, int entry)
{
uchar score[VtScoreSize];
int type;
ulong gen;
VtEntry dir;
Cache *c;
c = u->c;
vtLock(u->lk);
if(u->state == LumpVenti)
goto Exit;
switch(u->type) {
default:
fprint(2, "freeing bad lump type: %d\n", u->type);
return;
case VtPointerType0:
if((entry+1)*VtScoreSize > u->asize)
goto Exit;
memmove(score, u->data + entry*VtScoreSize, VtScoreSize);
memmove(u->data + entry*VtScoreSize, vtZeroScore, VtScoreSize);
type = u->dir?VtDirType:VtDataType;
break;
case VtPointerType1:
case VtPointerType2:
case VtPointerType3:
case VtPointerType4:
case VtPointerType5:
case VtPointerType6:
if((entry+1)*VtScoreSize > u->asize)
goto Exit;
memmove(score, u->data + entry*VtScoreSize, VtScoreSize);
memmove(u->data + entry*VtScoreSize, vtZeroScore, VtScoreSize);
type = u->type-1;
break;
case VtDirType:
if((entry+1)*VtEntrySize > u->asize)
goto Exit;
if(!vtEntryUnpack(&dir, u->data, entry))
goto Exit;
if(!dir.flags & VtEntryActive)
goto Exit;
gen = dir.gen;
if(gen != ~0)
gen++;
if(dir.depth == 0)
type = (dir.flags&VtEntryDir)?VtDirType:VtDataType;
else
type = VtPointerType0 + dir.depth - 1;
memmove(score, dir.score, VtScoreSize);
memset(&dir, 0, sizeof(dir));
dir.gen = gen;
vtEntryPack(&dir, u->data, entry);
break;
case VtDataType:
type = VtErrType;
break;
}
vtUnlock(u->lk);
if(type == VtErrType || !scoreIsLocal(score))
return;
u = cacheGetLump(c, score, type, c->size);
if(u == nil)
return;
lumpDecRef(u, 1);
/* XXX remove extra reference */
lumpDecRef(u, 0);
return;
Exit:
vtUnlock(u->lk);
return;
}
void
lumpCleanup(Lump *u)
{
int i, n;
switch(u->type) {
default:
return;
case VtPointerType0:
case VtPointerType1:
case VtPointerType2:
case VtPointerType3:
case VtPointerType4:
case VtPointerType5:
case VtPointerType6:
n = u->asize/VtScoreSize;
break;
case VtDirType:
n = u->asize/VtEntrySize;
break;
}
for(i=0; i<n; i++)
lumpFreeEntry(u, i);
}
void
lumpDecRef(Lump *b, int unlock)
{
int i;
Cache *c;
if(b == nil)
return;
if(unlock)
vtUnlock(b->lk);
c = b->c;
vtLock(c->lk);
if(--b->ref > 0) {
vtUnlock(c->lk);
return;
}
assert(b->ref == 0);
switch(b->state) {
default:
fprint(2, "bad state: %s\n", lumpState(b->state));
assert(0);
case LumpActive:
/* hack - but will do for now */
b->ref++;
vtUnlock(c->lk);
lumpCleanup(b);
vtLock(c->lk);
b->ref--;
lumpSetState(b, LumpFree);
break;
case LumpZombie:
lumpSetState(b, LumpFree);
break;
case LumpFree:
case LumpVenti:
break;
}
/*
* reinsert in the free heap
*/
if(b->heap == BadHeap) {
i = upHeap(c->nheap++, b);
c->heap[i] = b;
b->heap = i;
}
vtUnlock(c->lk);
}
Lump *
lumpIncRef(Lump *b)
{
Cache *c;
c = b->c;
vtLock(c->lk);
assert(b->ref > 0);
b->ref++;
vtUnlock(c->lk);
return b;
}