From 056fe1ba7fa0b70f871dfb9005b24eb8e4cc230b Mon Sep 17 00:00:00 2001 From: rsc Date: Sun, 23 Nov 2003 18:19:58 +0000 Subject: new venti library. --- src/libventi/cache.c | 560 ++++++++++++++++++++ src/libventi/client.c | 151 ++++++ src/libventi/conn.c | 36 ++ src/libventi/cvt.h | 15 + src/libventi/debug.c | 17 + src/libventi/dial.c | 21 + src/libventi/dtype.c | 78 +++ src/libventi/entry.c | 85 ++++ src/libventi/fcall.c | 230 +++++++++ src/libventi/fcallfmt.c | 55 ++ src/libventi/file.c | 1264 ++++++++++++++++++++++++++++++++++++++++++++++ src/libventi/hangup.c | 22 + src/libventi/mem.c | 87 ++++ src/libventi/mkfile | 42 ++ src/libventi/packet.c | 941 ++++++++++++++++++++++++++++++++++ src/libventi/queue.c | 103 ++++ src/libventi/queue.h | 6 + src/libventi/root.c | 67 +++ src/libventi/rpc.c | 155 ++++++ src/libventi/scorefmt.c | 18 + src/libventi/send.c | 212 ++++++++ src/libventi/server.c | 172 +++++++ src/libventi/srvhello.c | 50 ++ src/libventi/strdup.c | 18 + src/libventi/string.c | 50 ++ src/libventi/version.c | 115 +++++ src/libventi/zero.c | 55 ++ src/libventi/zeroscore.c | 10 + 28 files changed, 4635 insertions(+) create mode 100644 src/libventi/cache.c create mode 100644 src/libventi/client.c create mode 100644 src/libventi/conn.c create mode 100644 src/libventi/cvt.h create mode 100644 src/libventi/debug.c create mode 100644 src/libventi/dial.c create mode 100644 src/libventi/dtype.c create mode 100644 src/libventi/entry.c create mode 100644 src/libventi/fcall.c create mode 100644 src/libventi/fcallfmt.c create mode 100644 src/libventi/file.c create mode 100644 src/libventi/hangup.c create mode 100644 src/libventi/mem.c create mode 100644 src/libventi/mkfile create mode 100644 src/libventi/packet.c create mode 100644 src/libventi/queue.c create mode 100644 src/libventi/queue.h create mode 100644 src/libventi/root.c create mode 100644 src/libventi/rpc.c create mode 100644 src/libventi/scorefmt.c create mode 100644 src/libventi/send.c create mode 100644 src/libventi/server.c create mode 100644 src/libventi/srvhello.c create mode 100644 src/libventi/strdup.c create mode 100644 src/libventi/string.c create mode 100644 src/libventi/version.c create mode 100644 src/libventi/zero.c create mode 100644 src/libventi/zeroscore.c diff --git a/src/libventi/cache.c b/src/libventi/cache.c new file mode 100644 index 00000000..ed8a7f2f --- /dev/null +++ b/src/libventi/cache.c @@ -0,0 +1,560 @@ +/* + * Memory-only VtBlock cache. + * + * The cached Venti blocks are in the hash chains. + * The cached local blocks are only in the blocks array. + * The free blocks are in the heap, which is supposed to + * be indexed by second-to-last use but actually + * appears to be last use. + */ + +#include +#include +#include + +int nread, ncopy, nwrite; + +enum { + BioLocal = 1, + BioVenti, + BioReading, + BioWriting, + BioEmpty, + BioVentiError, +}; +enum { + BadHeap = ~0, +}; +struct VtCache +{ + QLock lk; + VtConn *z; + u32int blocksize; + u32int now; /* ticks for usage time stamps */ + VtBlock **hash; /* hash table for finding addresses */ + int nhash; + VtBlock **heap; /* heap for finding victims */ + int nheap; + VtBlock *block; /* all allocated blocks */ + int nblock; + uchar *mem; /* memory for all blocks and data */ + int mode; +}; + +static void cachecheck(VtCache*); + +VtCache* +vtcachealloc(VtConn *z, int blocksize, ulong nblock, int mode) +{ + uchar *p; + VtCache *c; + int i; + VtBlock *b; + + c = vtmallocz(sizeof(VtCache)); + + c->z = z; + c->blocksize = (blocksize + 127) & ~127; + c->nblock = nblock; + + c->nhash = nblock; + c->hash = vtmallocz(nblock*sizeof(VtBlock*)); + c->heap = vtmallocz(nblock*sizeof(VtBlock*)); + c->block = vtmallocz(nblock*sizeof(VtBlock)); + c->mem = vtmallocz(nblock*c->blocksize); + c->mode = mode; + + p = c->mem; + for(i=0; iblock[i]; + b->addr = NilBlock; + b->c = c; + b->data = p; + b->heap = i; + c->heap[i] = b; + p += c->blocksize; + } + c->nheap = nblock; + cachecheck(c); + return c; +} + +void +vtcachefree(VtCache *c) +{ + int i; + + qlock(&c->lk); + + cachecheck(c); + for(i=0; inblock; i++) + assert(c->block[i].ref == 0); + + vtfree(c->hash); + vtfree(c->heap); + vtfree(c->block); + vtfree(c->mem); + vtfree(c); +} + +static void +vtcachedump(VtCache *c) +{ + int i; + VtBlock *b; + + for(i=0; inblock; i++){ + b = &c->block[i]; + print("cache block %d: type %d score %V iostate %d addr %d ref %d nlock %d\n", + i, b->type, b->score, b->iostate, b->addr, b->ref, b->nlock); + } +} + +static void +cachecheck(VtCache *c) +{ + u32int size, now; + int i, k, refed; + VtBlock *b; + + size = c->blocksize; + now = c->now; + + for(i = 0; i < c->nheap; i++){ + if(c->heap[i]->heap != i) + sysfatal("mis-heaped at %d: %d", i, c->heap[i]->heap); + if(i > 0 && c->heap[(i - 1) >> 1]->used - now > c->heap[i]->used - now) + sysfatal("bad heap ordering"); + k = (i << 1) + 1; + if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now) + sysfatal("bad heap ordering"); + k++; + if(k < c->nheap && c->heap[i]->used - now > c->heap[k]->used - now) + sysfatal("bad heap ordering"); + } + + refed = 0; + for(i = 0; i < c->nblock; i++){ + b = &c->block[i]; + if(b->data != &c->mem[i * size]) + sysfatal("mis-blocked at %d", i); + if(b->ref && b->heap == BadHeap) + refed++; + else if(b->addr != NilBlock) + refed++; + } +if(c->nheap + refed != c->nblock){ +fprint(2, "cachecheck: nheap %d refed %d nblocks %d\n", c->nheap, refed, c->nblock); +//vtcachedump(c); +} + assert(c->nheap + refed == c->nblock); + refed = 0; + for(i = 0; i < c->nblock; i++){ + b = &c->block[i]; + if(b->ref){ +if(1)fprint(2, "a=%ud %V ref=%d\n", b->addr, b->score, b->ref); + refed++; + } + } +if(refed > 0)fprint(2, "cachecheck: in used %d\n", refed); +} + +static int +upheap(int i, VtBlock *b) +{ + VtBlock *bb; + u32int now; + int p; + VtCache *c; + + c = b->c; + now = c->now; + for(; i != 0; i = p){ + p = (i - 1) >> 1; + bb = c->heap[p]; + if(b->used - now >= bb->used - now) + break; + c->heap[i] = bb; + bb->heap = i; + } + c->heap[i] = b; + b->heap = i; + + return i; +} + +static int +downheap(int i, VtBlock *b) +{ + VtBlock *bb; + u32int now; + int k; + VtCache *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]->used - now > c->heap[k + 1]->used - now) + k++; + bb = c->heap[k]; + if(b->used - now <= bb->used - now) + break; + c->heap[i] = bb; + bb->heap = i; + } + c->heap[i] = b; + b->heap = i; + return i; +} + +/* + * Delete a block from the heap. + * Called with c->lk held. + */ +static void +heapdel(VtBlock *b) +{ + int i, si; + VtCache *c; + + c = b->c; + + si = b->heap; + if(si == BadHeap) + return; + b->heap = BadHeap; + c->nheap--; + if(si == c->nheap) + return; + b = c->heap[c->nheap]; + i = upheap(si, b); + if(i == si) + downheap(i, b); +} + +/* + * Insert a block into the heap. + * Called with c->lk held. + */ +static void +heapins(VtBlock *b) +{ + assert(b->heap == BadHeap); + upheap(b->c->nheap++, b); +} + +/* + * locate the vtBlock with the oldest second to last use. + * remove it from the heap, and fix up the heap. + */ +/* called with c->lk held */ +static VtBlock* +vtcachebumpblock(VtCache *c) +{ + VtBlock *b; + + /* + * locate the vtBlock with the oldest second to last use. + * remove it from the heap, and fix up the heap. + */ + if(c->nheap == 0){ + vtcachedump(c); +abort(); + sysfatal("vtcachebumpblock: no free blocks in vtCache"); + } + b = c->heap[0]; + heapdel(b); + + assert(b->heap == BadHeap); + assert(b->ref == 0); + + /* + * unchain the vtBlock from hash chain if any + */ + if(b->prev){ + *(b->prev) = b->next; + if(b->next) + b->next->prev = b->prev; + b->prev = nil; + } + + +if(0)fprint(2, "droping %x:%V\n", b->addr, b->score); + /* set vtBlock to a reasonable state */ + b->ref = 1; + b->iostate = BioEmpty; + return b; +} + +/* + * fetch a local block from the memory cache. + * if it's not there, load it, bumping some other Block. + * if we're out of free blocks, we're screwed. + */ +VtBlock* +vtcachelocal(VtCache *c, u32int addr, int type) +{ + VtBlock *b; + + if(addr >= c->nblock) + sysfatal("vtcachelocal: asked for block #%ud; only %d blocks\n", + addr, c->nblock); + + b = &c->block[addr]; + if(b->addr == NilBlock || b->iostate != BioLocal) +{ +abort(); + sysfatal("vtcachelocal: block is not local"); +} + + if(b->type != type) +{ +print("%d != %d\n", b->type, type); +abort(); + sysfatal("vtcachelocal: block has wrong type %d != %d", b->type, type); +} + + qlock(&c->lk); + b->ref++; + qunlock(&c->lk); + + qlock(&b->lk); + b->nlock = 1; + return b; +} + +VtBlock* +vtcacheallocblock(VtCache *c, int type) +{ + VtBlock *b; + +if(type >= VtMaxType) + abort(); + + qlock(&c->lk); + b = vtcachebumpblock(c); + b->iostate = BioLocal; + b->type = type; + b->addr = b - c->block; + vtzeroextend(type, b->data, 0, c->blocksize); + vtlocaltoglobal(b->addr, b->score); + qunlock(&c->lk); + + qlock(&b->lk); + b->nlock = 1; + + return b; +} + +/* + * fetch a global (Venti) block from the memory cache. + * if it's not there, load it, bumping some other block. + */ +VtBlock* +vtcacheglobal(VtCache *c, uchar score[VtScoreSize], int type) +{ + VtBlock *b; + ulong h; + int n; + u32int addr; + + addr = vtglobaltolocal(score); + if(addr != NilBlock) + return vtcachelocal(c, addr, type); + + h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->nhash; + + /* + * look for the block in the cache + */ + qlock(&c->lk); + for(b = c->hash[h]; b != nil; b = b->next){ + if(b->addr != NilBlock || memcmp(b->score, score, VtScoreSize) != 0 || b->type != type) + continue; + heapdel(b); + b->ref++; + qunlock(&c->lk); + qlock(&b->lk); + b->nlock = 1; + return b; + } + + /* + * not found + */ + b = vtcachebumpblock(c); + b->addr = NilBlock; + b->type = type; + memmove(b->score, score, VtScoreSize); + /* chain onto correct hash */ + b->next = c->hash[h]; + c->hash[h] = b; + if(b->next != nil) + b->next->prev = &b->next; + b->prev = &c->hash[h]; + + /* + * Lock b before unlocking c, so that others wait while we read. + * + * You might think there is a race between this qlock(b) before qunlock(c) + * and the qlock(c) while holding a qlock(b) in vtblockwrite. However, + * the block here can never be the block in a vtblockwrite, so we're safe. + * We're certainly living on the edge. + */ + qlock(&b->lk); + b->nlock = 1; + qunlock(&c->lk); + + n = vtread(c->z, score, type, b->data, c->blocksize); + if(n < 0){ +fprint(2, "vtread: %r\n"); + b->iostate = BioVentiError; + vtblockput(b); + return nil; + } + vtzeroextend(type, b->data, n, c->blocksize); + b->iostate = BioVenti; + b->nlock = 1; + b->decrypted = 0; + return b; +} + +/* + * The thread that has locked b may refer to it by + * multiple names. Nlock counts the number of + * references the locking thread holds. It will call + * vtblockput once per reference. + */ +void +vtblockduplock(VtBlock *b) +{ + assert(b->nlock > 0); + b->nlock++; +} + +/* + * we're done with the block. + * unlock it. can't use it after calling this. + */ +void +vtblockput(VtBlock* b) +{ + VtCache *c; + + if(b == nil) + return; + +if(0)fprint(2, "vtblockput: %d: %x %d %d\n", getpid(), b->addr, c->nheap, b->iostate); + + if(--b->nlock > 0) + return; + + /* + * b->nlock should probably stay at zero while + * the vtBlock is unlocked, but diskThread and vtSleep + * conspire to assume that they can just qlock(&b->lk); vtblockput(b), + * so we have to keep b->nlock set to 1 even + * when the vtBlock is unlocked. + */ + assert(b->nlock == 0); + b->nlock = 1; + + qunlock(&b->lk); + c = b->c; + qlock(&c->lk); + + if(--b->ref > 0){ + qunlock(&c->lk); + return; + } + + assert(b->ref == 0); + switch(b->iostate){ + case BioVenti: +//if(b->addr != NilBlock) print("blockput %d\n", b->addr); + b->used = c->now++; + case BioVentiError: + heapins(b); + break; + case BioLocal: + break; + } + qunlock(&c->lk); +} + +int +vtblockwrite(VtBlock *b) +{ + uchar score[VtScoreSize]; + VtCache *c; + uint h; + int n; + + if(b->iostate != BioLocal){ + abort(); + sysfatal("vtBlockWrite: not a local block"); + } + + c = b->c; + n = vtzerotruncate(b->type, b->data, c->blocksize); + if(vtwrite(c->z, score, b->type, b->data, n) < 0) + return -1; + + memmove(b->score, score, VtScoreSize); + + qlock(&c->lk); + b->iostate = BioVenti; + h = (u32int)(score[0]|(score[1]<<8)|(score[2]<<16)|(score[3]<<24)) % c->nhash; + b->next = c->hash[h]; + c->hash[h] = b; + if(b->next != nil) + b->next->prev = &b->next; + b->prev = &c->hash[h]; + qunlock(&c->lk); + return 0; +} + +uint +vtcacheblocksize(VtCache *c) +{ + return c->blocksize; +} + +VtBlock* +vtblockcopy(VtBlock *b) +{ + VtBlock *bb; + +ncopy++; + bb = vtcacheallocblock(b->c, b->type); + if(bb == nil){ + vtblockput(b); + return nil; + } + memmove(bb->data, b->data, b->c->blocksize); + vtblockput(b); + return bb; +} + +void +vtlocaltoglobal(u32int addr, uchar score[VtScoreSize]) +{ + memset(score, 0, 16); + score[16] = addr>>24; + score[17] = addr>>16; + score[18] = addr>>8; + score[19] = addr; +} + + +u32int +vtglobaltolocal(uchar score[VtScoreSize]) +{ + static uchar zero[16]; + if(memcmp(score, zero, 16) != 0) + return NilBlock; + return (score[16]<<24)|(score[17]<<16)|(score[18]<<8)|score[19]; +} diff --git a/src/libventi/client.c b/src/libventi/client.c new file mode 100644 index 00000000..c16fe483 --- /dev/null +++ b/src/libventi/client.c @@ -0,0 +1,151 @@ +#include +#include +#include + +static int +vtfcallrpc(VtConn *z, VtFcall *ou, VtFcall *in) +{ + Packet *p; + + p = vtfcallpack(ou); + if(p == nil) + return -1; + if((p = vtrpc(z, p)) == nil) + return -1; + if(vtfcallunpack(in, p) < 0){ + packetfree(p); + return -1; + } + if(in->type == VtRerror){ + werrstr(in->error); + vtfcallclear(in); + packetfree(p); + return -1; + } + if(in->type != ou->type+1){ + werrstr("type mismatch: sent %c%d got %c%d", + "TR"[ou->type&1], ou->type>>1, + "TR"[in->type&1], in->type>>1); + vtfcallclear(in); + packetfree(p); + return -1; + } + packetfree(p); + return 0; +} + +int +vthello(VtConn *z) +{ + VtFcall tx, rx; + + memset(&tx, 0, sizeof tx); + tx.type = VtThello; + tx.version = z->version; + tx.uid = z->uid; + if(tx.uid == nil) + tx.uid = "anonymous"; + if(vtfcallrpc(z, &tx, &rx) < 0) + return -1; + z->sid = rx.sid; + rx.sid = 0; + vtfcallclear(&rx); + return 0; +} + +Packet* +vtreadpacket(VtConn *z, uchar score[VtScoreSize], uint type, int n) +{ + VtFcall tx, rx; + + memset(&tx, 0, sizeof tx); + tx.type = VtTread; + tx.dtype = type; + tx.count = n; + memmove(tx.score, score, VtScoreSize); + if(vtfcallrpc(z, &tx, &rx) < 0) + return nil; + if(packetsize(rx.data) > n){ + werrstr("read returned too much data"); + packetfree(rx.data); + return nil; + } + packetsha1(rx.data, tx.score); + if(memcmp(score, tx.score, VtScoreSize) != 0){ + werrstr("read asked for %V got %V", score, tx.score); + packetfree(rx.data); + return nil; + } + + return rx.data; +} + +int +vtread(VtConn *z, uchar score[VtScoreSize], uint type, uchar *buf, int n) +{ + int nn; + Packet *p; + + if((p = vtreadpacket(z, score, type, n)) == nil) + return -1; + nn = packetsize(p); + if(packetconsume(p, buf, nn) < 0) + abort(); + return nn; +} + +int +vtwritepacket(VtConn *z, uchar score[VtScoreSize], uint type, Packet *p) +{ + VtFcall tx, rx; + + tx.type = VtTwrite; + tx.dtype = type; + tx.data = p; + packetsha1(p, score); + if(vtfcallrpc(z, &tx, &rx) < 0) + return -1; + if(memcmp(score, rx.score, VtScoreSize) != 0){ + werrstr("sha1 hash mismatch: want %V got %V", score, rx.score); + return -1; + } + return 0; +} + +int +vtwrite(VtConn *z, uchar score[VtScoreSize], uint type, uchar *buf, int n) +{ + Packet *p; + + p = packetforeign(buf, n, nil, nil); + return vtwritepacket(z, score, type, p); +} + +int +vtsync(VtConn *z) +{ + VtFcall tx, rx; + + tx.type = VtTsync; + return vtfcallrpc(z, &tx, &rx); +} + +int +vtping(VtConn *z) +{ + VtFcall tx, rx; + + tx.type = VtTping; + return vtfcallrpc(z, &tx, &rx); +} + +int +vtconnect(VtConn *z) +{ + if(vtversion(z) < 0) + return -1; + if(vthello(z) < 0) + return -1; + return 0; +} + diff --git a/src/libventi/conn.c b/src/libventi/conn.c new file mode 100644 index 00000000..3fa6fbe5 --- /dev/null +++ b/src/libventi/conn.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include "queue.h" + +VtConn* +vtconn(int infd, int outfd) +{ + VtConn *z; + + z = vtmallocz(sizeof(VtConn)); + z->tagrend.l = &z->lk; + z->rpcfork.l = &z->lk; + z->infd = infd; + z->outfd = outfd; + z->part = packetalloc(); + return z; +} + +void +vtfreeconn(VtConn *z) +{ + vthangup(z); + qlock(&z->lk); + for(;;){ + if(z->readq) + _vtqhangup(z->readq); + else if(z->writeq) + _vtqhangup(z->writeq); + else + break; + rsleep(&z->rpcfork); + } + packetfree(z->part); + vtfree(z); +} diff --git a/src/libventi/cvt.h b/src/libventi/cvt.h new file mode 100644 index 00000000..ef4c2f51 --- /dev/null +++ b/src/libventi/cvt.h @@ -0,0 +1,15 @@ +/* + * integer conversion routines + */ +#define U8GET(p) ((p)[0]) +#define U16GET(p) (((p)[0]<<8)|(p)[1]) +#define U32GET(p) ((u32int)(((p)[0]<<24)|((p)[1]<<16)|((p)[2]<<8)|(p)[3])) +#define U48GET(p) (((vlong)U16GET(p)<<32)|(vlong)U32GET((p)+2)) +#define U64GET(p) (((vlong)U32GET(p)<<32)|(vlong)U32GET((p)+4)) + +#define U8PUT(p,v) (p)[0]=(v) +#define U16PUT(p,v) (p)[0]=(v)>>8;(p)[1]=(v) +#define U32PUT(p,v) (p)[0]=(v)>>24;(p)[1]=(v)>>16;(p)[2]=(v)>>8;(p)[3]=(v) +#define U48PUT(p,v,t32) t32=(v)>>32;U16PUT(p,t32);t32=(v);U32PUT((p)+2,t32) +#define U64PUT(p,v,t32) t32=(v)>>32;U32PUT(p,t32);t32=(v);U32PUT((p)+4,t32) + diff --git a/src/libventi/debug.c b/src/libventi/debug.c new file mode 100644 index 00000000..e0452e48 --- /dev/null +++ b/src/libventi/debug.c @@ -0,0 +1,17 @@ +#include +#include +#include + +void +vtdebug(VtConn *z, char *fmt, ...) +{ + va_list arg; + + if(z->debug == 0) + return; + + va_start(arg, fmt); + vfprint(2, fmt, arg); + va_end(arg); +} + diff --git a/src/libventi/dial.c b/src/libventi/dial.c new file mode 100644 index 00000000..f7cbce33 --- /dev/null +++ b/src/libventi/dial.c @@ -0,0 +1,21 @@ +#include +#include +#include + +VtConn* +vtdial(char *addr) +{ + char *na; + int fd; + + if(addr == nil) + addr = getenv("venti"); + if(addr == nil) + addr = "$venti"; + + na = netmkaddr(addr, "net", "venti"); + if((fd = dial(na, nil, nil, nil)) < 0) + return nil; + + return vtconn(fd, fd); +} diff --git a/src/libventi/dtype.c b/src/libventi/dtype.c new file mode 100644 index 00000000..0151ba28 --- /dev/null +++ b/src/libventi/dtype.c @@ -0,0 +1,78 @@ +#include +#include +#include + +enum { + OVtErrType, /* illegal */ + + OVtRootType, + OVtDirType, + OVtPointerType0, + OVtPointerType1, + OVtPointerType2, + OVtPointerType3, + OVtPointerType4, + OVtPointerType5, + OVtPointerType6, + OVtPointerType7, /* not used */ + OVtPointerType8, /* not used */ + OVtPointerType9, /* not used */ + OVtDataType, + + OVtMaxType +}; + + +uint todisk[] = { + OVtDataType, + OVtPointerType0, + OVtPointerType1, + OVtPointerType2, + OVtPointerType3, + OVtPointerType4, + OVtPointerType5, + OVtPointerType6, + OVtDirType, + OVtPointerType0, + OVtPointerType1, + OVtPointerType2, + OVtPointerType3, + OVtPointerType4, + OVtPointerType5, + OVtPointerType6, + OVtRootType, +}; + +uint fromdisk[] = { + ~0, + VtRootType, + VtDirType, + VtDirType+1, + VtDirType+2, + VtDirType+3, + VtDirType+4, + VtDirType+5, + VtDirType+6, + VtDirType+7, + ~0, + ~0, + ~0, + VtDataType, +}; + +uint +vttodisktype(uint n) +{ + if(n >= nelem(todisk)) + return ~0; + return todisk[n]; +} + +uint +vtfromdisktype(uint n) +{ + if(n >= nelem(fromdisk)) + return ~0; + return fromdisk[n]; +} + diff --git a/src/libventi/entry.c b/src/libventi/entry.c new file mode 100644 index 00000000..59c09e9a --- /dev/null +++ b/src/libventi/entry.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include "cvt.h" + +static int +checksize(int n) +{ + if(n < 256 || n > VtMaxLumpSize) { + werrstr("bad block size"); + return -1; + } + return 0; +} + +void +vtentrypack(VtEntry *e, uchar *p, int index) +{ + ulong t32; + int flags; + uchar *op; + int depth; + + p += index * VtEntrySize; + op = p; + + U32PUT(p, e->gen); + p += 4; + U16PUT(p, e->psize); + p += 2; + U16PUT(p, e->dsize); + p += 2; + depth = e->type&VtTypeDepthMask; + flags = (e->flags&~(VtEntryDir|VtEntryDepthShift)); + flags |= depth << VtEntryDepthShift; + if(e->type - depth == VtEntryDir) + flags |= VtEntryDir; + U8PUT(p, flags); + p++; + memset(p, 0, 5); + p += 5; + U48PUT(p, e->size, t32); + p += 6; + memmove(p, e->score, VtScoreSize); + p += VtScoreSize; + + assert(p-op == VtEntrySize); +} + +int +vtentryunpack(VtEntry *e, uchar *p, int index) +{ + uchar *op; + + p += index * VtEntrySize; + op = p; + + e->gen = U32GET(p); + p += 4; + e->psize = U16GET(p); + p += 2; + e->dsize = U16GET(p); + p += 2; + e->flags = U8GET(p); + e->type = (e->flags&VtEntryDir) ? VtDirType : VtDataType; + e->type += (e->flags & VtEntryDepthMask) >> VtEntryDepthShift; + e->flags &= ~(VtEntryDir|VtEntryDepthMask); + p++; + p += 5; + e->size = U48GET(p); + p += 6; + memmove(e->score, p, VtScoreSize); + p += VtScoreSize; + + assert(p-op == VtEntrySize); + + if(!(e->flags & VtEntryActive)) + return 0; + + if(checksize(e->psize) < 0 || checksize(e->dsize) < 0) + return -1; + + return 0; +} + diff --git a/src/libventi/fcall.c b/src/libventi/fcall.c new file mode 100644 index 00000000..ace8962a --- /dev/null +++ b/src/libventi/fcall.c @@ -0,0 +1,230 @@ +#include +#include +#include + +Packet* +vtfcallpack(VtFcall *f) +{ + uchar buf[4]; + Packet *p; + + p = packetalloc(); + + buf[0] = f->type; + buf[1] = f->tag; + packetappend(p, buf, 2); + + switch(f->type){ + default: + werrstr("vtfcallpack: unknown packet type %d", f->type); + goto Err; + + case VtRerror: + if(vtputstring(p, f->error) < 0) + goto Err; + break; + + case VtTping: + break; + + case VtRping: + break; + + case VtThello: + if(vtputstring(p, f->version) < 0 + || vtputstring(p, f->uid) < 0) + goto Err; + buf[0] = f->strength; + buf[1] = f->ncrypto; + packetappend(p, buf, 2); + packetappend(p, f->crypto, f->ncrypto); + buf[0] = f->ncodec; + packetappend(p, buf, 1); + packetappend(p, f->codec, f->ncodec); + break; + + case VtRhello: + if(vtputstring(p, f->sid) < 0) + goto Err; + buf[0] = f->rcrypto; + buf[1] = f->rcodec; + packetappend(p, buf, 2); + break; + + case VtTgoodbye: + break; + + case VtTread: + packetappend(p, f->score, VtScoreSize); + buf[0] = vttodisktype(f->dtype); + if(~buf[0] == 0) + goto Err; + buf[1] = 0; + buf[2] = f->count >> 8; + buf[3] = f->count; + packetappend(p, buf, 4); + break; + + case VtRread: + packetconcat(p, f->data); + break; + + case VtTwrite: + buf[0] = vttodisktype(f->dtype); + if(~buf[0] == 0) + goto Err; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + packetappend(p, buf, 4); + packetconcat(p, f->data); + break; + + case VtRwrite: + packetappend(p, f->score, VtScoreSize); + break; + + case VtTsync: + break; + + case VtRsync: + break; + } + + return p; + +Err: + packetfree(p); + return nil; +} + +int +vtfcallunpack(VtFcall *f, Packet *p) +{ + uchar buf[4]; + + memset(f, 0, sizeof *f); + + if(packetconsume(p, buf, 2) < 0) + return -1; + + f->type = buf[0]; + f->tag = buf[1]; + + switch(f->type){ + default: + werrstr("vtfcallunpack: unknown bad packet type %d", f->type); + vtfcallclear(f); + return -1; + + case VtRerror: + if(vtgetstring(p, &f->error) < 0) + goto Err; + break; + + case VtTping: + break; + + case VtRping: + break; + + case VtThello: + if(vtgetstring(p, &f->version) < 0 + || vtgetstring(p, &f->uid) < 0 + || packetconsume(p, buf, 2) < 0) + goto Err; + f->strength = buf[0]; + f->ncrypto = buf[1]; + if(f->ncrypto){ + f->crypto = vtmalloc(f->ncrypto); + if(packetconsume(p, buf, f->ncrypto) < 0) + goto Err; + } + if(packetconsume(p, buf, 1) < 0) + goto Err; + f->ncodec = buf[0]; + if(f->ncodec){ + f->codec = vtmalloc(f->ncodec); + if(packetconsume(p, buf, f->ncodec) < 0) + goto Err; + } + break; + + case VtRhello: + if(vtgetstring(p, &f->sid) < 0 + || packetconsume(p, buf, 2) < 0) + goto Err; + f->rcrypto = buf[0]; + f->rcodec = buf[1]; + break; + + case VtTgoodbye: + break; + + case VtTread: + if(packetconsume(p, f->score, VtScoreSize) < 0 + || packetconsume(p, buf, 4) < 0) + goto Err; + f->dtype = vtfromdisktype(buf[0]); + if(~f->dtype == 0) + goto Err; + f->count = (buf[2] << 8) | buf[3]; + break; + + case VtRread: + f->data = packetalloc(); + packetconcat(f->data, p); + break; + + case VtTwrite: + if(packetconsume(p, buf, 4) < 0) + goto Err; + f->dtype = vtfromdisktype(buf[0]); + if(~f->dtype == 0) + goto Err; + f->data = packetalloc(); + packetconcat(f->data, p); + break; + + case VtRwrite: + if(packetconsume(p, f->score, VtScoreSize) < 0) + goto Err; + break; + + case VtTsync: + break; + + case VtRsync: + break; + } + + if(packetsize(p) != 0) + goto Err; + + return 0; + +Err: + werrstr("bad packet"); + return -1; +} + +void +vtfcallclear(VtFcall *f) +{ + vtfree(f->error); + f->error = nil; + vtfree(f->uid); + f->uid = nil; + vtfree(f->sid); + f->sid = nil; + vtfree(f->version); + f->version = nil; + vtfree(f->crypto); + f->crypto = nil; + vtfree(f->codec); + f->codec = nil; + vtfree(f->auth); + f->auth = nil; + packetfree(f->data); + f->auth = nil; +} diff --git a/src/libventi/fcallfmt.c b/src/libventi/fcallfmt.c new file mode 100644 index 00000000..23e13eff --- /dev/null +++ b/src/libventi/fcallfmt.c @@ -0,0 +1,55 @@ +#include +#include +#include + +int +vtfcallfmt(Fmt *f) +{ + VtFcall *t; + + t = va_arg(f->args, VtFcall*); + if(t == nil){ + fmtprint(f, ""); + return 0; + } + switch(t->type){ + default: + return fmtprint(f, "%c%d tag %ud", "TR"[t->type&1], t->type>>1, t->tag); + case VtRerror: + return fmtprint(f, "Rerror tag %ud error %s", t->tag, t->error); + case VtTping: + return fmtprint(f, "Tping tag %ud", t->tag); + case VtRping: + return fmtprint(f, "Rping tag %ud", t->tag); + case VtThello: + return fmtprint(f, "Thello tag %ud vers %s uid %s strength %d crypto %d:%.*H codec %d:%.*H", t->tag, + t->version, t->uid, t->strength, t->ncrypto, t->ncrypto, t->crypto, + t->ncodec, t->ncodec, t->codec); + case VtRhello: + return fmtprint(f, "Rhello tag %ud sid %s rcrypto %d rcodec %d", t->tag, t->sid, t->rcrypto, t->rcodec); + case VtTgoodbye: + return fmtprint(f, "Tgoodbye tag %ud", t->tag); + case VtRgoodbye: + return fmtprint(f, "Rgoodbye tag %ud", t->tag); + case VtTauth0: + return fmtprint(f, "Tauth0 tag %ud auth %.*H", t->tag, t->nauth, t->auth); + case VtRauth0: + return fmtprint(f, "Rauth0 tag %ud auth %.*H", t->tag, t->nauth, t->auth); + case VtTauth1: + return fmtprint(f, "Tauth1 tag %ud auth %.*H", t->tag, t->nauth, t->auth); + case VtRauth1: + return fmtprint(f, "Rauth1 tag %ud auth %.*H", t->tag, t->nauth, t->auth); + case VtTread: + return fmtprint(f, "Tread tag %ud score %V dtype %d count %d", t->tag, t->score, t->dtype, t->count); + case VtRread: + return fmtprint(f, "Rread tag %ud count %d", t->tag, packetsize(t->data)); + case VtTwrite: + return fmtprint(f, "Twrite tag %ud dtype %d count %d", t->tag, t->dtype, packetsize(t->data)); + case VtRwrite: + return fmtprint(f, "Rwrite tag %ud score %V", t->tag, t->score); + case VtTsync: + return fmtprint(f, "Tsync tag %ud", t->tag); + case VtRsync: + return fmtprint(f, "Rsync tag %ud", t->tag); + } +} diff --git a/src/libventi/file.c b/src/libventi/file.c new file mode 100644 index 00000000..40484117 --- /dev/null +++ b/src/libventi/file.c @@ -0,0 +1,1264 @@ +/* + * 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 +#include +#include + +enum +{ + MaxBlock = (1UL<<31), +}; + +struct VtFile +{ + QLock lk; + int ref; + int local; + VtBlock *b; /* block containing this file */ + uchar score[VtScoreSize]; /* score of block containing this file */ + +/* immutable */ + VtCache *c; + int mode; + u32int gen; + int dsize; + int dir; + VtFile *parent; + int epb; /* entries per block in parent */ + u32int offset; /* entry offset in parent */ +}; + +static char EBadEntry[] = "bad VtEntry"; +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; + u32int size; + 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) + goto Bad; + + /* + * 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\n"); + goto Bad; + } + if(!(e.flags & VtEntryActive)){ + if(0)fprint(2, "not active\n"); + goto Bad; + } + if(e.psize < 256 || e.dsize < 256){ + fprint(2, "psize %ud dsize %ud\n", e.psize, e.dsize); + goto Bad; + } + + if(DEPTH(e.type) < sizetodepth(e.size, e.psize, e.dsize)){ + fprint(2, "depth %ud size %llud psize %ud dsize %ud\n", + DEPTH(e.type), e.size, e.psize, e.dsize); + goto Bad; + } + + size = vtcacheblocksize(c); + if(e.dsize > size || e.psize > size){ + fprint(2, "psize %ud dsize %ud blocksize %ud\n", e.psize, e.dsize, size); + goto Bad; + } + + r = vtmallocz(sizeof(VtFile)); + r->c = c; + r->mode = mode; + 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 == VtOREAD || p->mode == VtORDWR); + p->ref++; + qunlock(&p->lk); + } + memmove(r->score, b->score, VtScoreSize); + r->offset = offset; + r->epb = epb; + + return r; +Bad: + werrstr(EBadEntry); + return nil; + +} + +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); + 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; + if(type == VtDirType) + e.flags |= VtEntryDir; + 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 dir) +{ + int i; + VtBlock *b; + u32int bn, size; + VtEntry e; + int epb; + VtFile *rr; + u32int offset; + + assert(ISLOCKED(r)); + + 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 + */ + offset = lnrand(size+1); + offset -= offset % epb; + + /* 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; idata, 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 = dir ? VtDirType : VtDataType; + 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, 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+1dir; + 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(; idata+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; +} + +int +vtfilesetsize(VtFile *r, uvlong 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(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){ + b = vtcacheallocblock(c, type); + 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; +} + +/* + * 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; + VtEntry oe; + + assert(ISLOCKED(r)); + assert(depth <= VtPointerDepth); + + b = vtcacheglobal(r->c, e->score, e->type); + if(b == nil) + return -1; + + oe = *e; + + /* + * 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 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 0; + + /* + * 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 +mkindices(VtEntry *e, u32int bn, int *index) +{ + int i, np; + + memset(index, 0, VtPointerDepth*sizeof(int)); + + np = e->psize/VtScoreSize; + for(i=0; bn > 0; i++){ + if(i >= VtPointerDepth){ + werrstr(EBadAddr); + 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) + return nil; + if(i > DEPTH(e.type)){ + if(mode == VtOREAD){ + werrstr(EBadAddr); + 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(b, index[i], r->c, i==0 ? mode : m, &e); + if(bb == nil) + goto Err; + vtblockput(b); + b = bb; + } + return b; +Err: + vtblockput(b); + return nil; +} + +int +vtfileblockhash(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; + + 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(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: +fprint(2, "vtfileblockhash: %r\n"); + 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); + if(b == nil) + return nil; + 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; + } +} + +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; + 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; + 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(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; idata, i) < 0) + goto Err; + if(flushblock(c, nil, e.score, e.psize/VtScoreSize, e.dsize/VtEntrySize, + e.type) < 0) + goto Err; + } + break; + + default: /* VtPointerTypeX */ + for(i=0; idata+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){ + 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, ok; + int index[VtPointerDepth+1], index1[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; + + 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; + mkindices(&e, offset/e.dsize, index1); + ppb = e.psize / VtScoreSize; + epb = e.dsize / VtEntrySize; + + index[depth] = r->offset % r->epb; + for(i=depth; i>=0; i--){ + bb = blockwalk(b, index[i], r->c, VtORDWR, &e); + if(bb == nil) + goto Err; + bi[i] = bb; + b = bb; + } + ret = 0; + + base = e.type&~VtTypeDepthMask; + for(i=0; ic, nil, b->data+j*VtScoreSize, ppb, epb, base+i-1) < 0) + goto Err; + /* + * if the rest of the block is already flushed, + * we can flush the whole block. + */ + ok = 1; + for(; jdata+j*VtScoreSize) != NilBlock) + ok = 0; + } + if(ok){ + 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 +#include +#include +#include "queue.h" + +void +vthangup(VtConn *z) +{ + qlock(&z->lk); + z->state = VtStateClosed; + if(z->infd >= 0) + close(z->infd); + if(z->outfd >= 0 && z->outfd != z->infd) + close(z->outfd); + z->infd = -1; + z->outfd = -1; + if(z->writeq) + _vtqhangup(z->writeq); + if(z->readq) + _vtqhangup(z->readq); + qunlock(&z->lk); +} diff --git a/src/libventi/mem.c b/src/libventi/mem.c new file mode 100644 index 00000000..cf86fe13 --- /dev/null +++ b/src/libventi/mem.c @@ -0,0 +1,87 @@ +#include +#include +#include + +enum { + IdealAlignment = 32, + ChunkSize = 128*1024, +}; + + +void +vtfree(void *p) +{ + if(p == 0) + return; + free(p); +} + +void * +vtmalloc(int size) +{ + void *p; + + p = malloc(size); + if(p == 0) + sysfatal("vtmalloc: out of memory"); + setmalloctag(p, getcallerpc(&size)); + return p; +} + +void * +vtmallocz(int size) +{ + void *p = vtmalloc(size); + memset(p, 0, size); + setmalloctag(p, getcallerpc(&size)); + return p; +} + +void * +vtrealloc(void *p, int size) +{ + if(p == nil) + return vtmalloc(size); + p = realloc(p, size); + if(p == 0) + sysfatal("vtMemRealloc: out of memory"); + setrealloctag(p, getcallerpc(&size)); + return p; +} + +void * +vtbrk(int n) +{ + static Lock lk; + static uchar *buf; + static int nbuf; + static int nchunk; + int align, pad; + void *p; + + if(n >= IdealAlignment) + align = IdealAlignment; + else if(n > 8) + align = 8; + else + align = 4; + + lock(&lk); + pad = (align - (ulong)buf) & (align-1); + if(n + pad > nbuf) { + buf = vtmallocz(ChunkSize); + nbuf = ChunkSize; + pad = (align - (ulong)buf) & (align-1); + nchunk++; + } + + assert(n + pad <= nbuf); + + p = buf + pad; + buf += pad + n; + nbuf -= pad + n; + unlock(&lk); + + return p; +} + diff --git a/src/libventi/mkfile b/src/libventi/mkfile new file mode 100644 index 00000000..735e1e3c --- /dev/null +++ b/src/libventi/mkfile @@ -0,0 +1,42 @@ +PLAN9=../.. +<$PLAN9/src/mkhdr + +LIB=libventi.a + +OFILES=\ + cache.$O\ + client.$O\ + conn.$O\ + dial.$O\ + debug.$O\ + dtype.$O\ + entry.$O\ + fcall.$O\ + fcallfmt.$O\ + file.$O\ + hangup.$O\ + mem.$O\ + packet.$O\ + queue.$O\ + root.$O\ + rpc.$O\ + scorefmt.$O\ + send.$O\ + server.$O\ + srvhello.$O\ + strdup.$O\ + string.$O\ + version.$O\ + zero.$O\ + zeroscore.$O\ + +HFILES=\ + $PLAN9/include/venti.h\ + +<$PLAN9/src/mksyslib + +send.$O: queue.h +server.$O: queue.h +queue.$O: queue.h + + diff --git a/src/libventi/packet.c b/src/libventi/packet.c new file mode 100644 index 00000000..c781eb39 --- /dev/null +++ b/src/libventi/packet.c @@ -0,0 +1,941 @@ +#include +#include +#include +#include + +typedef struct Mem Mem; +typedef struct Frag Frag; + +enum { + BigMemSize = MaxFragSize, + SmallMemSize = BigMemSize/8, + NLocalFrag = 2, +}; + +/* position to carve out of a Mem */ +enum { + PFront, + PMiddle, + PEnd, +}; + +struct Mem +{ + Lock lk; + int ref; + uchar *bp; + uchar *ep; + uchar *rp; + uchar *wp; + Mem *next; +}; + +enum { + FragLocalFree, + FragLocalAlloc, + FragGlobal, +}; + +struct Frag +{ + int state; + Mem *mem; + uchar *rp; + uchar *wp; + Frag *next; + void (*free)(void*); + void *a; +}; + +struct Packet +{ + int size; + int asize; /* allocated memory - greater than size unless foreign frags */ + + Packet *next; + + Frag *first; + Frag *last; + + Frag local[NLocalFrag]; +}; + +static Frag *fragalloc(Packet*, int n, int pos, Frag *next); +static Frag *fragdup(Packet*, Frag*); +static void fragfree(Frag*); + +static Mem *memalloc(int, int); +static void memfree(Mem*); +static int memhead(Mem *m, uchar *rp, int n); +static int memtail(Mem *m, uchar *wp, int n); + +static char EPacketSize[] = "bad packet size"; +static char EPacketOffset[] = "bad packet offset"; +static char EBadSize[] = "bad size"; + +static struct { + Lock lk; + Packet *packet; + int npacket; + Frag *frag; + int nfrag; + Mem *bigmem; + int nbigmem; + Mem *smallmem; + int nsmallmem; +} freelist; + +#define FRAGSIZE(f) ((f)->wp - (f)->rp) +#define FRAGASIZE(f) ((f)->mem ? (f)->mem->ep - (f)->mem->bp : 0) + +#define NOTFREE(p) assert((p)->size>=0) + +Packet * +packetalloc(void) +{ + Packet *p; + + lock(&freelist.lk); + p = freelist.packet; + if(p != nil) + freelist.packet = p->next; + else + freelist.npacket++; + unlock(&freelist.lk); + + if(p == nil) + p = vtbrk(sizeof(Packet)); + else + assert(p->size == -1); + p->size = 0; + p->asize = 0; + p->first = nil; + p->last = nil; + p->next = nil; + +if(1)fprint(2, "packetalloc %p from %08lux %08lux %08lux\n", p, *((uint*)&p+2), *((uint*)&p+3), *((uint*)&p+4)); + + return p; +} + +void +packetfree(Packet *p) +{ + Frag *f, *ff; + +if(1)fprint(2, "packetfree %p from %08lux\n", p, getcallerpc(&p)); + + if(p == nil) + return; + + NOTFREE(p); + p->size = -1; + + for(f=p->first; f!=nil; f=ff) { + ff = f->next; + fragfree(f); + } + p->first = nil; + p->last = nil; + + lock(&freelist.lk); + p->next = freelist.packet; + freelist.packet = p; + unlock(&freelist.lk); +} + +Packet * +packetdup(Packet *p, int offset, int n) +{ + Frag *f, *ff; + Packet *pp; + + NOTFREE(p); + if(offset < 0 || n < 0 || offset+n > p->size) { + werrstr(EBadSize); + return nil; + } + + pp = packetalloc(); + if(n == 0) + return pp; + + pp->size = n; + + /* skip offset */ + for(f=p->first; offset >= FRAGSIZE(f); f=f->next) + offset -= FRAGSIZE(f); + + /* first frag */ + ff = fragdup(pp, f); + ff->rp += offset; + pp->first = ff; + n -= FRAGSIZE(ff); + pp->asize += FRAGASIZE(ff); + + /* the remaining */ + while(n > 0) { + f = f->next; + ff->next = fragdup(pp, f); + ff = ff->next; + n -= FRAGSIZE(ff); + pp->asize += FRAGASIZE(ff); + } + + /* fix up last frag: note n <= 0 */ + ff->wp += n; + ff->next = nil; + pp->last = ff; + + return pp; +} + +Packet * +packetsplit(Packet *p, int n) +{ + Packet *pp; + Frag *f, *ff; + + NOTFREE(p); + if(n < 0 || n > p->size) { + werrstr(EPacketSize); + return nil; + } + + pp = packetalloc(); + if(n == 0) + return pp; + + pp->size = n; + p->size -= n; + ff = nil; + for(f=p->first; n > 0 && n >= FRAGSIZE(f); f=f->next) { + n -= FRAGSIZE(f); + p->asize -= FRAGASIZE(f); + pp->asize += FRAGASIZE(f); + ff = f; + } + + /* split shared frag */ + if(n > 0) { + ff = f; + f = fragdup(pp, ff); + pp->asize += FRAGASIZE(ff); + ff->next = nil; + ff->wp = ff->rp + n; + f->rp += n; + } + + pp->first = p->first; + pp->last = ff; + p->first = f; + return pp; +} + +int +packetconsume(Packet *p, uchar *buf, int n) +{ + NOTFREE(p); + if(buf && packetcopy(p, buf, 0, n) < 0) + return 0; + return packettrim(p, n, p->size-n); +} + +int +packettrim(Packet *p, int offset, int n) +{ + Frag *f, *ff; + + NOTFREE(p); + if(offset < 0 || offset > p->size) { + werrstr(EPacketOffset); + return -1; + } + + if(n < 0 || offset + n > p->size) { + werrstr(EPacketOffset); + return -1; + } + + p->size = n; + + /* easy case */ + if(n == 0) { + for(f=p->first; f != nil; f=ff) { + ff = f->next; + fragfree(f); + } + p->first = p->last = nil; + p->asize = 0; + return 0; + } + + /* free before offset */ + for(f=p->first; offset >= FRAGSIZE(f); f=ff) { + p->asize -= FRAGASIZE(f); + offset -= FRAGSIZE(f); + ff = f->next; + fragfree(f); + } + + /* adjust frag */ + f->rp += offset; + p->first = f; + + /* skip middle */ + for(; n > 0 && n > FRAGSIZE(f); f=f->next) + n -= FRAGSIZE(f); + + /* adjust end */ + f->wp = f->rp + n; + p->last = f; + ff = f->next; + f->next = nil; + + /* free after */ + for(f=ff; f != nil; f=ff) { + p->asize -= FRAGASIZE(f); + ff = f->next; + fragfree(f); + } + return 0; +} + +uchar * +packetheader(Packet *p, int n) +{ + Frag *f; + Mem *m; + + NOTFREE(p); + if(n <= 0 || n > MaxFragSize) { + werrstr(EPacketSize); + return nil; + } + + p->size += n; + + /* try and fix in current frag */ + f = p->first; + if(f != nil) { + m = f->mem; + if(n <= f->rp - m->bp) + if(m->ref == 1 || memhead(m, f->rp, n) >= 0) { + f->rp -= n; + return f->rp; + } + } + + /* add frag to front */ + f = fragalloc(p, n, PEnd, p->first); + p->asize += FRAGASIZE(f); + if(p->first == nil) + p->last = f; + p->first = f; + return f->rp; +} + +uchar * +packettrailer(Packet *p, int n) +{ + Mem *m; + Frag *f; + + NOTFREE(p); + if(n <= 0 || n > MaxFragSize) { + werrstr(EPacketSize); + return nil; + } + + p->size += n; + + /* try and fix in current frag */ + if(p->first != nil) { + f = p->last; + m = f->mem; + if(n <= m->ep - f->wp) + if(m->ref == 1 || memtail(m, f->wp, n) >= 0) { + f->wp += n; + return f->wp - n; + } + } + + /* add frag to end */ + f = fragalloc(p, n, (p->first == nil)?PMiddle:PFront, nil); + p->asize += FRAGASIZE(f); + if(p->first == nil) + p->first = f; + else + p->last->next = f; + p->last = f; + return f->rp; +} + +int +packetprefix(Packet *p, uchar *buf, int n) +{ + Frag *f; + int nn; + Mem *m; + + NOTFREE(p); + if(n <= 0) + return 0; + + p->size += n; + + /* try and fix in current frag */ + f = p->first; + if(f != nil) { + m = f->mem; + nn = f->rp - m->bp; + if(nn > n) + nn = n; + if(m->ref == 1 || memhead(m, f->rp, nn) >= 0) { + f->rp -= nn; + n -= nn; + memmove(f->rp, buf+n, nn); + } + } + + while(n > 0) { + nn = n; + if(nn > MaxFragSize) + nn = MaxFragSize; + f = fragalloc(p, nn, PEnd, p->first); + p->asize += FRAGASIZE(f); + if(p->first == nil) + p->last = f; + p->first = f; + n -= nn; + memmove(f->rp, buf+n, nn); + } + return 0; +} + +int +packetappend(Packet *p, uchar *buf, int n) +{ + Frag *f; + int nn; + Mem *m; + + NOTFREE(p); + if(n <= 0) + return 0; + + p->size += n; + /* try and fix in current frag */ + if(p->first != nil) { + f = p->last; + m = f->mem; + nn = m->ep - f->wp; + if(nn > n) + nn = n; + if(m->ref == 1 || memtail(m, f->wp, nn) >= 0) { + memmove(f->wp, buf, nn); + f->wp += nn; + buf += nn; + n -= nn; + } + } + + while(n > 0) { + nn = n; + if(nn > MaxFragSize) + nn = MaxFragSize; + f = fragalloc(p, nn, (p->first == nil)?PMiddle:PFront, nil); + p->asize += FRAGASIZE(f); + if(p->first == nil) + p->first = f; + else + p->last->next = f; + p->last = f; + memmove(f->rp, buf, nn); + buf += nn; + n -= nn; + } + return 0; +} + +int +packetconcat(Packet *p, Packet *pp) +{ + NOTFREE(p); + NOTFREE(pp); + if(pp->size == 0) + return 0; + p->size += pp->size; + p->asize += pp->asize; + + if(p->first != nil) + p->last->next = pp->first; + else + p->first = pp->first; + p->last = pp->last; + pp->size = 0; + pp->asize = 0; + pp->first = nil; + pp->last = nil; + return 0; +} + +uchar * +packetpeek(Packet *p, uchar *buf, int offset, int n) +{ + Frag *f; + int nn; + uchar *b; + + NOTFREE(p); + if(n == 0) + return buf; + + if(offset < 0 || offset >= p->size) { + werrstr(EPacketOffset); + return nil; + } + + if(n < 0 || offset + n > p->size) { + werrstr(EPacketSize); + return nil; + } + + /* skip up to offset */ + for(f=p->first; offset >= FRAGSIZE(f); f=f->next) + offset -= FRAGSIZE(f); + + /* easy case */ + if(offset + n <= FRAGSIZE(f)) + return f->rp + offset; + + for(b=buf; n>0; n -= nn) { + nn = FRAGSIZE(f) - offset; + if(nn > n) + nn = n; + memmove(b, f->rp+offset, nn); + offset = 0; + f = f->next; + b += nn; + } + + return buf; +} + +int +packetcopy(Packet *p, uchar *buf, int offset, int n) +{ + uchar *b; + + NOTFREE(p); + b = packetpeek(p, buf, offset, n); + if(b == nil) + return -1; + if(b != buf) + memmove(buf, b, n); + return 0; +} + +int +packetfragments(Packet *p, IOchunk *io, int nio, int offset) +{ + Frag *f; + int size; + IOchunk *eio; + + NOTFREE(p); + if(p->size == 0 || nio <= 0) + return 0; + + if(offset < 0 || offset > p->size) { + werrstr(EPacketOffset); + return -1; + } + + for(f=p->first; offset >= FRAGSIZE(f); f=f->next) + offset -= FRAGSIZE(f); + + size = 0; + eio = io + nio; + for(; f != nil && io < eio; f=f->next) { + io->addr = f->rp + offset; + io->len = f->wp - (f->rp + offset); + offset = 0; + size += io->len; + io++; + } + + return size; +} + +void +packetstats(void) +{ + Packet *p; + Frag *f; + Mem *m; + + int np, nf, nsm, nbm; + + lock(&freelist.lk); + np = 0; + for(p=freelist.packet; p; p=p->next) + np++; + nf = 0; + for(f=freelist.frag; f; f=f->next) + nf++; + nsm = 0; + for(m=freelist.smallmem; m; m=m->next) + nsm++; + nbm = 0; + for(m=freelist.bigmem; m; m=m->next) + nbm++; + + fprint(2, "packet: %d/%d frag: %d/%d small mem: %d/%d big mem: %d/%d\n", + np, freelist.npacket, + nf, freelist.nfrag, + nsm, freelist.nsmallmem, + nbm, freelist.nbigmem); + + unlock(&freelist.lk); +} + + +uint +packetsize(Packet *p) +{ + NOTFREE(p); + if(0) { + Frag *f; + int size = 0; + + for(f=p->first; f; f=f->next) + size += FRAGSIZE(f); + if(size != p->size) + fprint(2, "packetsize %d %d\n", size, p->size); + assert(size == p->size); + } + return p->size; +} + +uint +packetasize(Packet *p) +{ + NOTFREE(p); + if(0) { + Frag *f; + int asize = 0; + + for(f=p->first; f; f=f->next) + asize += FRAGASIZE(f); + if(asize != p->asize) + fprint(2, "packetasize %d %d\n", asize, p->asize); + assert(asize == p->asize); + } + return p->asize; +} + +void +packetsha1(Packet *p, uchar digest[VtScoreSize]) +{ + DigestState ds; + Frag *f; + int size; + + NOTFREE(p); + memset(&ds, 0, sizeof ds); + size = p->size; + for(f=p->first; f; f=f->next) { + sha1(f->rp, FRAGSIZE(f), nil, &ds); + size -= FRAGSIZE(f); + } + assert(size == 0); + sha1(nil, 0, digest, &ds); +} + +int +packetcmp(Packet *pkt0, Packet *pkt1) +{ + Frag *f0, *f1; + int n0, n1, x; + + NOTFREE(pkt0); + NOTFREE(pkt1); + f0 = pkt0->first; + f1 = pkt1->first; + + if(f0 == nil) + return (f1 == nil)?0:-1; + if(f1 == nil) + return 1; + n0 = FRAGSIZE(f0); + n1 = FRAGSIZE(f1); + + for(;;) { + if(n0 < n1) { + x = memcmp(f0->wp - n0, f1->wp - n1, n0); + if(x != 0) + return x; + n1 -= n0; + f0 = f0->next; + if(f0 == nil) + return -1; + n0 = FRAGSIZE(f0); + } else if (n0 > n1) { + x = memcmp(f0->wp - n0, f1->wp - n1, n1); + if(x != 0) + return x; + n0 -= n1; + f1 = f1->next; + if(f1 == nil) + return 1; + n1 = FRAGSIZE(f1); + } else { /* n0 == n1 */ + x = memcmp(f0->wp - n0, f1->wp - n1, n0); + if(x != 0) + return x; + f0 = f0->next; + f1 = f1->next; + if(f0 == nil) + return (f1 == nil)?0:-1; + if(f1 == nil) + return 1; + n0 = FRAGSIZE(f0); + n1 = FRAGSIZE(f1); + } + } + return 0; /* for ken */ +} + + +static Frag * +fragalloc(Packet *p, int n, int pos, Frag *next) +{ + Frag *f, *ef; + Mem *m; + + /* look for local frag */ + f = &p->local[0]; + ef = &p->local[NLocalFrag]; + for(;fstate == FragLocalFree) { + f->state = FragLocalAlloc; + goto Found; + } + } + lock(&freelist.lk); + f = freelist.frag; + if(f != nil) + freelist.frag = f->next; + else + freelist.nfrag++; + unlock(&freelist.lk); + + if(f == nil) { + f = vtbrk(sizeof(Frag)); + f->state = FragGlobal; + } + +Found: + f->next = next; + + if(n == 0){ + f->mem = 0; + f->rp = 0; + f->wp = 0; + return f; + } + + if(pos == PEnd && next == nil) + pos = PMiddle; + m = memalloc(n, pos); + f->mem = m; + f->rp = m->rp; + f->wp = m->wp; + return f; +} + +Packet* +packetforeign(uchar *buf, int n, void (*free)(void *a), void *a) +{ + Packet *p; + Frag *f; + + p = packetalloc(); + f = fragalloc(p, 0, 0, nil); + f->free = free; + f->a = a; + f->next = nil; + f->rp = buf; + f->wp = buf+n; + + p->first = f; + p->size = n; + return p; +} + +static Frag * +fragdup(Packet *p, Frag *f) +{ + Frag *ff; + Mem *m; + + m = f->mem; + + /* + * m->rp && m->wp can be out of date when ref == 1 + * also, potentially reclaims space from previous frags + */ + if(m && m->ref == 1) { + m->rp = f->rp; + m->wp = f->wp; + } + + ff = fragalloc(p, 0, 0, nil); + *ff = *f; + if(m){ + lock(&m->lk); + m->ref++; + unlock(&m->lk); + } + return ff; +} + + +static void +fragfree(Frag *f) +{ + if(f->mem == nil){ + if(f->free) + (*f->free)(f->a); + }else{ + memfree(f->mem); + f->mem = 0; + } + + if(f->state == FragLocalAlloc) { + f->state = FragLocalFree; + return; + } + + lock(&freelist.lk); + f->next = freelist.frag; + freelist.frag = f; + unlock(&freelist.lk); +} + +static Mem * +memalloc(int n, int pos) +{ + Mem *m; + int nn; + + if(n < 0 || n > MaxFragSize) { + werrstr(EPacketSize); + return 0; + } + if(n <= SmallMemSize) { + lock(&freelist.lk); + m = freelist.smallmem; + if(m != nil) + freelist.smallmem = m->next; + else + freelist.nsmallmem++; + unlock(&freelist.lk); + nn = SmallMemSize; + } else { + lock(&freelist.lk); + m = freelist.bigmem; + if(m != nil) + freelist.bigmem = m->next; + else + freelist.nbigmem++; + unlock(&freelist.lk); + nn = BigMemSize; + } + + if(m == nil) { + m = vtbrk(sizeof(Mem)); + m->bp = vtbrk(nn); + m->ep = m->bp + nn; + } + assert(m->ref == 0); + m->ref = 1; + + switch(pos) { + default: + assert(0); + case PFront: + m->rp = m->bp; + break; + case PMiddle: + /* leave a little bit at end */ + m->rp = m->ep - n - 32; + break; + case PEnd: + m->rp = m->ep - n; + break; + } + /* check we did not blow it */ + if(m->rp < m->bp) + m->rp = m->bp; + m->wp = m->rp + n; + assert(m->rp >= m->bp && m->wp <= m->ep); + return m; +} + +static void +memfree(Mem *m) +{ + lock(&m->lk); + m->ref--; + if(m->ref > 0) { + unlock(&m->lk); + return; + } + unlock(&m->lk); + assert(m->ref == 0); + + switch(m->ep - m->bp) { + default: + assert(0); + case SmallMemSize: + lock(&freelist.lk); + m->next = freelist.smallmem; + freelist.smallmem = m; + unlock(&freelist.lk); + break; + case BigMemSize: + lock(&freelist.lk); + m->next = freelist.bigmem; + freelist.bigmem = m; + unlock(&freelist.lk); + break; + } +} + +static int +memhead(Mem *m, uchar *rp, int n) +{ + lock(&m->lk); + if(m->rp != rp) { + unlock(&m->lk); + return -1; + } + m->rp -= n; + unlock(&m->lk); + return 0; +} + +static int +memtail(Mem *m, uchar *wp, int n) +{ + lock(&m->lk); + if(m->wp != wp) { + unlock(&m->lk); + return -1; + } + m->wp += n; + unlock(&m->lk); + return 0; +} diff --git a/src/libventi/queue.c b/src/libventi/queue.c new file mode 100644 index 00000000..6e9bfa8d --- /dev/null +++ b/src/libventi/queue.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include "queue.h" + +typedef struct Qel Qel; +struct Qel +{ + Qel *next; + void *p; +}; + +struct Queue +{ + int hungup; + QLock lk; + Rendez r; + Qel *head; + Qel *tail; +}; + +Queue* +_vtqalloc(void) +{ + Queue *q; + + q = vtmallocz(sizeof(Queue)); + q->r.l = &q->lk; + return q; +} + +int +_vtqsend(Queue *q, void *p) +{ + Qel *e; + + e = vtmalloc(sizeof(Qel)); + qlock(&q->lk); + if(q->hungup){ + werrstr("hungup queue"); + qunlock(&q->lk); + return -1; + } + e->p = p; + e->next = nil; + if(q->head == nil) + q->head = e; + else + q->tail->next = e; + q->tail = e; + rwakeup(&q->r); + qunlock(&q->lk); + return 0; +} + +void* +_vtqrecv(Queue *q) +{ + void *p; + Qel *e; + + qlock(&q->lk); + while(q->head == nil && !q->hungup) + rsleep(&q->r); + if(q->hungup){ + qunlock(&q->lk); + return nil; + } + e = q->head; + q->head = e->next; + qunlock(&q->lk); + p = e->p; + vtfree(e); + return p; +} + +void* +_vtnbqrecv(Queue *q) +{ + void *p; + Qel *e; + + qlock(&q->lk); + if(q->head == nil){ + qunlock(&q->lk); + return nil; + } + e = q->head; + q->head = e->next; + qunlock(&q->lk); + p = e->p; + vtfree(e); + return p; +} + +void +_vtqhangup(Queue *q) +{ + qlock(&q->lk); + q->hungup = 1; + rwakeupall(&q->r); + qunlock(&q->lk); +} diff --git a/src/libventi/queue.h b/src/libventi/queue.h new file mode 100644 index 00000000..99e08763 --- /dev/null +++ b/src/libventi/queue.h @@ -0,0 +1,6 @@ +typedef struct Queue Queue; +Queue *_vtqalloc(void); +int _vtqsend(Queue*, void*); +void *_vtqrecv(Queue*); +void _vtqhangup(Queue*); +void *_vtnbqrecv(Queue*); diff --git a/src/libventi/root.c b/src/libventi/root.c new file mode 100644 index 00000000..c9449af8 --- /dev/null +++ b/src/libventi/root.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include "cvt.h" + +static int +checksize(int n) +{ + if(n < 256 || n > VtMaxLumpSize) { + werrstr("bad block size"); + return -1; + } + return 0; +} + +void +vtrootpack(VtRoot *r, uchar *p) +{ + uchar *op = p; + + U16PUT(p, VtRootVersion); + p += 2; + memmove(p, r->name, sizeof(r->name)); + p += sizeof(r->name); + memmove(p, r->type, sizeof(r->type)); + p += sizeof(r->type); + memmove(p, r->score, VtScoreSize); + p += VtScoreSize; + U16PUT(p, r->blocksize); + p += 2; + memmove(p, r->prev, VtScoreSize); + p += VtScoreSize; + + assert(p-op == VtRootSize); +} + +int +vtrootunpack(VtRoot *r, uchar *p) +{ + uchar *op = p; + uint vers; + memset(r, 0, sizeof(*r)); + + vers = U16GET(p); + if(vers != VtRootVersion) { + werrstr("unknown root version"); + return 0; + } + p += 2; + memmove(r->name, p, sizeof(r->name)); + r->name[sizeof(r->name)-1] = 0; + p += sizeof(r->name); + memmove(r->type, p, sizeof(r->type)); + r->type[sizeof(r->type)-1] = 0; + p += sizeof(r->type); + memmove(r->score, p, VtScoreSize); + p += VtScoreSize; + r->blocksize = U16GET(p); + if(checksize(r->blocksize) < 0) + return -1; + p += 2; + memmove(r->prev, p, VtScoreSize); + p += VtScoreSize; + + assert(p-op == VtRootSize); + return 0; +} diff --git a/src/libventi/rpc.c b/src/libventi/rpc.c new file mode 100644 index 00000000..915a0243 --- /dev/null +++ b/src/libventi/rpc.c @@ -0,0 +1,155 @@ +/* + * Multiplexed Venti client. It would be nice if we + * could turn this into a generic library routine rather + * than keep it Venti specific. A user-level 9P client + * could use something like this too. + * + * This is a little more complicated than it might be + * because we want it to work well within and without libthread. + * + * The mux code is inspired by tra's, which is inspired by the Plan 9 kernel. + */ + +#include +#include +#include + +typedef struct Rwait Rwait; +struct Rwait +{ + Rendez r; + Packet *p; + int done; + int sleeping; +}; + +static int gettag(VtConn*, Rwait*); +static void puttag(VtConn*, Rwait*, int); +static void muxrpc(VtConn*, Packet*); +Packet *vtrpc(VtConn*, Packet*); + +Packet* +vtrpc(VtConn *z, Packet *p) +{ + int i; + uchar tag, buf[2], *top; + Rwait *r; + + /* must malloc because stack could be private */ + r = vtmallocz(sizeof(Rwait)); + + qlock(&z->lk); + r->r.l = &z->lk; + tag = gettag(z, r); + + /* slam tag into packet */ + top = packetpeek(p, buf, 0, 2); + if(top == nil){ + packetfree(p); + return nil; + } + if(top == buf){ + werrstr("first two bytes must be in same packet fragment"); + packetfree(p); + return nil; + } + top[1] = tag; + qunlock(&z->lk); + if(vtsend(z, p) < 0) + return nil; + + qlock(&z->lk); + /* wait for the muxer to give us our packet */ + r->sleeping = 1; + z->nsleep++; + while(z->muxer && !r->done) + rsleep(&r->r); + z->nsleep--; + r->sleeping = 0; + + /* if not done, there's no muxer: start muxing */ + if(!r->done){ + if(z->muxer) + abort(); + z->muxer = 1; + while(!r->done){ + qunlock(&z->lk); + if((p = vtrecv(z)) == nil){ + z->muxer = 0; + return nil; + } + qlock(&z->lk); + muxrpc(z, p); + } + z->muxer = 0; + /* if there is anyone else sleeping, wake them to mux */ + if(z->nsleep){ + for(i=0; i<256; i++) + if(z->wait[i] != nil && ((Rwait*)z->wait[i])->sleeping) + break; + if(i==256) + fprint(2, "libventi: nsleep botch\n"); + else + rwakeup(&((Rwait*)z->wait[i])->r); + } + } + + p = r->p; + puttag(z, r, tag); + vtfree(r); + qunlock(&z->lk); + return p; +} + +static int +gettag(VtConn *z, Rwait *r) +{ + int i; + +Again: + while(z->ntag == 256) + rsleep(&z->tagrend); + for(i=0; i<256; i++) + if(z->wait[i] == 0){ + z->ntag++; + z->wait[i] = r; + return i; + } + fprint(2, "libventi: ntag botch\n"); + goto Again; +} + +static void +puttag(VtConn *z, Rwait *r, int tag) +{ + assert(z->wait[tag] == r); + z->wait[tag] = nil; + z->ntag--; + rwakeup(&z->tagrend); +} + +static void +muxrpc(VtConn *z, Packet *p) +{ + uchar tag, buf[2], *top; + Rwait *r; + + if((top = packetpeek(p, buf, 0, 2)) == nil){ + fprint(2, "libventi: short packet in vtrpc\n"); + packetfree(p); + return; + } + + tag = top[1]; + if((r = z->wait[tag]) == nil){ + fprint(2, "libventi: unexpected packet tag %d in vtrpc\n", tag); +abort(); + packetfree(p); + return; + } + + r->p = p; + r->done = 1; + rwakeup(&r->r); +} + diff --git a/src/libventi/scorefmt.c b/src/libventi/scorefmt.c new file mode 100644 index 00000000..496f85b7 --- /dev/null +++ b/src/libventi/scorefmt.c @@ -0,0 +1,18 @@ +#include +#include +#include + +int +vtscorefmt(Fmt *f) +{ + uchar *v; + int i; + + v = va_arg(f->args, uchar*); + if(v == nil) + fmtprint(f, "*"); + else + for(i = 0; i < VtScoreSize; i++) + fmtprint(f, "%2.2ux", v[i]); + return 0; +} diff --git a/src/libventi/send.c b/src/libventi/send.c new file mode 100644 index 00000000..a72a6c23 --- /dev/null +++ b/src/libventi/send.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include "queue.h" + +static int +_vtsend(VtConn *z, Packet *p) +{ + IOchunk ioc; + int n; + uchar buf[2]; + + if(z->state != VtStateConnected) { + werrstr("session not connected"); + return -1; + } + + /* add framing */ + n = packetsize(p); + if(n >= (1<<16)) { + werrstr("packet too large"); + packetfree(p); + return -1; + } + buf[0] = n>>8; + buf[1] = n; + packetprefix(p, buf, 2); + + for(;;){ + n = packetfragments(p, &ioc, 1, 0); + if(n == 0) + break; + if(write(z->outfd, ioc.addr, ioc.len) < ioc.len){ + packetfree(p); + return 0; + } + packetconsume(p, nil, ioc.len); + } + packetfree(p); + return 1; +} + +static Packet* +_vtrecv(VtConn *z) +{ + uchar buf[10], *b; + int n; + Packet *p; + int size, len; + + if(z->state != VtStateConnected) { + werrstr("session not connected"); + return nil; + } + + p = z->part; + /* get enough for head size */ + size = packetsize(p); + while(size < 2) { + b = packettrailer(p, MaxFragSize); + assert(b != nil); + n = read(z->infd, b, MaxFragSize); + if(n <= 0) + goto Err; + size += n; + packettrim(p, 0, size); + } + + if(packetconsume(p, buf, 2) < 0) + goto Err; + len = (buf[0] << 8) | buf[1]; + size -= 2; + + while(size < len) { + n = len - size; + if(n > MaxFragSize) + n = MaxFragSize; + b = packettrailer(p, n); + if(readn(z->infd, b, n) != n) + goto Err; + size += n; + } + p = packetsplit(p, len); + return p; +Err: + return nil; +} + +/* + * If you fork off two procs running vtrecvproc and vtsendproc, + * then vtrecv/vtsend (and thus vtrpc) will never block except on + * rendevouses, which is nice when it's running in one thread of many. + */ +void +vtrecvproc(void *v) +{ + Packet *p; + VtConn *z; + Queue *q; + + z = v; + q = _vtqalloc(); + + qlock(&z->lk); + z->readq = q; + qlock(&z->inlk); + rwakeup(&z->rpcfork); + qunlock(&z->lk); + + while((p = _vtrecv(z)) != nil) + if(_vtqsend(q, p) < 0){ + packetfree(p); + break; + } + qunlock(&z->inlk); + qlock(&z->lk); + _vtqhangup(q); + while((p = _vtnbqrecv(q)) != nil) + packetfree(p); + vtfree(q); + z->readq = nil; + rwakeup(&z->rpcfork); + qunlock(&z->lk); + vthangup(z); +} + +void +vtsendproc(void *v) +{ + Queue *q; + Packet *p; + VtConn *z; + + z = v; + q = _vtqalloc(); + + qlock(&z->lk); + z->writeq = q; + qlock(&z->outlk); + rwakeup(&z->rpcfork); + qunlock(&z->lk); + + while((p = _vtqrecv(q)) != nil) + if(_vtsend(z, p) < 0) + break; + qunlock(&z->outlk); + qlock(&z->lk); + _vtqhangup(q); + while((p = _vtnbqrecv(q)) != nil) + packetfree(p); + vtfree(q); + z->writeq = nil; + rwakeup(&z->rpcfork); + qunlock(&z->lk); + return; +} + +Packet* +vtrecv(VtConn *z) +{ + Packet *p; + + qlock(&z->lk); + if(z->state != VtStateConnected){ + werrstr("not connected"); + qunlock(&z->lk); + return nil; + } + if(z->readq){ + qunlock(&z->lk); + return _vtqrecv(z->readq); + } + + qlock(&z->inlk); + qunlock(&z->lk); + p = _vtrecv(z); + qunlock(&z->inlk); + if(!p) + vthangup(z); + return p; +} + +int +vtsend(VtConn *z, Packet *p) +{ + qlock(&z->lk); + if(z->state != VtStateConnected){ + packetfree(p); + werrstr("not connected"); + qunlock(&z->lk); + return -1; + } + if(z->writeq){ + qunlock(&z->lk); + if(_vtqsend(z->writeq, p) < 0){ + packetfree(p); + return -1; + } + return 0; + } + + qlock(&z->outlk); + qunlock(&z->lk); + if(_vtsend(z, p) < 0){ + qunlock(&z->outlk); + vthangup(z); + return -1; + } + qunlock(&z->outlk); + return 0; +} + diff --git a/src/libventi/server.c b/src/libventi/server.c new file mode 100644 index 00000000..60d253df --- /dev/null +++ b/src/libventi/server.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include "queue.h" + +enum +{ + STACK = 8192, +}; + +typedef struct VtSconn VtSconn; +struct VtSconn +{ + int ctl; + char dir[NETPATHLEN]; + VtSrv *srv; + VtConn *c; +}; + +struct VtSrv +{ + int afd; + int dead; + char adir[NETPATHLEN]; + Queue *q; /* Queue(VtReq*) */ +}; + +static void listenproc(void*); +static void connproc(void*); + +VtSrv* +vtlisten(char *addr) +{ + VtSrv *s; + + s = vtmallocz(sizeof(VtSrv)); + s->afd = announce(addr, s->adir); + if(s->afd < 0){ + free(s); + return nil; + } + s->q = _vtqalloc(); + proccreate(listenproc, s, STACK); + return s; +} + +static void +listenproc(void *v) +{ + int ctl; + char dir[NETPATHLEN]; + VtSrv *srv; + VtSconn *sc; + + srv = v; + for(;;){ + ctl = listen(srv->adir, dir); + if(ctl < 0){ + srv->dead = 1; + break; + } + sc = vtmallocz(sizeof(VtSconn)); + sc->ctl = ctl; + sc->srv = srv; + strcpy(sc->dir, dir); + proccreate(connproc, sc, STACK); + } + + // hangup +} + +static void +connproc(void *v) +{ + VtSconn *sc; + VtConn *c; + Packet *p; + VtReq *r; + int fd; + + r = nil; + c = nil; + sc = v; + fprint(2, "new call %s on %d\n", sc->dir, sc->ctl); + fd = accept(sc->ctl, sc->dir); + close(sc->ctl); + if(fd < 0){ + fprint(2, "accept %s: %r\n", sc->dir); + goto out; + } + + c = vtconn(fd, fd); + sc->c = c; + if(vtversion(c) < 0){ + fprint(2, "vtversion %s: %r\n", sc->dir); + goto out; + } + if(vtsrvhello(c) < 0){ + fprint(2, "vtsrvhello %s: %r\n", sc->dir); + goto out; + } + + fprint(2, "new proc %s\n", sc->dir); + proccreate(vtsendproc, c, STACK); + qlock(&c->lk); + while(!c->writeq) + rsleep(&c->rpcfork); + qunlock(&c->lk); + + while((p = vtrecv(c)) != nil){ + r = vtmallocz(sizeof(VtReq)); + if(vtfcallunpack(&r->tx, p) < 0){ + packetfree(p); + fprint(2, "bad packet on %s: %r\n", sc->dir); + continue; + } + packetfree(p); + if(r->tx.type == VtTgoodbye) + break; + r->rx.tag = r->tx.tag; + r->sc = sc; + if(_vtqsend(sc->srv->q, r) < 0){ + fprint(2, "hungup queue\n"); + break; + } + r = nil; + } + + fprint(2, "eof on %s\n", sc->dir); + +out: + if(r){ + vtfcallclear(&r->tx); + vtfree(r); + } + if(c) + vtfreeconn(c); + fprint(2, "freed %s\n", sc->dir); + vtfree(sc); + return; +} + +VtReq* +vtgetreq(VtSrv *srv) +{ + return _vtqrecv(srv->q); +} + +void +vtrespond(VtReq *r) +{ + Packet *p; + VtSconn *sc; + + sc = r->sc; + if(r->rx.tag != r->tx.tag) + abort(); + if(r->rx.type != r->tx.type+1 && r->rx.type != VtRerror) + abort(); + if((p = vtfcallpack(&r->rx)) == nil){ + fprint(2, "fcallpack on %s: %r\n", sc->dir); + packetfree(p); + vtfcallclear(&r->rx); + return; + } + vtsend(sc->c, p); + vtfcallclear(&r->tx); + vtfcallclear(&r->rx); + vtfree(r); +} + diff --git a/src/libventi/srvhello.c b/src/libventi/srvhello.c new file mode 100644 index 00000000..20aedbee --- /dev/null +++ b/src/libventi/srvhello.c @@ -0,0 +1,50 @@ +#include +#include +#include + +int +vtsrvhello(VtConn *z) +{ + VtFcall tx, rx; + Packet *p; + + if((p = vtrecv(z)) == nil) + return 0; + + if(vtfcallunpack(&tx, p) < 0){ + packetfree(p); + return 0; + } + packetfree(p); + + if(tx.type != VtThello){ + vtfcallclear(&tx); + werrstr("bad packet type %d; want Thello %d", tx.type, VtThello); + return 0; + } + if(tx.tag != 0){ + vtfcallclear(&tx); + werrstr("bad tag in hello"); + return 0; + } + if(strcmp(tx.version, z->version) != 0){ + vtfcallclear(&tx); + werrstr("bad version in hello"); + return 0; + } + vtfree(z->uid); + z->uid = tx.uid; + tx.uid = nil; + vtfcallclear(&tx); + + memset(&rx, 0, sizeof rx); + rx.type = VtRhello; + rx.tag = tx.tag; + rx.sid = "anonymous"; + if((p = vtfcallpack(&rx)) == nil) + return 0; + if(vtsend(z, p) < 0) + return 0; + + return 1; +} diff --git a/src/libventi/strdup.c b/src/libventi/strdup.c new file mode 100644 index 00000000..e191c390 --- /dev/null +++ b/src/libventi/strdup.c @@ -0,0 +1,18 @@ +#include +#include +#include + +char* +vtstrdup(char *s) +{ + int n; + char *ss; + + if(s == nil) + return nil; + n = strlen(s) + 1; + ss = vtmalloc(n); + memmove(ss, s, n); + return ss; +} + diff --git a/src/libventi/string.c b/src/libventi/string.c new file mode 100644 index 00000000..9763149a --- /dev/null +++ b/src/libventi/string.c @@ -0,0 +1,50 @@ +#include +#include +#include + +int +vtputstring(Packet *p, char *s) +{ + uchar buf[2]; + int n; + + if(s == nil){ + werrstr("null string in packet"); + return -1; + } + n = strlen(s); + if(n > VtMaxStringSize){ + werrstr("string too long in packet"); + return -1; + } + buf[0] = n>>8; + buf[1] = n; + packetappend(p, buf, 2); + packetappend(p, (uchar*)s, n); + return 0; +} + +int +vtgetstring(Packet *p, char **ps) +{ + uchar buf[2]; + int n; + char *s; + + if(packetconsume(p, buf, 2) < 0) + return -1; + n = (buf[0]<<8) + buf[1]; + if(n > VtMaxStringSize) { + werrstr("string too long in packet"); + return -1; + } + s = vtmalloc(n+1); + if(packetconsume(p, (uchar*)s, n) < 0){ + vtfree(s); + return -1; + } + s[n] = 0; + *ps = s; + return 0; +} + diff --git a/src/libventi/version.c b/src/libventi/version.c new file mode 100644 index 00000000..dbbc4dc5 --- /dev/null +++ b/src/libventi/version.c @@ -0,0 +1,115 @@ +#include +#include +#include + +static char *okvers[] = { + "02", + nil, +}; + +/* +static char EBigString[] = "string too long"; +static char EBigPacket[] = "packet too long"; +static char ENullString[] = "missing string"; +*/ +static char EBadVersion[] = "bad format in version string"; + +static int +vtreadversion(VtConn *z, char *q, char *v, int nv) +{ + int n; + + for(;;){ + if(nv <= 1){ + werrstr("version too long"); + return -1; + } + n = read(z->infd, v, 1); + if(n <= 0){ + if(n == 0) + werrstr("unexpected eof"); + return -1; + } + if(*v == '\n'){ + *v = 0; + break; + } + if((uchar)*v < ' ' || (uchar)*v > 0x7f || (*q && *v != *q)){ + werrstr(EBadVersion); + return -1; + } + v++; + nv--; + if(*q) + q++; + } + return 0; +} + +int +vtversion(VtConn *z) +{ + char buf[VtMaxStringSize], *p, *ep, *prefix, *pp; + int i; + + qlock(&z->lk); + if(z->state != VtStateAlloc){ + werrstr("bad session state"); + qunlock(&z->lk); + return -1; + } + + qlock(&z->inlk); + qlock(&z->outlk); + + p = buf; + ep = buf + sizeof buf; + prefix = "venti-"; + p = seprint(p, ep, "%s", prefix); + p += strlen(p); + for(i=0; okvers[i]; i++) + p = seprint(p, ep, "%s%s", i ? ":" : "", okvers[i]); + p = seprint(p, ep, "-libventi\n"); + assert(p-buf < sizeof buf); + + if(write(z->outfd, buf, p-buf) != p-buf) + goto Err; + vtdebug(z, "version string out: %s", buf); + + if(vtreadversion(z, prefix, buf, sizeof buf) < 0) + goto Err; + vtdebug(z, "version string in: %s", buf); + + p = buf+strlen(prefix); + for(;;){ + pp = strpbrk(p, ":-"); + for(i=0; okvers[i]; i++) + if(strlen(okvers[i]) == pp-p && memcmp(okvers[i], p, pp-p) == 0){ + *pp = 0; + z->version = vtstrdup(p); + goto Okay; + } + } + werrstr("unable to negotiate version"); + goto Err; + +Okay: + z->state = VtStateConnected; + qunlock(&z->inlk); + qunlock(&z->outlk); + qunlock(&z->lk); + return 0; + +Err: + if(z->infd >= 0) + close(z->infd); + if(z->outfd >= 0 && z->outfd != z->infd) + close(z->outfd); + z->infd = -1; + z->outfd = -1; + z->state = VtStateClosed; + qunlock(&z->inlk); + qunlock(&z->outlk); + qunlock(&z->lk); + return -1; +} diff --git a/src/libventi/zero.c b/src/libventi/zero.c new file mode 100644 index 00000000..c40aea96 --- /dev/null +++ b/src/libventi/zero.c @@ -0,0 +1,55 @@ +#include +#include +#include + +void +vtzeroextend(int type, uchar *buf, uint n, uint nn) +{ + uchar *p, *ep; + + switch(type&7) { + case 0: + memset(buf+n, 0, nn-n); + break; + default: + p = buf + (n/VtScoreSize)*VtScoreSize; + ep = buf + (nn/VtScoreSize)*VtScoreSize; + while(p < ep) { + memmove(p, vtzeroscore, VtScoreSize); + p += VtScoreSize; + } + memset(p, 0, buf+nn-p); + break; + } +} + +uint +vtzerotruncate(int type, uchar *buf, uint n) +{ + uchar *p; + + if(type == VtRootType){ + if(n < VtRootSize) + return n; + return VtRootSize; + } + + switch(type&7){ + case 0: + for(p = buf + n; p > buf; p--) { + if(p[-1] != 0) + break; + } + return p - buf; + default: + /* ignore slop at end of block */ + p = buf + (n/VtScoreSize)*VtScoreSize; + + while(p > buf) { + if(memcmp(p - VtScoreSize, vtzeroscore, VtScoreSize) != 0) + break; + p -= VtScoreSize; + } + return p - buf; + } +} diff --git a/src/libventi/zeroscore.c b/src/libventi/zeroscore.c new file mode 100644 index 00000000..6f22d72d --- /dev/null +++ b/src/libventi/zeroscore.c @@ -0,0 +1,10 @@ +#include +#include +#include + +/* score of a zero length block */ +uchar vtzeroscore[VtScoreSize] = { + 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, + 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 +}; + -- cgit v1.2.3