working open/read/close -dir
authorTero Marttila <terom@fixme.fi>
Mon, 13 Oct 2008 02:27:59 +0300
changeset 27 461be4cd34a3
parent 26 61668c57f4bb
child 28 e944453ca924
working open/read/close -dir
doc/fuse_db.sql
src/dbfs.c
src/dirbuf.c
src/dirbuf.h
src/evfuse.c
src/evfuse.h
src/evsql.c
src/evsql.h
src/evsql_internal.h
src/evsql_util.c
src/hello.c
src/lib/log.c
src/lib/log.h
src/simple.c
src/simple_hello.c
--- a/doc/fuse_db.sql	Sun Oct 12 21:59:52 2008 +0300
+++ b/doc/fuse_db.sql	Mon Oct 13 02:27:59 2008 +0300
@@ -5,3 +5,7 @@
 INSERT INTO inodes VALUES (2, 'REG', 292, 0);
 INSERT INTO file_tree (name, parent, inode) VALUES ('foo', 1, 2);
 
+ALTER TABLE file_tree ALTER COLUMN name DROP NOT NULL;
+ALTER TABLE file_tree ALTER COLUMN parent DROP NOT NULL;
+INSERT INTO file_tree (name, parent, inode) VALUES (NULL, NULL, 1);
+
--- a/src/dbfs.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/dbfs.c	Mon Oct 13 02:27:59 2008 +0300
@@ -52,17 +52,19 @@
 
 }
 
-void dbfs_destroy (void *userdata) {
-    INFO("[dbfs.destroy] userdata=%p", userdata);
+void dbfs_destroy (void *arg) {
+    struct dbfs *ctx = arg;
+    INFO("[dbfs.destroy %p]", ctx);
 
-
+    // exit libevent
+    event_base_loopexit(ctx->ev_base, NULL);
 }
 
 /*
  * Check the result set.
  *
  * Returns;
- *  -1  if the query failed, the columns do not match, or there are too many/few rows
+ *  -1  if the query failed, the columns do not match, or there are too many/few rows (unless rows was zero)
  *  0   the results match
  *  1   there were no results
  */
@@ -78,11 +80,11 @@
         SERROR(err = 1);
 
     // duplicate rows?
-    if (evsql_result_rows(res) != rows)
-        ERROR("multiple rows returned");
+    if (rows && evsql_result_rows(res) != rows)
+        ERROR("wrong number of rows returned");
     
     // correct number of columns
-    if (evsql_result_cols(res) != 5)
+    if (evsql_result_cols(res) != cols)
         ERROR("wrong number of columns: %zu", evsql_result_cols(res));
 
     // good
@@ -142,7 +144,7 @@
     )
         EERROR(err = EIO, "invalid db data");
         
-    INFO("[dbfs.lookup] -> ion=%u", ino);
+    INFO("[dbfs.lookup] -> ino=%u", ino);
     
     // stat attrs
     if (_dbfs_stat_info(&e.attr, res, 0, 1))
@@ -249,7 +251,7 @@
         "SELECT"
         " inodes.type, inodes.mode, inodes.size, count(*)"
         " FROM inodes"
-        " WHERE inodes.ino = ‰1::int4"
+        " WHERE inodes.ino = $1::int4"
         " GROUP BY inodes.type, inodes.mode, inodes.size";
 
     static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
@@ -279,13 +281,19 @@
 }
 
 struct dbfs_dirop {
-    struct fuse_file_info *fi;
+    struct fuse_file_info fi;
     struct fuse_req *req;
 
     struct evsql_trans *trans;
     
+    // dir/parent dir inodes
+    uint32_t ino, parent;
+    
     // opendir has returned and releasedir hasn't been called yet
     int open;
+
+    // for readdir
+    struct dirbuf dirbuf;
 };
 
 /*
@@ -294,49 +302,142 @@
  * req must be NULL.
  */
 static void dbfs_dirop_free (struct dbfs_dirop *dirop) {
-    assert(dirop->req == NULL);
+    assert(dirop);
+    assert(!dirop->open);
+    assert(!dirop->req);
 
-    if (dirop->trans)
+    if (dirop->trans) {
+        WARNING("aborting transaction");
         evsql_trans_abort(dirop->trans);
+    }
+
+    dirbuf_release(&dirop->dirbuf);
 
     free(dirop);
 }
 
