src/dbfs.c
changeset 27 461be4cd34a3
parent 26 61668c57f4bb
child 28 e944453ca924
--- 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