diff -r 61668c57f4bb -r 461be4cd34a3 src/dbfs.c --- 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(¶ms, 0, dirop->ino) + ) + SERROR(err = EIO); + + // query + if (evsql_query_params(ctx->db, dirop->trans, sql, ¶ms, 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(¶ms, 0, dirop->ino) + || evsql_param_uint32(¶ms, 1, off) + || evsql_param_uint32(¶ms, 2, dirbuf_estimate(&dirop->dirbuf, 0)) + ) + SERROR(err = EIO); + + // query + if (evsql_query_params(ctx->db, dirop->trans, sql, ¶ms, 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