+static void dbfs_opendir_info_res (const struct evsql_result_info *res, void *arg) {
+    struct dbfs_dirop *dirop = arg;
+    struct fuse_req *req = dirop->req; dirop->req = NULL;
+    int err;
+    
+    assert(req != NULL);
+   
+    // check the results
+    if ((err = _dbfs_check_res(res, 1, 2)))
+        SERROR(err = (err ==  1 ? ENOENT : EIO));
+
+    const char *type;
+
+    // extract the data
+    if (0
+        ||  evsql_result_uint32(res, 0, 0, &dirop->parent,  1 ) // file_tree.parent
+        ||  evsql_result_string(res, 0, 1, &type,           0 ) // inodes.type
+    )
+        SERROR(err = EIO);
+
+    // is it a dir?
+    if (_dbfs_mode(type) != S_IFDIR)
+        EERROR(err = ENOTDIR, "wrong type: %s", type);
+    
+    INFO("[dbfs.opendir %p:%p] -> ino=%lu, parent=%lu, type=%s", dirop, req, (unsigned long int) dirop->ino, (unsigned long int) dirop->parent, type);
+    
+    // send the openddir reply
+    if ((err = fuse_reply_open(req, &dirop->fi)))
+        EERROR(err, "fuse_reply_open");
+    
+    // dirop is now open
+    dirop->open = 1;
+
+    // ok, wait for the opendir call
+    return;
+
+error:
+    if (err) {
+        // abort the trans
+        evsql_trans_abort(dirop->trans);
+        
+        dirop->trans = NULL;
+
+        if ((err = fuse_reply_err(req, err)))
+            EWARNING(err, "fuse_reply_err");
+    }
+    
+    // free
+    evsql_result_free(res);
+}
+
 /*
  * The opendir transaction is ready
  */
 static void dbfs_dirop_ready (struct evsql_trans *trans, void *arg) {
     struct dbfs_dirop *dirop = arg;
-    struct fuse_req *req = dirop->req; dirop->req = NULL;
+    struct fuse_req *req = dirop->req;
+    struct dbfs *ctx = fuse_req_userdata(req);
     int err;
 
-    INFO("[dbfs.openddir %p:%p] -> trans=%p", dirop, req, trans);
+    assert(req != NULL);
+
+    INFO("[dbfs.opendir %p:%p] -> trans=%p", dirop, req, trans);
 
     // remember the transaction
     dirop->trans = trans;
+    
+    // first fetch info about the dir itself
+    const char *sql =
+        "SELECT"
+        " file_tree.parent, inodes.type"
+        " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.inode = inodes.ino)"
+        " WHERE file_tree.inode = $1::int4";
 
-    // send the openddir reply
-    if ((err = fuse_reply_open(dirop->req, dirop->fi)))
-        EERROR(err, "fuse_reply_open");
-    
-    // dirop is now open
-    dirop->open = 1;
+    static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
+        EVSQL_PARAM ( UINT32 ),
 
-    // ok, wait for the next fs req
+        EVSQL_PARAMS_END
+    };
+
+    // build params
+    if (0
+        ||  evsql_param_uint32(&params, 0, dirop->ino)
+    )
+        SERROR(err = EIO);
+        
+    // query
+    if (evsql_query_params(ctx->db, dirop->trans, sql, &params, dbfs_opendir_info_res, dirop) == NULL)
+        SERROR(err = EIO);
+
+    // ok, wait for the info results
     return;
 
 error:
+    // we handle the req
+    dirop->req = NULL;
+    
+    // free the dirop
     dbfs_dirop_free(dirop);
-
+    
     if ((err = fuse_reply_err(req, err)))
         EWARNING(err, "fuse_reply_err");
 }
 
 static void dbfs_dirop_done (struct evsql_trans *trans, void *arg) {
     struct dbfs_dirop *dirop = arg;
+    struct fuse_req *req = dirop->req; dirop->req = NULL;
     int err;
     
+    assert(req != NULL);
 
+    INFO("[dbfs.releasedir %p:%p] -> OK", dirop, req);
+
+    // forget trans
+    dirop->trans = NULL;
+    
+    // just reply
+    if ((err = fuse_reply_err(req, 0)))
+        EWARNING(err, "fuse_reply_err");
+    
+    // we can free dirop
+    dbfs_dirop_free(dirop);
 }
 
 static void dbfs_dirop_error (struct evsql_trans *trans, void *arg) {
@@ -371,14 +472,16 @@
         ERROR("calloc");
 
     INFO("[dbfs.opendir %p:%p] ino=%lu, fi=%p", dirop, req, ino, fi);
-        
+    
     // store the dirop
-    fi->fh = (uint64_t) dirop;
+    // copy *fi since it's on the stack
+    dirop->fi = *fi;
+    dirop->fi.fh = (uint64_t) dirop;
     dirop->req = req;
-    dirop->fi = fi;
+    dirop->ino = ino;
 
     // start a new transaction
-    if (evsql_trans(ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_dirop_error, dbfs_dirop_ready, dbfs_dirop_done, dirop))
+    if ((dirop->trans = evsql_trans(ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_dirop_error, dbfs_dirop_ready, dbfs_dirop_done, dirop)) == NULL)
         SERROR(err = EIO);
     
     // XXX: handle interrupts
@@ -396,23 +499,104 @@
         EWARNING(err, "fuse_reply_err");
 }
 
