Initial import.
authorfredrik <fredrik@959494ce-11ee-0310-bf91-de5d638817bd>
Thu, 20 Jul 2006 13:15:32 +0000 (13:15 +0000)
committerfredrik <fredrik@959494ce-11ee-0310-bf91-de5d638817bd>
Thu, 20 Jul 2006 13:15:32 +0000 (13:15 +0000)
git-svn-id: svn+ssh://svn.dolda2000.com/srv/svn/repos/src/vcfs@673 959494ce-11ee-0310-bf91-de5d638817bd

16 files changed:
Makefile [new file with mode: 0644]
blocktree.c [new file with mode: 0644]
blocktree.h [new file with mode: 0644]
filestore.c [new file with mode: 0644]
log.c [new file with mode: 0644]
log.h [new file with mode: 0644]
mkfs.vc.c [new file with mode: 0644]
mkstore.c [new file with mode: 0644]
store.c [new file with mode: 0644]
store.h [new file with mode: 0644]
storeget.c [new file with mode: 0644]
storeput.c [new file with mode: 0644]
utils.c [new file with mode: 0644]
utils.h [new file with mode: 0644]
vcfs.c [new file with mode: 0644]
vcfs.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..83686b5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+CFLAGS=-g -Wall
+
+all: storeget storeput mkstore mkfs.vc vcfs
+
+storeget: storeget.o store.o filestore.o log.o utils.o
+       gcc $(CFLAGS) -o $@ $^ -lgcrypt
+
+storeput: storeput.o store.o filestore.o log.o utils.o
+       gcc $(CFLAGS) -o $@ $^ -lgcrypt
+
+mkstore: mkstore.o store.o filestore.o log.o utils.o
+       gcc $(CFLAGS) -o $@ $^ -lgcrypt
+
+mkfs.vc: mkfs.vc.o store.o filestore.o log.o blocktree.o utils.o
+       gcc $(CFLAGS) -o $@ $^ -lgcrypt
+
+vcfs: vcfs.o store.o filestore.o log.o blocktree.o utils.o
+       gcc $(CFLAGS) -o $@ $^ -lgcrypt -lfuse
+
+vcfs.o: vcfs.c
+       gcc -c $(CFLAGS) -o $@ $< -DFUSE_USE_VERSION=26 -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse
+
+%.o: %.c
+       gcc -c $(CFLAGS) -o $@ $<
+
+clean:
+       rm -f *.o storeget storeput mkstore mkfs.vc vcfs
diff --git a/blocktree.c b/blocktree.c
new file mode 100644 (file)
index 0000000..8a3c36e
--- /dev/null
@@ -0,0 +1,298 @@
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include "store.h"
+#include "blocktree.h"
+
+#define min(a, b) (((b) < (a))?(b):(a))
+
+ssize_t btget(struct store *st, struct btnode *tree, block_t bl, void *buf, size_t len)
+{
+    int d;
+    block_t c, sel;
+    struct btnode indir[BT_INDSZ];
+    ssize_t sz;
+    
+    if(tree->d == 0) {
+       errno = ERANGE;
+       return(-1);
+    }
+    while(1) {
+       d = tree->d & 0x7f;
+       /* This check should really only be necessary on the first
+        * iteration, but I felt it was easier to put it in the
+        * loop. */
+       if((bl >> (d * BT_INDBITS)) > 0) {
+           errno = ERANGE;
+           return(-1);
+       }
+       
+       if(d == 0)
+           return(storeget(st, buf, len, &tree->a));
+       
+       /* Luckily, this is tail recursive */
+       if((sz = storeget(st, indir, BT_INDBSZ, &tree->a)) < 0)
+           return(-1);
+       c = sz / sizeof(struct btnode);
+       sel = bl >> ((d - 1) * BT_INDBITS);
+       if(sel >= c) {
+           errno = ERANGE;
+           return(-1);
+       }
+       tree = &indir[sel];
+       bl &= (1LL << ((d - 1) * BT_INDBITS)) - 1;
+    }
+    return(0);
+}
+
+static int btputleaf(struct store *st, struct btnode *leaf, struct btop *op, block_t bloff)
+{
+    void *buf;
+    struct addr na;
+    int ret;
+    
+    buf = NULL;
+    if(op->buf == NULL) {
+       buf = op->buf = malloc(op->len);
+       if(op->fillfn(buf, op->len, op->pdata))
+           return(-1);
+    }
+    ret = storeput(st, op->buf, op->len, &na);
+    if(buf != NULL)
+       free(buf);
+    if(ret)
+       return(-1);
+    leaf->d = 0x80;
+    leaf->a = na;
+    return(0);
+}
+
+static int countops(struct btop *ops, int numops, block_t bloff, block_t maxbl)
+{
+    int i;
+    
+    for(i = 0; i < numops; i++) {
+       if(ops[i].blk - bloff >= maxbl)
+           break;
+    }
+    return(i);
+}
+
+/*
+ * blputmany() in many ways makes the code uglier, but it saves a
+ * *lot* of space, since it doesn't need to store intermediary blocks.
+ */
+static int btputmany2(struct store *st, struct btnode *tree, struct btop *ops, int numops, block_t bloff)
+{
+    int i, subops, d, f, hasid;
+    block_t c, sel, bl, nextsz;
+    struct addr na;
+    struct btnode indir[BT_INDSZ];
+    ssize_t sz;
+    
+    d = tree->d & 0x7f;
+    f = tree->d & 0x80;
+
+    hasid = 0;
+    
+    for(i = 0; i < numops; ) {
+       bl = ops[i].blk - bloff;
+    
+       if((d == 0) && (bl == 0)) {
+           if(btputleaf(st, tree, ops, bloff))
+               return(-1);
+           i++;
+           continue;
+       }
+    
+       if(f && (bl == (1LL << (d * BT_INDBITS)))) {
+           /* New level of indirection */
+           if(hasid) {
+               if(storeput(st, indir, c * sizeof(struct btnode), &na))
+                   return(-1);
+               tree->a = na;
+           }
+           indir[0] = *tree;
+           tree->d = ++d;
+           f = 0;
+           c = 1;
+           hasid = 1;
+       } else if(d == 0) {
+           /* New tree */
+           if(bl != 0) {
+               errno = ERANGE;
+               return(-1);
+           }
+           /* Assume that numops == largest block number + 1 -- gaps
+            * will be detected as errors later */
+           for(bl = numops - 1; bl > 0; d++, bl <<= BT_INDBITS);
+           tree->d = d;
+           c = 0;
+           hasid = 1;
+       } else {
+           /* Get indirect block */
+           if(!hasid) {
+               if((sz = storeget(st, indir, BT_INDBSZ, &tree->a)) < 0)
+                   return(-1);
+               c = sz / sizeof(struct btnode);
+               hasid = 1;
+           }
+       }
+
+       sel = bl >> ((d - 1) * BT_INDBITS);
+       if(sel > c) {
+           errno = ERANGE;
+           return(-1);
+       }
+    
+       if(sel == c) {
+           /* Append new */
+           if((c > 0) && (!(indir[c - 1].d & 0x80) || ((indir[c - 1].d & 0x7f) < (d - 1)))) {
+               errno = ERANGE;
+               return(-1);
+           }
+           indir[c].d = 0;
+           c++;
+       }
+       nextsz = 1LL << ((d - 1) * BT_INDBITS);
+       subops = countops(ops + i, numops - i, bloff + (sel * nextsz), nextsz);
+       if(btputmany2(st, &indir[sel], ops + i, subops, bloff + (sel * nextsz)))
+           return(-1);
+       i += subops;
+       
+       if((sel == BT_INDSZ - 1) && (indir[sel].d == ((d - 1) | 0x80))) {
+           tree->d |= 0x80;
+           f = 1;
+       }
+    }
+    if(hasid) {
+       if(storeput(st, indir, c * sizeof(struct btnode), &na))
+           return(-1);
+       tree->a = na;
+    }
+    return(0);
+}
+
+int btputmany(struct store *st, struct btnode *tree, struct btop *ops, int numops)
+{
+    return(btputmany2(st, tree, ops, numops, 0));
+}
+
+int btput(struct store *st, struct btnode *tree, block_t bl, void *buf, size_t len)
+{
+    struct btop ops[1];
+    
+    ops[0].blk = bl;
+    ops[0].buf = buf;
+    ops[0].len = len;
+    return(btputmany(st, tree, ops, 1));
+}
+
+void btmkop(struct btop *op, block_t bl, void *buf, size_t len)
+{
+    memset(op, 0, sizeof(*op));
+    op->blk = bl;
+    op->buf = buf;
+    op->len = len;
+}
+
+static int opcmp(const struct btop **op1, const struct btop **op2)
+{
+    return((*op1)->blk - (*op2)->blk);
+}
+
+void btsortops(struct btop *ops, int numops)
+{
+    qsort(ops, numops, sizeof(*ops), (int (*)(const void *, const void *))opcmp);
+}
+
+/*
+Obsoleted
+
+int btappend(struct store *st, struct btnode *tree, void *buf, size_t len)
+{
+    int d, f;
+    struct btnode indir[BT_INDSZ];
+    struct addr na;
+    block_t c;
+    ssize_t sz;
+    
+    d = tree->d & 0x7f;
+    f = tree->d & 0x80;
+    
+    if(f) {
+       if(storeput(st, buf, len, &na))
+           return(-1);
+       indir[0] = *tree;
+       indir[1].d = 0x80;
+       indir[1].a = na;
+       if(storeput(st, indir, 2 * sizeof(*indir), &na))
+           return(-1);
+       tree->d = d + 1;
+       tree->a = na;
+       return(0);
+    }
+    
+    if(d == 0) {
+       if(storeput(st, buf, len, &na))
+           return(-1);
+       tree->d |= 0x80;
+       tree->a = na;
+       return(0);
+    }
+    
+    if((sz = storeget(st, indir, BT_INDBSZ, &tree->a)) < 0)
+       return(-1);
+    c = sz / sizeof(struct btnode);
+    if(!(indir[c - 1].d & 0x80) || ((indir[c - 1].d & 0x7f) < (d - 1))) {
+       if(btappend(st, &indir[c - 1], buf, len))
+           return(-1);
+       if(storeput(st, indir, sz, &na))
+           return(-1);
+       tree->a = na;
+       if((indir[c - 1].d & 0x80) && ((indir[c - 1].d & 0x7f) == (d - 1)))
+           tree->d |= 0x80;
+       return(0);
+    }
+    
+    if(storeput(st, buf, len, &na))
+       return(-1);
+    indir[c].d = 0x80;
+    indir[c].a = na;
+    if(storeput(st, indir, sz + sizeof(struct btnode), &na))
+       return(-1);
+    tree->a = na;
+    return(0);
+}
+*/
+
+block_t btcount(struct store *st, struct btnode *tree)
+{
+    int d, f;
+    struct btnode indir[BT_INDSZ];
+    block_t c, ret;
+    ssize_t sz;
+    
+    d = tree->d & 0x7f;
+    f = tree->d & 0x80;
+    
+    if(f)
+       return(1LL << (d * BT_INDBITS));
+    
+    if(d == 0)
+       return(0);
+    
+    ret = 0;
+    while(1) {
+       if((sz = storeget(st, indir, BT_INDBSZ, &tree->a)) < 0)
+           return(-1);
+       c = sz / sizeof(struct btnode);
+       ret += (c - 1) * (1LL << ((d - 1) * BT_INDBITS));
+       d = indir[c - 1].d & 0x7f;
+       f = indir[c - 1].d & 0x80;
+       if(f)
+           return(ret + (1LL << (d * BT_INDBITS)));
+       tree = &indir[c - 1];
+    }
+}
diff --git a/blocktree.h b/blocktree.h
new file mode 100644 (file)
index 0000000..c962874
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef _BLOCKTREE_H
+#define _BLOCKTREE_H
+
+#include "store.h"
+#include "inttypes.h"
+
+#define BT_INDBITS 10
+#define BT_INDSZ (1 << BT_INDBITS)
+#define BT_INDBSZ (sizeof(struct btnode) * BT_INDSZ)
+
+typedef loff_t block_t;
+
+struct btnode {
+    u_int8_t d;
+    struct addr a;
+};
+
+struct btop {
+    block_t blk;
+    void *buf;
+    size_t len;
+    int (*fillfn)(void *buf, size_t len, void *pdata);
+    void *pdata;
+};
+
+ssize_t btget(struct store *st, struct btnode *tree, block_t bl, void *buf, size_t len);
+int btputmany(struct store *st, struct btnode *tree, struct btop *ops, int numops);
+int btput(struct store *st, struct btnode *tree, block_t bl, void *buf, size_t len);
+block_t btcount(struct store *st, struct btnode *tree);
+void btsortops(struct btop *ops, int numops);
+void btmkop(struct btop *op, block_t bl, void *buf, size_t len);
+
+#endif
diff --git a/filestore.c b/filestore.c
new file mode 100644 (file)
index 0000000..51089a8
--- /dev/null
@@ -0,0 +1,360 @@
+#define _LARGEFILE64_SOURCE
+#define _XOPEN_SOURCE 500
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <gcrypt.h>
+#include <assert.h>
+
+#include "utils.h"
+#include "store.h"
+#include "log.h"
+
+#define LOGMAGIC "Dolda/Venti-1"
+#define IDXMAGIC "Dolda/Index-1"
+#define LOGENTMAGIC "\xca\xe5\x7a\x93"
+
+typedef loff_t idx_t;
+
+struct loghdr {
+    char magic[sizeof(LOGMAGIC)];
+};
+
+struct idxhdr {
+    char magic[sizeof(IDXMAGIC)];
+    u_int64_t size;
+};
+
+struct idxent {
+    struct addr addr;
+    u_int64_t l, r;
+    u_int64_t off;
+};
+
+struct logent {
+    u_int8_t magic[4];
+    struct addr name;
+    u_int16_t len;
+    u_int8_t fl;
+};
+
+struct fstore {
+    int logfd;
+    int idxfd;
+    loff_t logsize;
+    idx_t idxsize;
+};
+
+static int release(struct fstore *fst)
+{
+    if(fst->logfd >= 0) {
+       fsync(fst->logfd);
+       close(fst->logfd);
+    }
+    if(fst->idxfd >= 0) {
+       fsync(fst->idxfd);
+       close(fst->idxfd);
+    }
+    free(fst);
+    return(0);
+}
+
+static int releaseg(struct store *st)
+{
+    return(release(st->pdata));
+}
+
+static void hash(const void *buf, size_t len, struct addr *a)
+{
+    gcry_md_hash_buffer(GCRY_MD_SHA256, a->hash, buf, len);
+}
+
+static int getidx(struct fstore *fst, idx_t i, struct idxent *ie)
+{
+    return(readall(fst->idxfd, ie, sizeof(*ie), sizeof(struct idxhdr) + i * sizeof(struct idxent)));
+}
+
+static int putidx(struct fstore *fst, idx_t i, struct idxent *ie)
+{
+    return(writeall(fst->idxfd, ie, sizeof(*ie), sizeof(struct idxhdr) + i * sizeof(struct idxent)));
+}
+
+static idx_t lookup(struct fstore *fst, struct addr *a, idx_t *parent)
+{
+    idx_t i;
+    struct idxent ie;
+    int c;
+    
+    if(fst->idxsize == 0) {
+       if(parent != NULL)
+           *parent = -1;
+       return(-1);
+    }
+    i = 0;
+    while(1) {
+       assert(!getidx(fst, i, &ie));
+       c = addrcmp(a, &ie.addr);
+       if(c < 0) {
+           if(ie.l == 0) {
+               if(parent != NULL)
+                   *parent = i;
+               return(-1);
+           }
+           i = ie.l;
+       } else if(c > 0) {
+           if(ie.r == 0) {
+               if(parent != NULL)
+                   *parent = i;
+               return(-1);
+           }
+           i = ie.r;
+       } else {
+           return(i);
+       }
+    }
+}
+
+static idx_t newindex(struct fstore *fst)
+{
+    size_t newsize;
+    idx_t ni;
+    struct idxent ne;
+    struct idxhdr ih;
+    
+    /* XXX: Thread safety */
+    ni = fst->idxsize++;
+    newsize = sizeof(struct idxhdr) + fst->idxsize * sizeof(struct idxent);
+    if(ftruncate(fst->idxfd, newsize))
+       return(-1);
+    ne.l = ne.r = 0;
+    assert(!putidx(fst, ni, &ne));
+    assert(!readall(fst->idxfd, &ih, sizeof(ih), 0));
+    ih.size = fst->idxsize;
+    assert(!writeall(fst->idxfd, &ih, sizeof(ih), 0));
+    return(ni);
+}
+
+static int put(struct store *st, const void *buf, size_t len, struct addr *at)
+{
+    struct fstore *fst;
+    struct addr pa;
+    idx_t i, pi;
+    struct idxent ie;
+    loff_t leoff;
+    int c;
+    struct logent le;
+    
+    if(len > STORE_MAXBLSZ) {
+       errno = E2BIG;
+       return(-1);
+    }
+
+    fst = st->pdata;
+    hash(buf, len, &pa);
+    if(at != NULL)
+       memcpy(at->hash, pa.hash, 32);
+    
+    if(lookup(fst, &pa, &pi) != -1)
+       return(0);
+    
+    memcpy(le.magic, LOGENTMAGIC, 4);
+    le.name = pa;
+    le.len = len;
+    le.fl = 0;
+    /* XXX: Thread safety { */
+    leoff = fst->logsize;
+    fst->logsize += sizeof(le) + len;
+    /* } */
+    /* XXX: Handle data with embedded LOGENTMAGIC */
+    writeall(fst->logfd, &le, sizeof(le), leoff);
+    writeall(fst->logfd, buf, len, leoff + sizeof(le));
+
+    i = newindex(fst);
+    assert(!getidx(fst, i, &ie));
+    ie.addr = pa;
+    ie.off = leoff;
+    assert(!putidx(fst, i, &ie));
+    if(pi != -1) {
+       assert(!getidx(fst, pi, &ie));
+       c = addrcmp(&pa, &ie.addr);
+       if(c < 0)
+           ie.l = i;
+       else
+           ie.r = i;
+       assert(!putidx(fst, pi, &ie));
+    }
+    
+    return(0);
+}
+
+#define min(a, b) (((b) < (a))?(b):(a))
+
+static ssize_t get(struct store *st, void *buf, size_t len, struct addr *at)
+{
+    idx_t i;
+    struct idxent ie;
+    struct fstore *fst;
+    struct logent le;
+    struct addr v;
+    char tmpbuf[STORE_MAXBLSZ];
+    
+    fst = st->pdata;
+    if((i = lookup(fst, at, NULL)) == -1) {
+       errno = ENOENT;
+       return(-1);
+    }
+    assert(!getidx(fst, i, &ie));
+    
+    if(readall(fst->logfd, &le, sizeof(le), ie.off)) {
+       flog(LOG_CRIT, "could not read log entry: %s", strerror(errno));
+       errno = EIO;
+       return(-1);
+    }
+    if(memcmp(le.magic, LOGENTMAGIC, 4)) {
+       flog(LOG_CRIT, "invalid magic in log");
+       errno = EIO;
+       return(-1);
+    }
+    if(addrcmp(&le.name, at)) {
+       flog(LOG_CRIT, "did not receive correct block from log");
+       errno = EIO;
+       return(-1);
+    }
+    if(readall(fst->logfd, tmpbuf, le.len, ie.off + sizeof(le))) {
+       flog(LOG_CRIT, "could not read log data: %s", strerror(errno));
+       errno = EIO;
+       return(-1);
+    }
+    hash(tmpbuf, le.len, &v);
+    if(addrcmp(&v, &le.name)) {
+       flog(LOG_CRIT, "log data did not verify against hash");
+       errno = EIO;
+       return(-1);
+    }
+    if(buf != NULL)
+       memcpy(buf, tmpbuf, min(len, le.len));
+    return(le.len);
+}
+
+static struct storeops fstops = {
+    .release = releaseg,
+    .put = put,
+    .get = get,
+};
+
+struct store *newfstore(char *dir)
+{
+    struct store *st;
+    struct fstore *fst;
+    char tbuf[1024];
+    struct loghdr lh;
+    struct idxhdr ih;
+    struct stat64 sb;
+    
+    fst = calloc(1, sizeof(*fst));
+    fst->logfd = -1;
+    fst->idxfd = -1;
+    
+    snprintf(tbuf, sizeof(tbuf), "%s/log", dir);
+    if((fst->logfd = open(tbuf, O_RDWR | O_LARGEFILE)) < 0) {
+       flog(LOG_ERR, "could not open log %s: %s", tbuf, strerror(errno));
+       release(fst);
+       return(NULL);
+    }
+    if(fstat64(fst->logfd, &sb)) {
+       flog(LOG_ERR, "could not stat log: %s", strerror(errno));
+       release(fst);
+       return(NULL);
+    }
+    fst->logsize = sb.st_size;
+    if(readall(fst->logfd, &lh, sizeof(lh), 0)) {
+       flog(LOG_ERR, "could not read log header: %s", strerror(errno));
+       release(fst);
+       return(NULL);
+    }
+    if(memcmp(lh.magic, LOGMAGIC, sizeof(LOGMAGIC))) {
+       flog(LOG_ERR, "invalid log magic");
+       release(fst);
+       return(NULL);
+    }
+    
+    snprintf(tbuf, sizeof(tbuf), "%s/index", dir);
+    if((fst->idxfd = open(tbuf, O_RDWR | O_LARGEFILE)) < 0) {
+       flog(LOG_ERR, "could not open index %s: %s", tbuf, strerror(errno));
+       release(fst);
+       return(NULL);
+    }
+    if(fstat64(fst->idxfd, &sb)) {
+       flog(LOG_ERR, "could not stat index: %s", strerror(errno));
+       release(fst);
+       return(NULL);
+    }
+    if(readall(fst->idxfd, &ih, sizeof(ih), 0)) {
+       flog(LOG_ERR, "could not read index header: %s", strerror(errno));
+       release(fst);
+       return(NULL);
+    }
+    if(memcmp(ih.magic, IDXMAGIC, sizeof(IDXMAGIC))) {
+       flog(LOG_ERR, "invalid index magic");
+       release(fst);
+       return(NULL);
+    }
+    if(sb.st_size != (sizeof(struct idxhdr) + ih.size * sizeof(struct idxent))) {
+       flog(LOG_ERR, "invalid index size");
+       release(fst);
+       return(NULL);
+    }
+    fst->idxsize = ih.size;
+    
+    st = newstore(&fstops);
+    st->pdata = fst;
+    return(st);
+}
+
+int mkfstore(char *dir)
+{
+    char tbuf[1024];
+    int fd;
+    struct loghdr lh;
+    struct idxhdr ih;
+    
+    if(access(dir, F_OK)) {
+       if(mkdir(dir, 0700)) {
+           flog(LOG_ERR, "could not create %s: %s", dir, strerror(errno));
+           return(-1);
+       }
+    }
+    
+    snprintf(tbuf, sizeof(tbuf), "%s/log", dir);
+    if((fd = open(tbuf, O_WRONLY | O_CREAT | O_EXCL, 0600)) < 0) {
+       flog(LOG_ERR, "could not create log %s: %s", tbuf, strerror(errno));
+       return(-1);
+    }
+    memcpy(lh.magic, LOGMAGIC, sizeof(LOGMAGIC));
+    if(writeall(fd, &lh, sizeof(lh), 0)) {
+       flog(LOG_ERR, "could not write log header: %s", strerror(errno));
+       close(fd);
+       return(-1);
+    }
+    close(fd);
+    
+    snprintf(tbuf, sizeof(tbuf), "%s/index", dir);
+    if((fd = open(tbuf, O_WRONLY | O_CREAT | O_EXCL, 0600)) < 0) {
+       flog(LOG_ERR, "could not create index %s: %s", tbuf, strerror(errno));
+       return(-1);
+    }
+    memcpy(ih.magic, IDXMAGIC, sizeof(IDXMAGIC));
+    ih.size = 0;
+    if(writeall(fd, &ih, sizeof(ih), 0)) {
+       flog(LOG_ERR, "could not write index header: %s", strerror(errno));
+       close(fd);
+       return(-1);
+    }
+    close(fd);
+    return(0);
+}
diff --git a/log.c b/log.c
new file mode 100644 (file)
index 0000000..55af6bf
--- /dev/null
+++ b/log.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <syslog.h>
+#include <stdarg.h>
+
+#include "log.h"
+
+void logstderr(int level, char *msg, ...)
+{
+    va_list args;
+    
+    va_start(args, msg);
+    vfprintf(stderr, msg, args);
+    fputc('\n', stderr);
+    va_end(args);
+}
+
+void logsyslog(int level, char *msg, ...)
+{
+    va_list args;
+    
+    va_start(args, msg);
+    vsyslog(level, msg, args);
+    va_end(args);
+}
+
+void (*flog)(int, char *, ...) = logstderr;
diff --git a/log.h b/log.h
new file mode 100644 (file)
index 0000000..3a3ba7b
--- /dev/null
+++ b/log.h
@@ -0,0 +1,11 @@
+#ifndef _LOG_H
+#define _LOG_H
+
+#include <syslog.h>
+
+extern void (*flog)(int level, char *msg, ...);
+
+void logstderr(int level, char *msg, ...);
+void logsyslog(int level, char *msg, ...);
+
+#endif
diff --git a/mkfs.vc.c b/mkfs.vc.c
new file mode 100644 (file)
index 0000000..a507abe
--- /dev/null
+++ b/mkfs.vc.c
@@ -0,0 +1,78 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "utils.h"
+#include "store.h"
+#include "blocktree.h"
+#include "vcfs.h"
+
+int main(int argc, char **argv)
+{
+    struct store *st;
+    struct inode root;
+    struct revrec frev;
+    struct dentry dots;
+    time_t now;
+    int fd;
+    char tbuf[1024];
+    
+    if(argc < 2) {
+       fprintf(stderr, "usage; mkfs.vc DIR\n");
+       exit(1);
+    }
+    if(mkfstore(argv[1]))
+       exit(1);
+    if((st = newfstore(argv[1])) == NULL)
+       exit(1);
+    
+    now = time(NULL);
+    
+    memset(&dots, 0, sizeof(dots));
+    dots.inode = 0;
+    
+    root.mode = S_IFDIR | 0755;
+    root.mtime = root.ctime = now;
+    root.size = 2;
+    root.uid = getuid();
+    root.gid = getgid();
+    root.links = 2;
+    root.data.d = 0;
+    root.xattr.d = 0;
+    strcpy(dots.name, ".");
+    if(btput(st, &root.data, 0, &dots, sizeof(dots) - sizeof(dots.name) + 2)) {
+       fprintf(stderr, "mkfs.vc: could not create root directory entries: %s\n", strerror(errno));
+       exit(1);
+    }
+    strcpy(dots.name, "..");
+    if(btput(st, &root.data, 1, &dots, sizeof(dots) - sizeof(dots.name) + 3)) {
+       fprintf(stderr, "mkfs.vc: could not create root directory entries: %s\n", strerror(errno));
+       exit(1);
+    }
+    
+    frev.ct = now;
+    frev.root.d = 0;
+    if(btput(st, &frev.root, 0, &root, sizeof(root))) {
+       fprintf(stderr, "mkfs.vc: could not store root directory inode: %s\n", strerror(errno));
+       exit(1);
+    }
+    releasestore(st);
+    
+    snprintf(tbuf, sizeof(tbuf), "%s/revs", argv[1]);
+    if((fd = open(tbuf, O_WRONLY | O_CREAT | O_EXCL, 0600)) < 0) {
+       fprintf(stderr, "mkfs.vc: could not create revision database: %s\n", strerror(errno));
+       exit(1);
+    }
+    if(writeall(fd, &frev, sizeof(frev), 0)) {
+       fprintf(stderr, "mkfs.vc: could not write initial revision: %s\n", strerror(errno));
+       exit(1);
+    }
+    fsync(fd);
+    close(fd);
+    return(0);
+}
diff --git a/mkstore.c b/mkstore.c
new file mode 100644 (file)
index 0000000..f50860c
--- /dev/null
+++ b/mkstore.c
@@ -0,0 +1,15 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "store.h"
+
+int main(int argc, char **argv)
+{
+    if(argc < 2) {
+       fprintf(stderr, "usage: mkstore DIR\n");
+       exit(1);
+    }
+    if(mkfstore(argv[1]))
+       exit(1);
+    return(0);
+}
diff --git a/store.c b/store.c
new file mode 100644 (file)
index 0000000..8727512
--- /dev/null
+++ b/store.c
@@ -0,0 +1,164 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "store.h"
+
+struct store *newstore(struct storeops *ops)
+{
+    struct store *new;
+    
+    new = malloc(sizeof(*new));
+    new->ops = ops;
+    new->pdata = NULL;
+    new->cache = calloc(4096 * 4, sizeof(struct storecache));
+    return(new);
+}
+
+#define min(a, b) (((b) < (a))?(b):(a))
+
+static ssize_t cacheget(struct store *st, struct addr *a, void *buf, size_t len)
+{
+    int he, i;
+
+    he = a->hash[0] + ((a->hash[1] & 0x0f) << 8);
+    for(i = 0; i < 4; i++) {
+       if(!addrcmp(&st->cache[he * 4 + i].a, a))
+           break;
+    }
+    if(i == 4)
+       return(-2);
+    if(st->cache[he * 4 + i].data != NULL)
+       memcpy(buf, st->cache[he * 4 + i].data, min(len, st->cache[he * 4 + i].dlen));
+    return(st->cache[he * 4 + i].dlen);
+}
+
+static void cacheput(struct store *st, struct addr *a, const void *data, ssize_t len)
+{
+    int he, i;
+    struct storecache tmp;
+    
+    he = a->hash[0] + ((a->hash[1] & 0x0f) << 8);
+    for(i = 0; i < 4; i++) {
+       if(!addrcmp(&st->cache[he * 4 + i].a, a))
+           break;
+    }
+    if(i == 0)
+       return;
+    if(i < 4) {
+       tmp = st->cache[he * 4 + i];
+       memmove(&st->cache[he * 4 + 1], &st->cache[he * 4], i);
+       st->cache[he * 4] = tmp;
+       return;
+    }
+    if(st->cache[he * 4 + 3].data != NULL)
+       free(st->cache[he * 4 + 3].data);
+    memmove(&st->cache[he * 4 + 1], &st->cache[he * 4], 3);
+    st->cache[he * 4].a = *a;
+    if(len > 0)
+       st->cache[he * 4].data = memcpy(malloc(len), data, len);
+    else
+       st->cache[he * 4].data = NULL;
+    st->cache[he * 4].dlen = len;
+}
+
+int storeput(struct store *st, const void *buf, size_t len, struct addr *at)
+{
+    int ret;
+    struct addr na;
+
+    ret = st->ops->put(st, buf, len, &na);
+    if(!ret)
+       cacheput(st, &na, buf, len);
+    if(at != NULL)
+       *at = na;
+    return(ret);
+}
+
+ssize_t storeget(struct store *st, void *buf, size_t len, struct addr *at)
+{
+    ssize_t sz;
+    
+    sz = cacheget(st, at, buf, len);
+    if(sz != -2) {
+       if(sz == -1)
+           errno = ENOENT;
+       return(sz);
+    }
+    sz = st->ops->get(st, buf, len, at);
+    if((sz < 0) && (errno == ENOENT))
+       cacheput(st, at, NULL, -1);
+    else if(sz >= 0)
+       cacheput(st, at, buf, sz);
+    return(sz);
+}
+
+int releasestore(struct store *st)
+{
+    int err;
+    
+    if((err = st->ops->release(st)) != 0)
+       return(err);
+    free(st);
+    return(0);
+}
+
+int addrcmp(struct addr *a1, struct addr *a2)
+{
+    return(memcmp(a1->hash, a2->hash, 32));
+}
+
+char *formataddr(struct addr *a)
+{
+    int i;
+    static char buf[65];
+    
+    for(i = 0; i < 32; i++)
+       sprintf(buf + (i * 2), "%02x", a->hash[i]);
+    buf[64] = 0;
+    return(buf);
+}
+
+static int hex2int(char hex)
+{
+    if((hex >= 'a') && (hex <= 'f'))
+       return(hex - 'a' + 10);
+    if((hex >= 'A') && (hex <= 'F'))
+       return(hex - 'A' + 10);
+    if((hex >= '0') && (hex <= '9'))
+       return(hex - '0');
+    return(-1);
+}
+
+int parseaddr(char *p, struct addr *a)
+{
+    int i, d;
+    
+    for(i = 0; i < 32; i++) {
+       if((d = hex2int(*p++)) < 0)
+           return(-1);
+       while(*p == ' ')
+           p++;
+       a->hash[i] = d << 4;
+       if((d = hex2int(*p++)) < 0)
+           return(-1);
+       while(*p == ' ')
+           p++;
+       a->hash[i] |= d;
+    }
+    if(*p != 0)
+       return(-1);
+    return(0);
+}
+
+int niladdr(struct addr *a)
+{
+    int i;
+    
+    for(i = 0; i < 32; i++) {
+       if(a->hash[i])
+           return(0);
+    }
+    return(1);
+}
diff --git a/store.h b/store.h
new file mode 100644 (file)
index 0000000..cd03347
--- /dev/null
+++ b/store.h
@@ -0,0 +1,42 @@
+#ifndef _STORE_H
+#define _STORE_H
+
+#include <sys/types.h>
+
+#define STORE_MAXBLSZ 65535
+
+struct addr {
+    unsigned char hash[32];
+};
+
+struct storecache {
+    struct addr a;
+    void *data;
+    ssize_t dlen;
+};
+
+struct store {
+    struct storeops *ops;
+    void *pdata;
+    struct storecache *cache;
+};
+
+struct storeops {
+    int (*put)(struct store *st, const void *buf, size_t len, struct addr *at);
+    ssize_t (*get)(struct store *st, void *buf, size_t len, struct addr *at);
+    int (*release)(struct store *st);
+};
+
+struct store *newstore(struct storeops *ops);
+int storeput(struct store *st, const void *buf, size_t len, struct addr *at);
+ssize_t storeget(struct store *st, void *buf, size_t len, struct addr *at);
+int releasestore(struct store *st);
+int addrcmp(struct addr *a1, struct addr *a2);
+char *formataddr(struct addr *a);
+int parseaddr(char *buf, struct addr *a);
+int niladdr(struct addr *a);
+
+struct store *newfstore(char *dir);
+int mkfstore(char *dir);
+
+#endif
diff --git a/storeget.c b/storeget.c
new file mode 100644 (file)
index 0000000..ad746e7
--- /dev/null
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "store.h"
+
+char buf[STORE_MAXBLSZ];
+
+int main(int argc, char **argv)
+{
+    struct store *st;
+    struct addr a;
+    int ret;
+    
+    if(argc < 3) {
+       fprintf(stderr, "usage: storeget DIR HASH\n");
+       exit(1);
+    }
+    if((st = newfstore(argv[1])) == NULL)
+       exit(1);
+    parseaddr(argv[2], &a);
+    if((ret = storeget(st, buf, STORE_MAXBLSZ, &a)) < 0) {
+       perror(argv[2]);
+       exit(1);
+    }
+    write(1, buf, ret);
+    return(0);
+}
diff --git a/storeput.c b/storeput.c
new file mode 100644 (file)
index 0000000..bb7f007
--- /dev/null
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "store.h"
+
+char buf[STORE_MAXBLSZ];
+
+int main(int argc, char **argv)
+{
+    struct store *st;
+    struct addr a;
+    int ret, o;
+    
+    if(argc < 3) {
+       fprintf(stderr, "usage: storeget DIR\n");
+       exit(1);
+    }
+    for(o = 0; (ret = read(0, buf + o, STORE_MAXBLSZ - o)) > 0; o += ret);
+    if((st = newfstore(argv[1])) == NULL)
+       exit(1);
+    if((ret = storeput(st, buf, STORE_MAXBLSZ, &a)) < 0) {
+       perror(argv[2]);
+       exit(1);
+    }
+    printf("%s\n", formataddr(&a));
+    return(0);
+}
diff --git a/utils.c b/utils.c
new file mode 100644 (file)
index 0000000..4e4f537
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,51 @@
+#define _LARGEFILE64_SOURCE
+#define _XOPEN_SOURCE 500
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "utils.h"
+
+int readall(int fd, void *buf, size_t len, loff_t offset)
+{
+    int ret;
+    
+    while(len > 0) {
+       /*
+       if(lseek(fd, offset, SEEK_SET) != offset)
+           return(-1);
+       ret = read(fd, buf, len);
+       */
+       ret = pread64(fd, buf, len, offset);
+       if(ret < 0)
+           return(-1);
+       if(ret == 0) {
+           errno = ENODATA;
+           return(-1);
+       }
+       buf += ret;
+       len -= ret;
+       offset += ret;
+    }
+    return(0);
+}
+
+int writeall(int fd, const void *buf, size_t len, loff_t offset)
+{
+    int ret;
+    
+    while(len > 0) {
+       /*
+       if(lseek(fd, offset, SEEK_SET) != offset)
+           return(-1);
+       ret = write(fd, buf, len);
+       */
+       ret = pwrite64(fd, buf, len, offset);
+       if(ret < 0)
+           return(-1);
+       buf += ret;
+       len -= ret;
+       offset += ret;
+    }
+    return(0);
+}
diff --git a/utils.h b/utils.h
new file mode 100644 (file)
index 0000000..6dd998b
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,7 @@
+#ifndef _UTILS_H
+#define _UTILS_H
+
+int readall(int fd, void *buf, size_t len, loff_t offset);
+int writeall(int fd, const void *buf, size_t len, loff_t offset);
+
+#endif
diff --git a/vcfs.c b/vcfs.c
new file mode 100644 (file)
index 0000000..0a49f49
--- /dev/null
+++ b/vcfs.c
@@ -0,0 +1,592 @@
+#define _LARGEFILE64_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <fuse_lowlevel.h>
+
+#include "utils.h"
+#include "log.h"
+#include "store.h"
+#include "blocktree.h"
+#include "vcfs.h"
+
+/* XXX: The current i-numbering scheme sucks. */
+
+struct btree {
+    struct btree *l, *r;
+    int h;
+    void *d;
+};
+
+struct inoc {
+    vc_ino_t inode;
+    struct btnode inotab;
+    fuse_ino_t cnum;
+};
+
+struct vcfsdata {
+    struct store *st;
+    int revfd;
+    vc_rev_t currev;
+    vc_ino_t nextino;
+    struct btnode inotab;
+    struct btree *inocbf, *inocbv;
+    fuse_ino_t inocser;
+};
+
+#define max(a, b) (((b) > (a))?(b):(a))
+static struct btnode nilnode = {0, };
+
+static int btheight(struct btree *tree)
+{
+    if(tree == NULL)
+       return(0);
+    return(tree->h);
+}
+
+static void btsetheight(struct btree *tree)
+{
+    if(tree == NULL)
+       return;
+    tree->h = max(btheight(tree->l), btheight(tree->r)) + 1;
+}
+
+static void bbtrl(struct btree **tree);
+
+static void bbtrr(struct btree **tree)
+{
+    struct btree *m, *l, *r;
+    
+    if(btheight((*tree)->l->r) > btheight((*tree)->l->l))
+       bbtrl(&(*tree)->l);
+    r = (*tree);
+    l = r->l;
+    m = l->r;
+    r->l = m;
+    btsetheight(r);
+    l->r = r;
+    btsetheight(l);
+    *tree = l;
+}
+
+static void bbtrl(struct btree **tree)
+{
+    struct btree *m, *l, *r;
+    
+    if(btheight((*tree)->r->l) > btheight((*tree)->r->r))
+       bbtrr(&(*tree)->r);
+    l = (*tree);
+    r = l->r;
+    m = r->l;
+    l->r = m;
+    btsetheight(l);
+    r->l = l;
+    btsetheight(r);
+    *tree = r;
+}
+
+static int bbtreeput(struct btree **tree, void *item, int (*cmp)(void *, void *))
+{
+    int c, r;
+    
+    if(*tree == NULL) {
+       *tree = calloc(1, sizeof(**tree));
+       (*tree)->d = item;
+       (*tree)->h = 1;
+       return(1);
+    }
+    if((c = cmp(item, (*tree)->d)) < 0)
+       r = bbtreeput(&(*tree)->l, item, cmp);
+    else if(c > 0)
+       r = bbtreeput(&(*tree)->r, item, cmp);
+    else
+       return(0);
+    btsetheight(*tree);
+    if(btheight((*tree)->l) > btheight((*tree)->r) + 1)
+       bbtrr(tree);
+    if(btheight((*tree)->r) > btheight((*tree)->l) + 1)
+       bbtrl(tree);
+    return(r);
+}
+
+static void *btreeget(struct btree *tree, void *key, int (*cmp)(void *, void *))
+{
+    int c;
+    
+    while(1) {
+       if(tree == NULL)
+           return(NULL);
+       c = cmp(key, tree->d);
+       if(c < 0)
+           tree = tree->l;
+       else if(c > 0)
+           tree = tree->r;
+       else
+           return(tree->d);
+    }
+}
+
+static void dstrvcfs(struct vcfsdata *fsd)
+{
+    releasestore(fsd->st);
+    fsync(fsd->revfd);
+    close(fsd->revfd);
+    free(fsd);
+}
+
+static int inoccmpbf(struct inoc *a, struct inoc *b)
+{
+    return(a->cnum - b->cnum);
+}
+
+static int inoccmpbv(struct inoc *a, struct inoc *b)
+{
+    if(a->inode < b->inode)
+       return(-1);
+    if(a->inode > b->inode)
+       return(1);
+    if(a->inotab.d < b->inotab.d)
+       return(-1);
+    if(a->inotab.d > b->inotab.d)
+       return(1);
+    return(addrcmp(&a->inotab.a, &b->inotab.a));
+}
+
+static struct inoc *getinocbf(struct vcfsdata *fsd, fuse_ino_t inode)
+{
+    struct inoc key;
+    
+    key.cnum = inode;
+    return(btreeget(fsd->inocbf, &key, (int (*)(void *, void *))inoccmpbf));
+}
+
+static struct inoc *getinocbv(struct vcfsdata *fsd, vc_ino_t inode, struct btnode inotab)
+{
+    struct inoc key;
+    
+    key.inotab = inotab;
+    key.inode = inode;
+    return(btreeget(fsd->inocbv, &key, (int (*)(void *, void *))inoccmpbv));
+}
+
+static fuse_ino_t cacheinode(struct vcfsdata *fsd, vc_ino_t inode, struct btnode inotab)
+{
+    fuse_ino_t ret;
+    struct inoc *inoc;
+    
+    if((inoc = getinocbv(fsd, inode, inotab)) != NULL)
+       return(inoc->cnum);
+    ret = fsd->inocser++;
+    inoc = calloc(1, sizeof(*inoc));
+    inoc->inode = inode;
+    inoc->inotab = inotab;
+    inoc->cnum = ret;
+    bbtreeput(&fsd->inocbf, inoc, (int (*)(void *, void *))inoccmpbf);
+    bbtreeput(&fsd->inocbv, inoc, (int (*)(void *, void *))inoccmpbv);
+    return(ret);
+}
+
+static struct vcfsdata *initvcfs(char *dir)
+{
+    struct vcfsdata *fsd;
+    char tbuf[1024];
+    struct stat64 sb;
+    struct revrec cr;
+    
+    fsd = calloc(1, sizeof(*fsd));
+    snprintf(tbuf, sizeof(tbuf), "%s/revs", dir);
+    if((fsd->revfd = open(tbuf, O_RDWR | O_LARGEFILE)) < 0) {
+       flog(LOG_ERR, "could not open revision database: %s", strerror(errno));
+       free(fsd);
+       return(NULL);
+    }
+    if(fstat64(fsd->revfd, &sb)) {
+       flog(LOG_ERR, "could not stat revision database: %s", strerror(errno));
+       close(fsd->revfd);
+       free(fsd);
+       return(NULL);
+    }
+    if(sb.st_size % sizeof(struct revrec) != 0) {
+       flog(LOG_ERR, "revision database has illegal size");
+       close(fsd->revfd);
+       free(fsd);
+       return(NULL);
+    }
+    fsd->currev = (sb.st_size / sizeof(struct revrec)) - 1;
+    assert(!readall(fsd->revfd, &cr, sizeof(cr), fsd->currev * sizeof(struct revrec)));
+    fsd->inotab = cr.root;
+    if((fsd->st = newfstore(dir)) == NULL) {
+       close(fsd->revfd);
+       free(fsd);
+       return(NULL);
+    }
+    fsd->inocser = 1;
+    cacheinode(fsd, 0, nilnode);
+    if((fsd->nextino = btcount(fsd->st, &fsd->inotab)) < 0) {
+       flog(LOG_ERR, "could not count inodes: %s");
+       close(fsd->revfd);
+       releasestore(fsd->st);
+       free(fsd);
+       return(NULL);
+    }
+    return(fsd);
+}
+
+static vc_ino_t dirlookup(struct vcfsdata *fsd, struct btnode *dirdata, const char *name, int *di)
+{
+    struct dentry dent;
+    int i;
+    ssize_t sz;
+    
+    for(i = 0; ; i++) {
+       if((sz = btget(fsd->st, dirdata, i, &dent, sizeof(dent))) < 0) {
+           if(errno == ERANGE)
+               errno = ENOENT;
+           return(-1);
+       }
+       if((dent.inode >= 0) && !strncmp(dent.name, name, sizeof(dent.name))) {
+           if(di != NULL)
+               *di = i;
+           return(dent.inode);
+       }
+    }
+}
+
+static void fusedestroy(struct vcfsdata *fsd)
+{
+    dstrvcfs(fsd);
+}
+
+static void fillstat(struct stat *sb, struct inode *file)
+{
+    sb->st_mode = file->mode;
+    sb->st_atime = (time_t)file->mtime;
+    sb->st_mtime = (time_t)file->mtime;
+    sb->st_ctime = (time_t)file->ctime;
+    sb->st_size = file->size;
+    sb->st_uid = file->uid;
+    sb->st_gid = file->gid;
+    sb->st_nlink = file->links;
+}
+
+static int getinode(struct vcfsdata *fsd, struct btnode inotab, vc_ino_t ino, struct inode *buf)
+{
+    ssize_t sz;
+    
+    if(inotab.d == 0)
+       inotab = fsd->inotab;
+    if((sz = btget(fsd->st, &inotab, ino, buf, sizeof(*buf))) < 0)
+       return(-1);
+    if(sz != sizeof(*buf)) {
+       flog(LOG_ERR, "illegal size for inode %i", ino);
+       errno = EIO;
+       return(-1);
+    }
+    return(0);
+}
+
+static void fusegetattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+    struct vcfsdata *fsd;
+    struct stat sb;
+    struct inoc *inoc;
+    struct inode file;
+    
+    fsd = fuse_req_userdata(req);
+    memset(&sb, 0, sizeof(sb));
+    if((inoc = getinocbf(fsd, ino)) == NULL) {
+       fuse_reply_err(req, ENOENT);
+       return;
+    }
+    if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    fillstat(&sb, &file);
+    fuse_reply_attr(req, &sb, 0);
+}
+
+static void fuselookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+    struct vcfsdata *fsd;
+    struct inode file;
+    struct inoc *inoc;
+    struct fuse_entry_param e;
+    vc_ino_t target;
+    
+    fsd = fuse_req_userdata(req);
+    if((inoc = getinocbf(fsd, parent)) == NULL) {
+       fuse_reply_err(req, ENOENT);
+       return;
+    }
+    if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    if((target = dirlookup(fsd, &file.data, name, NULL)) < 0) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    if(getinode(fsd, inoc->inotab, target, &file)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    memset(&e, 0, sizeof(e));
+    e.ino = cacheinode(fsd, target, inoc->inotab);
+    fillstat(&e.attr, &file);
+    fuse_reply_entry(req, &e);
+}
+
+static void fusereaddir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+{
+    struct vcfsdata *fsd;
+    struct inoc *inoc;
+    struct inode file;
+    struct dentry dent;
+    struct stat sb;
+    ssize_t sz, osz, bsz;
+    char *buf;
+    
+    fsd = fuse_req_userdata(req);
+    if((inoc = getinocbf(fsd, ino)) == NULL) {
+       fuse_reply_err(req, ENOENT);
+       return;
+    }
+    if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    bsz = 0;
+    buf = NULL;
+    while(bsz < size) {
+       memset(&dent, 0, sizeof(dent));
+       if((sz = btget(fsd->st, &file.data, off++, &dent, sizeof(dent))) < 0) {
+           if(errno == ERANGE) {
+               if(buf != NULL)
+                   break;
+               fuse_reply_buf(req, NULL, 0);
+               return;
+           }
+           fuse_reply_err(req, errno);
+           if(buf != NULL)
+               free(buf);
+           return;
+       }
+       if(dent.inode < 0)
+           continue;
+       osz = bsz;
+       bsz += fuse_add_direntry(req, NULL, 0, dent.name, NULL, 0);
+       if(bsz > size)
+           break;
+       buf = realloc(buf, bsz);
+       memset(&sb, 0, sizeof(sb));
+       sb.st_ino = cacheinode(fsd, dent.inode, inoc->inotab);
+       fuse_add_direntry(req, buf + osz, bsz - osz, dent.name, &sb, off);
+    }
+    fuse_reply_buf(req, buf, bsz);
+    if(buf != NULL)
+       free(buf);
+}
+
+static vc_rev_t commit(struct vcfsdata *fsd, struct btnode inotab)
+{
+    struct revrec rr;
+    
+    rr.ct = time(NULL);
+    rr.root = inotab;
+    if(writeall(fsd->revfd, &rr, sizeof(rr), (fsd->currev + 1) * sizeof(struct revrec))) {
+       flog(LOG_CRIT, "could not write new revision: %s", strerror(errno));
+       return(-1);
+    }
+    fsd->inotab = inotab;
+    return(++fsd->currev);
+}
+
+static int deldentry(struct vcfsdata *fsd, struct inode *ino, int di)
+{
+    if((di < 0) || (di >= ino->size)) {
+       errno = ERANGE;
+       return(-1);
+    }
+    
+}
+
+static int setdentry(struct vcfsdata *fsd, struct inode *ino, int di, const char *name, vc_ino_t target)
+{
+    struct dentry dent;
+    ssize_t sz;
+    
+    if(strlen(name) > 255) {
+       errno = ENAMETOOLONG;
+       return(-1);
+    }
+    memset(&dent, 0, sizeof(dent));
+    strcpy(dent.name, name);
+    dent.inode = target;
+    sz = sizeof(dent) - sizeof(dent.name) + strlen(name) + 1;
+    if((di == -1) || (di == ino->size)) {
+       if(btput(fsd->st, &ino->data, ino->size, &dent, sz))
+           return(-1);
+       ino->size++;
+       return(0);
+    }
+    return(btput(fsd->st, &ino->data, di, &dent, sz));
+}
+
+static void fusemkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+{
+    struct vcfsdata *fsd;
+    struct inoc *inoc;
+    struct inode file, new;
+    struct btnode inotab;
+    struct fuse_entry_param e;
+    const struct fuse_ctx *ctx;
+    struct btop ops[2];
+    
+    fsd = fuse_req_userdata(req);
+    ctx = fuse_req_ctx(req);
+    if((inoc = getinocbf(fsd, parent)) == NULL) {
+       fuse_reply_err(req, ENOENT);
+       return;
+    }
+    if(inoc->inotab.d != 0) {
+       fuse_reply_err(req, EROFS);
+       return;
+    }
+    if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    if(!S_ISDIR(file.mode)) {
+       fuse_reply_err(req, ENOTDIR);
+       return;
+    }
+    if(dirlookup(fsd, &file.data, name, NULL) != -1) {
+       fuse_reply_err(req, EEXIST);
+       return;
+    }
+    
+    memset(&new, 0, sizeof(new));
+    new.mode = S_IFDIR | mode;
+    new.mtime = new.ctime = time(NULL);
+    new.size = 0;
+    new.uid = ctx->uid;
+    new.gid = ctx->gid;
+    new.links = 2;
+    if(setdentry(fsd, &new, -1, ".", fsd->nextino) || setdentry(fsd, &new, -1, "..", inoc->inode)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    
+    inotab = fsd->inotab;
+    if(setdentry(fsd, &file, -1, name, fsd->nextino)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    file.links++;
+    btmkop(ops + 0, inoc->inode, &file, sizeof(file));
+    btmkop(ops + 1, fsd->nextino, &new, sizeof(new));
+    if(btputmany(fsd->st, &inotab, ops, 2)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    /*
+    if(btput(fsd->st, &inotab, fsd->nextino, &new, sizeof(new))) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    if(btput(fsd->st, &inotab, inoc->inode, &file, sizeof(file))) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    */
+    commit(fsd, inotab);
+    
+    memset(&e, 0, sizeof(e));
+    e.ino = cacheinode(fsd, fsd->nextino++, nilnode);
+    fillstat(&e.attr, &new);
+    fuse_reply_entry(req, &e);
+}
+
+static void fusermdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+    struct vcfsdata *fsd;
+    struct inoc *inoc;
+    struct inode file;
+    int di;
+    
+    fsd = fuse_req_userdata(req);
+    if((inoc = getinocbf(fsd, parent)) == NULL) {
+       fuse_reply_err(req, ENOENT);
+       return;
+    }
+    if(inoc->inotab.d != 0) {
+       fuse_reply_err(req, EROFS);
+       return;
+    }
+    if(getinode(fsd, inoc->inotab, inoc->inode, &file)) {
+       fuse_reply_err(req, errno);
+       return;
+    }
+    if(!S_ISDIR(file.mode)) {
+       fuse_reply_err(req, ENOTDIR);
+       return;
+    }
+    if(dirlookup(fsd, &file.data, name, &di) == -1) {
+       fuse_reply_err(req, ENOENT);
+       return;
+    }
+}
+
+static struct fuse_lowlevel_ops fuseops = {
+    .destroy = (void (*)(void *))fusedestroy,
+    .lookup = fuselookup,
+    .getattr = fusegetattr,
+    .readdir = fusereaddir,
+    .mkdir = fusemkdir,
+    .rmdir = fusermdir,
+};
+
+int main(int argc, char **argv)
+{
+    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+    struct fuse_session *fs;
+    struct fuse_chan *ch;
+    struct vcfsdata *fsd;
+    char *mtpt;
+    int err, fd;
+    
+    if((fsd = initvcfs(".")) == NULL)
+       exit(1);
+    if(fuse_parse_cmdline(&args, &mtpt, NULL, NULL) < 0)
+       exit(1);
+    if((fd = fuse_mount(mtpt, &args)) < 0)
+       exit(1);
+    if((fs = fuse_lowlevel_new(&args, &fuseops, sizeof(fuseops), fsd)) == NULL) {
+       fuse_unmount(mtpt, fd);
+       close(fd);
+       fprintf(stderr, "vcfs: could not initialize fuse\n");
+       exit(1);
+    }
+    fuse_set_signal_handlers(fs);
+    if((ch = fuse_kern_chan_new(fd)) == NULL) {
+       fuse_remove_signal_handlers(fs);
+       fuse_unmount(mtpt, fd);
+       fuse_session_destroy(fs);
+       close(fd);
+       exit(1);
+    }
+    
+    fuse_session_add_chan(fs, ch);
+    err = fuse_session_loop(fs);
+    
+    fuse_remove_signal_handlers(fs);
+    fuse_unmount(mtpt, fd);
+    fuse_session_destroy(fs);
+    close(fd);
+    return(err?1:0);
+}
diff --git a/vcfs.h b/vcfs.h
new file mode 100644 (file)
index 0000000..8fd2727
--- /dev/null
+++ b/vcfs.h
@@ -0,0 +1,32 @@
+#ifndef _VCFS_H
+#define _VCFS_H
+
+#include <time.h>
+#include <inttypes.h>
+
+#include "blocktree.h"
+
+typedef loff_t vc_ino_t;
+typedef loff_t vc_rev_t;
+
+struct revrec {
+    u_int64_t ct;
+    struct btnode root;
+};
+
+struct inode {
+    u_int32_t mode;
+    u_int64_t mtime, ctime;
+    u_int64_t size;
+    u_int32_t uid, gid;
+    u_int32_t links;
+    struct btnode data;
+    struct btnode xattr;
+};
+
+struct dentry {
+    u_int64_t inode;
+    char name[256];
+};
+
+#endif