--- /dev/null
+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
--- /dev/null
+#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];
+ }
+}
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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;
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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);
+}
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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