+static void dbfs_readdir_files_res (const struct evsql_result_info *res, void *arg) {
+    struct dbfs_dirop *dirop = arg;
+    struct fuse_req *req = dirop->req; dirop->req = NULL;
+    int err;
+    size_t row;
+    
+    assert(req != NULL);
+    
+    // check the results
+    if ((err = _dbfs_check_res(res, 0, 4)) < 0)
+        SERROR(err = EIO);
+        
+    INFO("[dbfs.readdir %p:%p] -> files: res_rows=%zu", dirop, req, evsql_result_rows(res));
+        
+    // iterate over the rows
+    for (row = 0; row < evsql_result_rows(res); row++) {
+        uint32_t off, ino;
+        const char *name, *type;
+
+        // extract the data
+        if (0
+            ||  evsql_result_uint32(res, row, 0, &off,          0 ) // file_tree.offset
+            ||  evsql_result_string(res, row, 1, &name,         0 ) // file_tree.name
+            ||  evsql_result_uint32(res, row, 2, &ino,          0 ) // inodes.ino
+            ||  evsql_result_string(res, row, 3, &type,         0 ) // inodes.type
+        )
+            SERROR(err = EIO);
+        
+        INFO("\t%zu: off=%lu+2, name=%s, ino=%lu, type=%s", row, (long unsigned int) off, name, (long unsigned int) ino, type);
+
+        // add to the dirbuf
+        // offsets are just offset + 2
+        if ((err = dirbuf_add(req, &dirop->dirbuf, off + 2, off + 3, name, ino, _dbfs_mode(type))) < 0 && (err = EIO))
+            ERROR("failed to add dirent for inode=%lu", (long unsigned int) ino);
+        
+        // stop if it's full
+        if (err > 0)
+            break;
+    }
+
+    // send it
+    if ((err = dirbuf_done(req, &dirop->dirbuf)))
+        EERROR(err, "failed to send buf");
+    
+    // good, fallthrough
+    err = 0;
+
+error:
+    if (err) {
+        // abort the trans
+        evsql_trans_abort(dirop->trans);
+        
+        dirop->trans = NULL;
+
+        // we handle the req
+        dirop->req = NULL;
+    
+        if ((err = fuse_reply_err(req, err)))
+            EWARNING(err, "fuse_reply_err");
+    }
+    
+    // free
+    evsql_result_free(res);
+}
+
 static void dbfs_readdir (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) {
     struct dbfs *ctx = fuse_req_userdata(req);
     struct dbfs_dirop *dirop = (struct dbfs_dirop *) fi->fh;
     int err;
 
+    assert(!dirop->req);
+    assert(dirop->trans);
+    assert(dirop->ino == ino);
+    
     INFO("[dbfs.readdir %p:%p] ino=%lu, size=%zu, off=%zu, fi=%p : trans=%p", dirop, req, ino, size, off, fi, dirop->trans);
 
     // update dirop
     dirop->req = req;
-    assert(dirop->fi == fi);
+
+    // create the dirbuf
+    if (dirbuf_init(&dirop->dirbuf, size, off))
+        SERROR(err = EIO);
+
+    // add . and ..
+    // we set the next offset to 2, because all dirent offsets will be larger than that
+    if ((err = (0
+        ||  dirbuf_add(req, &dirop->dirbuf, 0, 1, ".",   dirop->ino,    S_IFDIR )
+        ||  dirbuf_add(req, &dirop->dirbuf, 1, 2, "..",  
+                        dirop->parent ? dirop->parent : dirop->ino,     S_IFDIR )
+    )) && (err = EIO))
+        ERROR("failed to add . and .. dirents");
 
     // select all relevant file entries
     const char *sql = 
         "SELECT"
-        " \"file_tree.offset\", file_tree.name, inodes.ino, inodes.type"
+        " file_tree.\"offset\", file_tree.name, inodes.ino, inodes.type"
         " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.inode = inodes.ino)"
-        " WHERE file_tree.parent = $1::int4 AND \"file_tree.offset\" >= $2::int4"
+        " WHERE file_tree.parent = $1::int4 AND file_tree.\"offset\" >= $2::int4"
         " LIMIT $3::int4";
 
     static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) {
@@ -423,7 +607,35 @@
         EVSQL_PARAMS_END
     };
 
-    // XXX: incomplete
+    // adjust offset to take . and .. into account
+    if (off > 2)
+        off -= 2;
+    
+    // build params
+    if (0
+        ||  evsql_param_uint32(&params, 0, dirop->ino)
+        ||  evsql_param_uint32(&params, 1, off)
+        ||  evsql_param_uint32(&params, 2, dirbuf_estimate(&dirop->dirbuf, 0))
+    )
+        SERROR(err = EIO);
+
+    // query
+    if (evsql_query_params(ctx->db, dirop->trans, sql, &params, dbfs_readdir_files_res, dirop) == NULL)
+        SERROR(err = EIO);
+
+    // good, wait
+    return;
+
+error:
+    // we handle the req
+    dirop->req = NULL;
+
+    // abort the trans
+    evsql_trans_abort(dirop->trans); dirop->trans = NULL;
+
+    if ((err = fuse_reply_err(req, err)))
+        EWARNING(err, "fuse_reply_err");
+
 }
 
 static void dbfs_releasedir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) {
@@ -432,36 +644,61 @@
     int err;
 
     (void) ctx;
+    
+    assert(!dirop->req);
+    assert(dirop->ino == ino);
 
     INFO("[dbfs.releasedir %p:%p] ino=%lu, fi=%p : trans=%p", dirop, req, ino, fi, dirop->trans);
 
     // update dirop. Must keep it open so that dbfs_dirop_error won't free it
+    // copy *fi since it's on the stack
+    dirop->fi = *fi;
+    dirop->fi.fh = (uint64_t) dirop;
     dirop->req = req;
-    assert(dirop->fi == fi);
+    
+    if (dirop->trans) {
+        // we can commit the transaction, although we didn't make any changes
+        // if this fails the transaction, then dbfs_dirop_error will take care of sending the error, and dirop->req will be
+        // NULL
+        if (evsql_trans_commit(dirop->trans))
+            SERROR(err = EIO);
 
-    // we can commit the transaction, although we didn't make any changes
-    // if this fails the transaction, then dbfs_dirop_error will take care of sending the error, and dirop->req will be
-    // NULL
-    if (evsql_trans_commit(dirop->trans))
-        SERROR(err = EIO);
+    } else {
+        // trans failed earlier, so have releasedir just succeed
+        if ((err = fuse_reply_err(req, 0)))
+            EERROR(err, "fuse_reply_err");
 
-    // not open anymore
+        // req is done
+        dirop->req = NULL;
+    }
+
+    // fall-through to cleanup
+    err = 0;
+
+error:
+    // the dirop is not open anymore and can be freed once done with
     dirop->open = 0;
 
-    // XXX: handle interrupts
-    
-    // wait
-    return;
+    // if trans_commit triggered an error but didn't call dbfs_dirop_error, we need to take care of it
+    if (err && dirop->req) {
+        int err2;
 
-error:
-    if (dirop->req) {
         // we handle the req
         dirop->req = NULL;
 
+        if ((err2 = fuse_reply_err(req, err)))
+            EWARNING(err2, "fuse_reply_err");
+    } 
+    
+    // same for trans, we need to abort it if trans_commit failed and fs_dirop_error didn't get called
+    if (err && dirop->trans) {
         dbfs_dirop_free(dirop);
-
-        if ((err = fuse_reply_err(req, err)))
-            EWARNING(err, "fuse_reply_err");
+    
+    } else
+      // alternatively, if the trans error'd itself away (now or earlier), we don't need to keep the dirop around
+      // anymore now that we've checkd its state
+      if (!dirop->trans) {
+        dbfs_dirop_free(dirop);
     }
 }
 
@@ -475,6 +712,7 @@
     .getattr        = dbfs_getattr,
 
     .opendir        = dbfs_opendir,
+    .readdir        = dbfs_readdir,
     .releasedir     = dbfs_releasedir,
 };
 
