# HG changeset patch # User Tero Marttila # Date 1223854079 -10800 # Node ID 461be4cd34a3f3535983a6665a0bef672ee32222 # Parent 61668c57f4bbc061cabf6f6f8beacc1ed6e12a07 working open/read/close -dir diff -r 61668c57f4bb -r 461be4cd34a3 doc/fuse_db.sql --- 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); + 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 diff -r 61668c57f4bb -r 461be4cd34a3 src/dirbuf.c --- 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; +} + diff -r 61668c57f4bb -r 461be4cd34a3 src/dirbuf.h --- 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 */ diff -r 61668c57f4bb -r 461be4cd34a3 src/evfuse.c --- 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); + } +} + diff -r 61668c57f4bb -r 461be4cd34a3 src/evfuse.h --- 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 */ diff -r 61668c57f4bb -r 461be4cd34a3 src/evsql.c --- 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); + + } +} + diff -r 61668c57f4bb -r 461be4cd34a3 src/evsql.h --- 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 diff -r 61668c57f4bb -r 461be4cd34a3 src/evsql_internal.h --- 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; diff -r 61668c57f4bb -r 461be4cd34a3 src/evsql_util.c --- 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); diff -r 61668c57f4bb -r 461be4cd34a3 src/hello.c --- 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); diff -r 61668c57f4bb -r 461be4cd34a3 src/lib/log.c --- 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); diff -r 61668c57f4bb -r 461be4cd34a3 src/lib/log.h --- 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__) diff -r 61668c57f4bb -r 461be4cd34a3 src/simple.c --- 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 diff -r 61668c57f4bb -r 461be4cd34a3 src/simple_hello.c --- 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)