@@ -521,7 +759,7 @@
 error :
     // cleanup
     if (ctx.ev_fuse)
-        evfuse_close(ctx.ev_fuse);
+        evfuse_free(ctx.ev_fuse);
 
     // XXX: ctx.db
     
--- a/src/dirbuf.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/dirbuf.c	Mon Oct 13 02:27:59 2008 +0300
@@ -5,11 +5,11 @@
 #include "lib/log.h"
 #include "lib/math.h"
 
-int dirbuf_init (struct dirbuf *buf, size_t req_size) {
+int dirbuf_init (struct dirbuf *buf, size_t req_size, off_t req_off) {
     buf->len = req_size;
-    buf->off = 0;
+    buf->req_off = req_off;
     
-    INFO("\tdirbuf.init: req_size=%zu", req_size);
+    DEBUG("\tdirbuf.init: req_size=%zu", req_size);
 
     // allocate the mem
     if ((buf->buf = malloc(buf->len)) == NULL)
@@ -22,7 +22,7 @@
     return -1;
 }
 
-size_t difbuf_estimate (size_t req_size, size_t min_namelen) {
+size_t dirbuf_estimate (struct dirbuf *buf, size_t min_namelen) {
     char namebuf[DIRBUF_NAME_MAX];
     int i;
     
@@ -32,18 +32,18 @@
 
     namebuf[i] = '\0';
 
-    return req_size / (fuse_add_direntry(NULL, NULL, 0, namebuf, NULL, 0));
+    return buf->len / (fuse_add_direntry(NULL, NULL, 0, namebuf, NULL, 0));
 }
 
-int dirbuf_add (fuse_req_t req, off_t req_off, struct dirbuf *buf, off_t ent_off, off_t next_off, const char *ent_name, fuse_ino_t ent_ino, mode_t ent_mode) {
+int dirbuf_add (fuse_req_t req, struct dirbuf *buf, off_t ent_off, off_t next_off, const char *ent_name, fuse_ino_t ent_ino, mode_t ent_mode) {
     struct stat stbuf;
     size_t ent_size;
 
-    INFO("\tdirbuf.add: req_off=%zu, buf->len=%zu, buf->off=%zu, ent_off=%zu, next_off=%zu, ent_name=`%s`, ent_ino=%lu, ent_mode=%07o",
+    DEBUG("\tdirbuf.add: req_off=%zu, buf->len=%zu, buf->off=%zu, ent_off=%zu, next_off=%zu, ent_name=`%s`, ent_ino=%lu, ent_mode=%07o",
         req_off, buf->len, buf->off, ent_off, next_off, ent_name, ent_ino, ent_mode);
     
     // skip entries as needed
-    if (ent_off < req_off) 
+    if (ent_off < buf->req_off) 
         return 0;
 
     // set ino
@@ -70,12 +70,16 @@
     // send the reply, return the error later
     err = fuse_reply_buf(req, buf->buf, buf->off);
 
-    INFO("\tdirbuf.done: size=%zu/%zu, err=%d", buf->off, buf->len, err);
+    DEBUG("\tdirbuf.done: size=%zu/%zu, err=%d", buf->off, buf->len, err);
 
     // free the dirbuf
-    free(buf->buf);
+    dirbuf_release(buf);
 
     // return the error code
     return err;
 }
 
+void dirbuf_release (struct dirbuf *buf) {
+    free(buf->buf); buf->buf = NULL;
+}
+
--- a/src/dirbuf.h	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/dirbuf.h	Mon Oct 13 02:27:59 2008 +0300
@@ -13,27 +13,29 @@
 struct dirbuf {
     char *buf;
     size_t len;
-    size_t off;
+    off_t off, req_off;
 };
 
 // maximum length for a dirbuf name, including NUL byte
 #define DIRBUF_NAME_MAX 256
 
 /*
+ * Initialize a dirbuf for a request. The dirbuf will be filled with at most req_size bytes of dir entries.
+ *
+ *  req_size    - how many bytes of dirbuf data we want, at most 
+ *  req_off     - the offset of the first dirent to include
+ */
+int dirbuf_init (struct dirbuf *buf, size_t req_size, off_t req_off);
+
+/*
  * Estimate how many dir entries will, at most, fit into a difbuf of the given size, based on a minimum filename size.
  */
-size_t difbuf_estimate (size_t req_size, size_t min_namelen);
-
-/*
- * Initialize a dirbuf for a request. The dirbuf will be filled with at most req_size bytes of dir entries.
- */
-int dirbuf_init (struct dirbuf *buf, size_t req_size);
+size_t dirbuf_estimate (struct dirbuf *buf, size_t min_namelen);
 
 /*
  * Add an dir entry to the dirbuf. The dirbuf should not be full.
  *
  * Offsets are followed:
- *  req_off     - the offset of the first dirent to include
  *  ent_off     - the offset of this dirent
  *  next_off    - the offset of the next dirent
  *
@@ -41,11 +43,18 @@
  *
  * Returns 0 if the ent was added or skipped, -1 on error, 1 if the dirbuf is full (no more ents should be added).
  */
-int dirbuf_add (fuse_req_t req, off_t req_off, struct dirbuf *buf, off_t ent_off, off_t next_off, const char *ent_name, fuse_ino_t ent_ino, mode_t ent_mode);
+int dirbuf_add (fuse_req_t req, struct dirbuf *buf, off_t ent_off, off_t next_off, const char *ent_name, fuse_ino_t ent_ino, mode_t ent_mode);
 
 /*
  * Attempt to send the readdir reply, free the buf, and return the error code from fuse_reply_buf
  */
 int dirbuf_done (fuse_req_t req, struct dirbuf *buf);
 
+/*
+ * Release the dirop without sending any reply back.
+ *
+ * This is safe to be called multiple times.
+ */
+void dirbuf_release (struct dirbuf *buf);
+
 #endif /* DIRBUF_H */
--- a/src/evfuse.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/evfuse.c	Mon Oct 13 02:27:59 2008 +0300
@@ -26,6 +26,9 @@
     char *recv_buf;
 };
 
+// prototypes
+void evfuse_close (struct evfuse *ctx);
+
 static void _evfuse_ev_read (evutil_socket_t fd, short what, void *arg) {
     struct evfuse *ctx = arg;
     struct fuse_chan *ch = ctx->chan;
@@ -58,7 +61,7 @@
     return;
 
 error:
-    // fail
+    // close, but don't free
     evfuse_close(ctx);
 }
 
@@ -104,28 +107,47 @@
     return ctx;
 
 error:
-    free(ctx);
+    evfuse_free(ctx);
 
     return NULL;
 }
 
 void evfuse_close (struct evfuse *ctx) {
-    // remove our event
-    if (event_del(ctx->ev))
-        PWARNING("event_del");
+    if (ctx->ev) {
+        // remove our event
+        if (event_del(ctx->ev))
+            PWARNING("event_del");
 
-    // remove the chan
-    fuse_session_remove_chan(ctx->chan);
-    
-    // destroy the session
-    fuse_session_destroy(ctx->session);
+        ctx->ev = NULL;
+    }
 
-    // unmount
-    fuse_unmount(ctx->mountpoint, ctx->chan);
-    
+    if (ctx->session) {
+        // remove the chan
+        fuse_session_remove_chan(ctx->chan);
+
+        // destroy the session
+        fuse_session_destroy(ctx->session);
+
+        ctx->session = NULL;
+    }
+
+    if (ctx->chan) {
+        // unmount
+        fuse_unmount(ctx->mountpoint, ctx->chan);
+
+        ctx->chan = NULL;
+    }
+
     // free
-    free(ctx->recv_buf);
-    free(ctx->mountpoint);
-    free(ctx);
+    free(ctx->recv_buf); ctx->recv_buf = NULL;
+    free(ctx->mountpoint); ctx->mountpoint = NULL;
 }
 
+void evfuse_free (struct evfuse *ctx) {
+    if (ctx) {
+        evfuse_close(ctx);
+
+        free(ctx);
+    }
+}
+
--- a/src/evfuse.h	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/evfuse.h	Mon Oct 13 02:27:59 2008 +0300
@@ -17,9 +17,11 @@
 struct evfuse *evfuse_new (struct event_base *evbase, struct fuse_args *args, struct fuse_lowlevel_ops *llops, void *cb_data);
 
 /*
- * Close a evfuse context.
+ * Close and free evfuse context.
+ *
+ * Safe to call after errors/llops.destroy
  */
-void evfuse_close (struct evfuse *ctx);
+void evfuse_free (struct evfuse *ctx);
 
 #endif /* EVFUSE_H */
 
--- a/src/evsql.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/evsql.c	Mon Oct 13 02:27:59 2008 +0300
@@ -69,6 +69,9 @@
  * The command should already be taken care of (NULL).
  */
 static void _evsql_query_free (struct evsql_query *query) {
+    if (!query)
+        return;
+        
     assert(query->command == NULL);
     
     // free params if present
@@ -83,12 +86,18 @@
 
 /*
  * Execute the callback if res is given, and free the query.
+ *
+ * The query has been aborted, it will simply be freed
  */
 static void _evsql_query_done (struct evsql_query *query, const struct evsql_result_info *res) {
-    if (res) 
-        // call the callback
-        query->cb_fn(res, query->cb_arg);
-    
+    if (res) {
+        if (query->cb_fn)
+            // call the callback
+            query->cb_fn(res, query->cb_arg);
+        else
+            WARNING("supressing cb_fn because query was aborted");
+    }
+
     // free
     _evsql_query_free(query);
 }
@@ -171,12 +180,15 @@
 
 /*
  * Fail a single query, this will trigger the callback and free it.
+ *
+ * NOTE: Only for *TRANSACTIONLESS* queries.
  */
 static void _evsql_query_fail (struct evsql* evsql, struct evsql_query *query) {
     struct evsql_result_info res; ZINIT(res);
     
     // set up the result_info
     res.evsql = evsql;
+    res.trans = NULL;
     res.error = 1;
     
     // finish off the query
@@ -187,16 +199,21 @@
  * Fail a transaction, this will silently drop any query, trigger the error callback, two-way-deassociate/release the
  * conn, and then free the trans.
  */ 
-static void _evsql_trans_fail (struct evsql_trans *trans, int silent) {
+static void _evsql_trans_fail (struct evsql_trans *trans) {
     if (trans->query) {
         // free the query silently
         _evsql_query_free(trans->query); trans->query = NULL;
+
+        // also deassociate it from the conn!
+        trans->conn->query = NULL;
     }
 
     // tell the user
     // XXX: trans is in a bad state during this call
-    if (!silent)
+    if (trans->error_fn)
         trans->error_fn(trans, trans->cb_arg);
+    else
+        WARNING("supressing error because error_fn was NULL");
  
     // deassociate and release the conn
     trans->conn->trans = NULL; _evsql_conn_release(trans->conn); trans->conn = NULL;
@@ -215,7 +232,7 @@
 static void _evsql_conn_fail (struct evsql_conn *conn) {
     if (conn->trans) {
         // let transactions handle their connection failures
-        _evsql_trans_fail(conn->trans, 0);
+        _evsql_trans_fail(conn->trans);
 
     } else {
         if (conn->query) {
@@ -302,13 +319,15 @@
     return;
 
 error:
-    _evsql_trans_fail(res->trans, 0);
+    _evsql_trans_fail(res->trans);
 }
 
 /*
  * The transaction's connection is ready, send the 'BEGIN' query.
+ *
+ * If anything fails, calls _evsql_trans_fail and returns nonzero, zero on success
  */
-static void _evsql_trans_conn_ready (struct evsql *evsql, struct evsql_trans *trans) {
+static int _evsql_trans_conn_ready (struct evsql *evsql, struct evsql_trans *trans) {
     char trans_sql[EVSQL_QUERY_BEGIN_BUF];
     const char *isolation_level;
     int ret;
@@ -345,15 +364,17 @@
         ERROR("trans_sql overflow: %d >= %d", ret, EVSQL_QUERY_BEGIN_BUF);
     
     // execute the query
-    if (evsql_query(evsql, trans, trans_sql, _evsql_trans_ready, NULL))
+    if (evsql_query(evsql, trans, trans_sql, _evsql_trans_ready, NULL) == NULL)
         ERROR("evsql_query");
     
     // success
-    return;
+    return 0;
 
 error:
     // fail the transaction
-    _evsql_trans_fail(trans, 0);
+    _evsql_trans_fail(trans);
+
+    return -1;
 }
 
 /*
@@ -364,7 +385,8 @@
 
     if (conn->trans)
         // notify the transaction
-        _evsql_trans_conn_ready(conn->evsql, conn->trans);
+        // don't care about errors
+        (void) _evsql_trans_conn_ready(conn->evsql, conn->trans);
     
     else
         // pump any waiting transactionless queries
@@ -403,6 +425,7 @@
     
     // set up the result_info
     res.evsql = conn->evsql;
+    res.trans = conn->trans;
     
     if (query->result.evpq == NULL) {
         // if a query didn't return any results (bug?), warn and fail the query
@@ -429,6 +452,11 @@
         // we can deassign the trans's query
         conn->trans->query = NULL;
 
+        // was an abort?
+        if (!query->cb_fn)
+            // notify the user that the transaction query has been aborted
+            conn->trans->ready_fn(conn->trans, conn->trans->cb_arg);
+
         // then hand the query to the user
         _evsql_query_done(query, &res);
         
@@ -616,10 +644,15 @@
  * request should be dropped.
  */
 static int _evsql_conn_get (struct evsql *evsql, struct evsql_conn **conn_ptr, int may_queue) {
+    int have_nontrans = 0;
     *conn_ptr = NULL;
     
     // find a connection that isn't busy and is ready (unless the query queue is empty).
     LIST_FOREACH(*conn_ptr, &evsql->conn_list, entry) {
+        // we can only have a query enqueue itself if there is a non-trans conn it can later use
+        if (!(*conn_ptr)->trans)
+            have_nontrans = 1;
+
         // skip busy conns always
         if (_evsql_conn_busy(*conn_ptr))
             continue;
@@ -637,8 +670,8 @@
     if (*conn_ptr)
         return 0;
 
-    // return NULL if may_queue and the conn list is not empty
-    if (may_queue && !LIST_EMPTY(&evsql->conn_list))
+    // return NULL if may_queue and we have a non-trans conn that we can, at some point, use
+    if (may_queue && have_nontrans)
         return 0;
     
     // we need to open a new connection
@@ -660,7 +693,6 @@
 
     // store
     trans->evsql = evsql;
-    trans->error_fn = error_fn;
     trans->ready_fn = ready_fn;
     trans->done_fn = done_fn;
     trans->cb_arg = cb_arg;
@@ -675,13 +707,19 @@
 
     // is it already ready?
     if (_evsql_conn_ready(trans->conn) > 0) {
-        // call _evsql_trans_conn_ready directly
-        _evsql_trans_conn_ready(evsql, trans);
+        // call _evsql_trans_conn_ready directly, it will handle cleanup (silently, !error_fn)
+        if (_evsql_trans_conn_ready(evsql, trans)) {
+            // return NULL directly
+            return NULL;
+        }
 
     } else {
         // otherwise, wait for the conn to be ready
          
     }
+    
+    // and let it pass errors to the user
+    trans->error_fn = error_fn;
 
     // ok
     return trans;
@@ -734,8 +772,8 @@
 
         // execute directly
         if (_evsql_query_exec(trans->conn, query, command)) {
-            // ack, fail the connection
-            _evsql_conn_fail(trans->conn);
+            // ack, fail the transaction
+            _evsql_trans_fail(trans);
             
             // caller frees query
             goto error;
@@ -863,6 +901,18 @@
     return NULL;
 }
 
+void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query) {
+    assert(query);
+
+    if (trans) {
+        // must be the right query
+        assert(trans->query == query);
+    }
+
+    // just strip the callback and wait for it to complete as normal
+    query->cb_fn = NULL;
+}
+
 void _evsql_trans_commit_res (const struct evsql_result_info *res, void *arg) {
     (void) arg;
 
@@ -882,14 +932,27 @@
     return;
 
 error:
-    _evsql_trans_fail(res->trans, 0);
+    _evsql_trans_fail(res->trans);
 }
 
 int evsql_trans_commit (struct evsql_trans *trans) {
     static const char *sql = "COMMIT TRANSACTION";
+
+    if (trans->query)
+        ERROR("cannot COMMIT because transaction is still busy");
     
     // query
-    return (evsql_query(trans->evsql, trans, sql, _evsql_trans_commit_res, NULL) != NULL);
+    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_commit_res, NULL) == NULL)
+        goto error;
+    
+    // mark it as commited in case someone wants to abort it
+    trans->has_commit = 1;
+
+    // success
+    return 0;
+
+error:
+    return -1;
 }
 
 void _evsql_trans_rollback_res (const struct evsql_result_info *res, void *arg) {
@@ -908,14 +971,49 @@
     return;
 
 error:
-    // but do it silently
-    _evsql_trans_fail(res->trans, 1);
+    // fail the connection too, errors are supressed
+    _evsql_trans_fail(res->trans);
 }
 
-int evsql_trans_abort (struct evsql_trans *trans) {
+/*
+ * Used as the ready_fn callback in case of abort, otherwise directly
+ */
+void _evsql_trans_rollback (struct evsql_trans *trans, void *unused) {
     static const char *sql = "ROLLBACK TRANSACTION";
 
+    (void) unused;
+
     // query
-    return (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, NULL) != NULL);
+    if (evsql_query(trans->evsql, trans, sql, _evsql_trans_rollback_res, NULL) == NULL) {
+        // fail the transaction/connection
+        _evsql_trans_fail(trans);
+    }
+
 }
 
+void evsql_trans_abort (struct evsql_trans *trans) {
+    // supress errors
+    trans->error_fn = NULL;
+    
+    if (trans->has_commit) {
+        // abort after commit doesn't make sense
+        FATAL("transaction was already commited");
+    }
+
+    if (trans->query) {
+        // gah, some query is running
+        WARNING("aborting pending query");
+        
+        // prepare to rollback once complete
+        trans->ready_fn = _evsql_trans_rollback;
+        
+        // abort
+        evsql_query_abort(trans, trans->query);
+
+    } else {
+        // just rollback directly
+        _evsql_trans_rollback(trans, NULL);
+
+    }
+}
+
--- a/src/evsql.h	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/evsql.h	Mon Oct 13 02:27:59 2008 +0300
@@ -175,15 +175,30 @@
 struct evsql_query *evsql_query_params (struct evsql *evsql, struct evsql_trans *trans, const char *command, const struct evsql_query_params *params, evsql_query_cb query_fn, void *cb_arg);
 
 /*
+ * Abort a query, the query callback will not be called, the query and any possible results will be discarded.
+ *
+ * This does not garuntee that the query will not execute, simply that you won't get the results.
+ *
+ * If the query is part of a transaction, then trans must be given, and the query must be the query that is currently
+ * executing on that trans. The transaction's ready_fn will be called once the query has been aborted.
+ */
+void evsql_query_abort (struct evsql_trans *trans, struct evsql_query *query);
+
+/*
  * Commit a transaction, calling done_fn if it was succesfull (error_fn otherwise).
+ *
+ * trans must be idle, just like for evsql_query.
+ *
+ * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error.
  */
 int evsql_trans_commit (struct evsql_trans *trans);
 
 /*
- * Abort a transaction, rolling it back. No callbacks will be called, unless this function returns nonzero, in which
- * case error_fn might be called.
+ * Abort a transaction, rolling it back. No callbacks will be called.
+ *
+ * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error.
  */
-int evsql_trans_abort (struct evsql_trans *trans);
+void evsql_trans_abort (struct evsql_trans *trans);
 
 /*
  * Transaction-handling functions
--- a/src/evsql_internal.h	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/evsql_internal.h	Mon Oct 13 02:27:59 2008 +0300
@@ -81,6 +81,9 @@
     // the transaction type
     enum evsql_trans_type type;
 
+    // has evsql_trans_commit be called?
+    int has_commit : 1;
+
     // our current query
     struct evsql_query *query;
 
--- a/src/evsql_util.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/evsql_util.c	Mon Oct 13 02:27:59 2008 +0300
@@ -106,6 +106,9 @@
 
     if (evsql_result_binary(res, row, col, &data, sizeof(*uval), nullok))
         goto error;
+    
+    if (!data)
+        return 0;
 
     sval = ntohs(*((int16_t *) data));
 
@@ -126,6 +129,9 @@
 
     if (evsql_result_binary(res, row, col, &data, sizeof(*uval), nullok))
         goto error;
+    
+    if (!data)
+        return 0;
 
     sval = ntohl(*(int32_t *) data);
 
@@ -146,6 +152,9 @@
 
     if (evsql_result_binary(res, row, col, &data, sizeof(*uval), nullok))
         goto error;
+    
+    if (!data)
+        return 0;
 
     sval = ntohq(*(int64_t *) data);
 
--- a/src/hello.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/hello.c	Mon Oct 13 02:27:59 2008 +0300
@@ -96,12 +96,12 @@
     }
 
     // fill in the dirbuf
-    if (dirbuf_init(&buf, size))
+    if (dirbuf_init(&buf, size, off))
         ERROR("failed to init dirbuf");
 
-    err =   dirbuf_add(req, off, &buf, 0, 1,  ".",        1,    S_IFDIR )
-        ||  dirbuf_add(req, off, &buf, 1, 2,  "..",       1,    S_IFDIR )
-        ||  dirbuf_add(req, off, &buf, 2, 3,  file_name,  2,    S_IFREG );
+    err =   dirbuf_add(req, &buf, 0, 1,  ".",        1,    S_IFDIR )
+        ||  dirbuf_add(req, &buf, 1, 2,  "..",       1,    S_IFDIR )
+        ||  dirbuf_add(req, &buf, 2, 3,  file_name,  2,    S_IFREG );
 
     if (err < 0)
         ERROR("failed to add dirents to buf");
@@ -234,7 +234,7 @@
 error :
     // cleanup
     if (ctx.ev_fuse)
-        evfuse_close(ctx.ev_fuse);
+        evfuse_free(ctx.ev_fuse);
     
     if (ctx.signals)
         signals_free(ctx.signals);
--- a/src/lib/log.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/lib/log.c	Mon Oct 13 02:27:59 2008 +0300
@@ -9,6 +9,9 @@
 static void _generic_err_vargs (int flags, const char *func, int err, const char *fmt, va_list va) {
     FILE *stream = flags & LOG_DISPLAY_STDERR ? stderr : stdout;
 
+    if (flags & LOG_DISPLAY_FATAL)
+        fprintf(stream, "FATAL: ");
+
     if (func)
         fprintf(stream, "%s: ", func);
     
@@ -33,7 +36,7 @@
     va_list va;
 
     va_start(va, fmt);
-    _generic_err_vargs(flags, func, err, fmt, va);
+    _generic_err_vargs(flags | LOG_DISPLAY_FATAL, func, err, fmt, va);
     va_end(va);
       
     exit(EXIT_FAILURE);
--- a/src/lib/log.h	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/lib/log.h	Mon Oct 13 02:27:59 2008 +0300
@@ -12,6 +12,8 @@
     LOG_DISPLAY_PERR =      0x02,
 
     LOG_DISPLAY_NONL =      0x04,
+
+    LOG_DISPLAY_FATAL =     0x08,
 };
 
 
@@ -53,6 +55,7 @@
 #include "error.h"
 
 #define WARNING(...) err_func(__func__, __VA_ARGS__)
+#define NWARNING(...) err_func_nonl(__func__, __VA_ARGS__)
 #define PWARNING(...) perr_func(__func__, __VA_ARGS__)
 #define EWARNING(err, ...) eerr_func(__func__, (err), __VA_ARGS__)
 
--- a/src/simple.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/simple.c	Mon Oct 13 02:27:59 2008 +0300
@@ -157,13 +157,13 @@
         EERROR(err = ENOTDIR, "bad mode");
 
     // fill in the dirbuf
-    if (dirbuf_init(&buf, size))
+    if (dirbuf_init(&buf, size, off))
         ERROR("failed to init dirbuf");
     
     // add . and ..
     // we set the next offset to 2, because all dirent offsets will be larger than that
-    err =   dirbuf_add(req, off, &buf, 0, 1, ".",   dir_node->inode,    S_IFDIR )
-        ||  dirbuf_add(req, off, &buf, 1, 2, "..",  dir_node->inode,    S_IFDIR );
+    err =   dirbuf_add(req, &buf, 0, 1, ".",   dir_node->inode,    S_IFDIR )
+        ||  dirbuf_add(req, &buf, 1, 2, "..",  dir_node->inode,    S_IFDIR );
     
     if (err != 0)
         EERROR(err, "failed to add . and .. dirents");
@@ -175,7 +175,7 @@
             continue;
         
         // child node offsets are just inode + 2
-        if ((err = dirbuf_add(req, off, &buf, node->inode + 2, node->inode + 3, node->name, node->inode, node->mode_type)) < 0)
+        if ((err = dirbuf_add(req, &buf, node->inode + 2, node->inode + 3, node->name, node->inode, node->mode_type)) < 0)
             EERROR(err, "failed to add dirent for inode=%lu", node->inode);
         
         // stop if it's full
--- a/src/simple_hello.c	Sun Oct 12 21:59:52 2008 +0300
+++ b/src/simple_hello.c	Mon Oct 13 02:27:59 2008 +0300
@@ -59,7 +59,7 @@
 error :
     // cleanup
     if (ctx.ev_fuse)
-        evfuse_close(ctx.ev_fuse);
+        evfuse_free(ctx.ev_fuse);
 
 /*
     if (ctx.fs)