# HG changeset patch # User Tero Marttila # Date 1236464352 -7200 # Node ID 9dfc861273e588d38b563007afef586783581e1b # Parent 40a3b13ffc9dac8e240342ad5a488514b2bac7ef strip dbfs code away, and wrangle the code a bit diff -r 40a3b13ffc9d -r 9dfc861273e5 include/evsql.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/evsql.h Sun Mar 08 00:19:12 2009 +0200 @@ -0,0 +1,276 @@ +#ifndef EVSQL_H +#define EVSQL_H + +/* + * An event-based (Postgre)SQL client API using libevent + */ + +// XXX: libpq +#include +#include +#include + +/* + * The generic context handle + */ +struct evsql; + +/* + * A query handle + */ +struct evsql_query; + +/* + * Transaction handle + */ +struct evsql_trans; + +/* + * Transaction type + */ +enum evsql_trans_type { + EVSQL_TRANS_DEFAULT, + EVSQL_TRANS_SERIALIZABLE, + EVSQL_TRANS_REPEATABLE_READ, + EVSQL_TRANS_READ_COMMITTED, + EVSQL_TRANS_READ_UNCOMMITTED, +}; + +/* + * Parameter type + */ +enum evsql_param_format { + EVSQL_FMT_TEXT, + EVSQL_FMT_BINARY, +}; + +enum evsql_param_type { + EVSQL_PARAM_INVALID, + + EVSQL_PARAM_NULL_, + + EVSQL_PARAM_BINARY, + EVSQL_PARAM_STRING, + EVSQL_PARAM_UINT16, + EVSQL_PARAM_UINT32, + EVSQL_PARAM_UINT64, +}; + +/* + * Query parameter info. + * + * Use the EVSQL_PARAM_* macros to define the value of list + */ +struct evsql_query_params { + // nonzero to get results in binary format + enum evsql_param_format result_fmt; + + // the list of parameters, terminated by { 0, 0 } + struct evsql_query_param { + // the param type + enum evsql_param_type type; + + // pointer to the raw data + const char *data_raw; + + // the value + union { + uint16_t uint16; + uint32_t uint32; + uint64_t uint64; + } data; + + // the explicit length of the parameter if it's binary, zero for text. + // set to -1 to indicate that the value is still missing + ssize_t length; + } list[]; +}; + +// macros for defining evsql_query_params +#define EVSQL_PARAMS(result_fmt) { result_fmt, +#define EVSQL_PARAM(typenam) { EVSQL_PARAM_ ## typenam, NULL } +#define EVSQL_PARAMS_END { EVSQL_PARAM_INVALID, NULL } \ + } // <<< + +/* + * Result type + */ +struct evsql_result_info { + struct evsql *evsql; + struct evsql_trans *trans; + + int error; + + union evsql_result { + // libpq + PGresult *pq; + } result; +}; + +/* + * Callback for handling query results. + * + * The query has completed, either succesfully or unsuccesfully (nonzero .error). + * + * Use the evsql_result_* functions to manipulate the results. + */ +typedef void (*evsql_query_cb)(const struct evsql_result_info *res, void *arg); + +/* + * Callback for handling global-level errors. + * + * The evsql is not useable anymore. + * + * XXX: this is not actually called yet, no retry logic implemented. + */ +typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg); + +/* + * Callback for handling transaction-level errors. + * + * The transaction is not useable anymore. + */ +typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg); + +/* + * The transaction is ready for use. + */ +typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg); + +/* + * The transaction was commited, and should not be used anymore. + */ +typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg); + +/* + * Create a new PostgreSQL/libpq(evpq) -based evsql using the given conninfo. + * + * The given conninfo must stay valid for the duration of the evsql's lifetime. + */ +struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg); + +/* + * Create a new transaction. + * + * Transactions are separate connections that provide transaction-isolation. + * + * Once the transaction is ready for use, ready_fn will be called. If the transaction fails, any pending query will be + * forgotten, and error_fn called. This also includes some (but not all) cases where evsql_query returns nonzero. + * + */ +struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, evsql_trans_error_cb error_fn, evsql_trans_ready_cb ready_fn, evsql_trans_done_cb done_fn, void *cb_arg); + +/* + * Queue the given query for execution. + * + * If trans is specified (not NULL), then the transaction must be idle, and the query will be executed in that + * transaction's context. Otherwise, the query will be executed without a transaction, andmay be executed immediately, + * or if other similar queries are running, it will be queued for later execution. + * + * Once the query is complete (got a result, got an error, the connection failed), then the query_cb will be triggered. + */ +struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg); + +/* + * Same as evsql_query, but uses the SQL-level support for binding parameters. + */ +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. + * + * done_fn will never be called directly, always via the event loop. + * + * 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. + * + * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error. + */ +void evsql_trans_abort (struct evsql_trans *trans); + +/* + * Transaction-handling functions + */ + +// error string, meant to be called from evsql_trans_error_cb +const char *evsql_trans_error (struct evsql_trans *trans); + +/* + * Param-building functions + */ +int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len); +int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr); +int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval); +int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval); +int evsql_param_null (struct evsql_query_params *params, size_t param); +int evsql_params_clear (struct evsql_query_params *params); + +/* + * Query-handling functions + */ + +// print out a textual repr of the given query/params via DEBUG +void evsql_query_debug (const char *sql, const struct evsql_query_params *params); + +/* + * Result-handling functions + */ + +// get error message associated with function +const char *evsql_result_error (const struct evsql_result_info *res); + +// number of rows in the result +size_t evsql_result_rows (const struct evsql_result_info *res); + +// number of columns in the result +size_t evsql_result_cols (const struct evsql_result_info *res); + +// number of affected rows for UPDATE/INSERT +size_t evsql_result_affected (const struct evsql_result_info *res); + +// fetch the raw binary value from a result set, and return it via ptr +// if size is nonzero, check that the size of the field data matches +int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok); +int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok); + +// fetch certain kinds of values from a binary result set +int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok); +int evsql_result_uint32 (const struct evsql_result_info *res, size_t row, size_t col, uint32_t *uval, int nullok); +int evsql_result_uint64 (const struct evsql_result_info *res, size_t row, size_t col, uint64_t *uval, int nullok); + +// release the result set, freeing its memory +void evsql_result_free (const struct evsql_result_info *res); + +// platform-dependant aliases +#define evsql_result_ushort evsql_result_uint16 + +#if _LP64 +#define evsql_result_ulong evsql_result_uint64 +#else +#define evsql_result_ulong evsql_result_uint32 +#endif /* _LP64 */ + +/* + * Close a connection. Callbacks for waiting queries will not be run. + * + * XXX: not implemented yet. + */ +void evsql_close (struct evsql *evsql); + +#endif /* EVSQL_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/core.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core.c Sun Mar 08 00:19:12 2009 +0200 @@ -0,0 +1,1025 @@ +#define _GNU_SOURCE +#include +#include +#include + +#include "internal.h" +#include "lib/log.h" +#include "lib/error.h" +#include "lib/misc.h" + +/* + * A couple function prototypes + */ +static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn); + +/* + * Actually execute the given query. + * + * The backend should be able to accept the query at this time. + * + * You should assume that if trying to execute a query fails, then the connection should also be considred as failed. + */ +static int _evsql_query_exec (struct evsql_conn *conn, struct evsql_query *query, const char *command) { + int err; + + DEBUG("evsql.%p: exec query=%p on trans=%p on conn=%p:", conn->evsql, query, conn->trans, conn); + + switch (conn->evsql->type) { + case EVSQL_EVPQ: + // got params? + if (query->params.count) { + err = evpq_query_params(conn->engine.evpq, command, + query->params.count, + query->params.types, + query->params.values, + query->params.lengths, + query->params.formats, + query->params.result_format + ); + + } else { + // plain 'ole query + err = evpq_query(conn->engine.evpq, command); + } + + if (err) { + if (PQstatus(evpq_pgconn(conn->engine.evpq)) != CONNECTION_OK) + WARNING("conn failed"); + else + WARNING("query failed, dropping conn as well"); + } + + break; + + default: + FATAL("evsql->type"); + } + + if (!err) + // assign the query + conn->query = query; + + return err; +} + +/* + * Free the query and related resources, doesn't trigger any callbacks or remove from any queues. + * + * 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 + free(query->params.types); + free(query->params.values); + free(query->params.lengths); + free(query->params.formats); + + // free the query itself + free(query); +} + +/* + * 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) { + 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); +} + +/* + * XXX: + * / +static void _evsql_destroy (struct evsql *evsql, const struct evsql_result_info *res) { + struct evsql_query *query; + + // clear the queue + while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) { + _evsql_query_done(query, res); + + TAILQ_REMOVE(&evsql->query_queue, query, entry); + } + + // free + free(evsql); +} +*/ + +/* + * Free the transaction, it should already be deassociated from the query and conn. + */ +static void _evsql_trans_free (struct evsql_trans *trans) { + // ensure we don't leak anything + assert(trans->query == NULL); + assert(trans->conn == NULL); + + // free + free(trans); +} + +/* + * Release a connection. It should already be deassociated from the trans and query. + * + * Releases the engine, removes from the conn_list and frees this. + */ +static void _evsql_conn_release (struct evsql_conn *conn) { + // ensure we don't leak anything + assert(conn->trans == NULL); + assert(conn->query == NULL); + + // release the engine + switch (conn->evsql->type) { + case EVSQL_EVPQ: + evpq_release(conn->engine.evpq); + break; + + default: + FATAL("evsql->type"); + } + + // remove from list + LIST_REMOVE(conn, entry); + + // catch deadlocks + assert(!LIST_EMPTY(&conn->evsql->conn_list) || TAILQ_EMPTY(&conn->evsql->query_queue)); + + // free + free(conn); +} + +/* + * Release a transaction, it should already be deassociated from the query. + * + * Perform a two-way-deassociation with the conn, and then free the trans. + */ +static void _evsql_trans_release (struct evsql_trans *trans) { + assert(trans->query == NULL); + assert(trans->conn != NULL); + + // deassociate the conn + trans->conn->trans = NULL; trans->conn = NULL; + + // free the trans + _evsql_trans_free(trans); +} + +/* + * 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 + _evsql_query_done(query, &res); +} + +/* + * 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) { + 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 (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; + + // pump the queue for requests that were waiting for this connection + _evsql_pump(trans->evsql, NULL); + + // free the trans + _evsql_trans_free(trans); +} + +/* + * Fail a connection. If the connection is transactional, this will just call _evsql_trans_fail, but otherwise it will + * fail any ongoing query, and then release the connection. + */ +static void _evsql_conn_fail (struct evsql_conn *conn) { + if (conn->trans) { + // let transactions handle their connection failures + _evsql_trans_fail(conn->trans); + + } else { + if (conn->query) { + // fail the in-progress query + _evsql_query_fail(conn->evsql, conn->query); conn->query = NULL; + } + + // finish off the whole connection + _evsql_conn_release(conn); + } +} + +/* + * Processes enqueued non-transactional queries until the queue is empty, or we managed to exec a query. + * + * If execing a query on a connection fails, both the query and the connection are failed (in that order). + * + * Any further queries will then also be failed, because there's no reconnection/retry logic yet. + * + * This means that if conn is NULL, all queries are failed. + */ +static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn) { + struct evsql_query *query; + int err; + + // look for waiting queries + while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) { + // dequeue + TAILQ_REMOVE(&evsql->query_queue, query, entry); + + if (conn) { + // try and execute it + err = _evsql_query_exec(conn, query, query->command); + } + + // free the command buf + free(query->command); query->command = NULL; + + if (err || !conn) { + if (!conn) { + // warn when dropping queries + WARNING("failing query becuse there are no conns"); + } + + // fail the query + _evsql_query_fail(evsql, query); + + if (conn) { + // fail the connection + WARNING("failing the connection because a query-exec failed"); + + _evsql_conn_fail(conn); conn = NULL; + } + + } else { + // we have succesfully enqueued a query, and we can wait for this connection to complete + break; + + } + + // handle the rest of the queue + } + + // ok + return; +} + +/* + * Callback for a trans's 'BEGIN' query, which means the transaction is now ready for use. + */ +static void _evsql_trans_ready (const struct evsql_result_info *res, void *arg) { + (void) arg; + + assert(res->trans); + + // check for errors + if (res->error) + ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res)); + + // transaction is now ready for use + res->trans->ready_fn(res->trans, res->trans->cb_arg); + + // good + return; + +error: + _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 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; + + // determine the isolation_level to use + switch (trans->type) { + case EVSQL_TRANS_DEFAULT: + isolation_level = NULL; break; + + case EVSQL_TRANS_SERIALIZABLE: + isolation_level = "SERIALIZABLE"; break; + + case EVSQL_TRANS_REPEATABLE_READ: + isolation_level = "REPEATABLE READ"; break; + + case EVSQL_TRANS_READ_COMMITTED: + isolation_level = "READ COMMITTED"; break; + + case EVSQL_TRANS_READ_UNCOMMITTED: + isolation_level = "READ UNCOMMITTED"; break; + + default: + FATAL("trans->type: %d", trans->type); + } + + // build the trans_sql + if (isolation_level) + ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION ISOLATION LEVEL %s", isolation_level); + else + ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION"); + + // make sure it wasn't truncated + if (ret >= EVSQL_QUERY_BEGIN_BUF) + 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) == NULL) + ERROR("evsql_query"); + + // success + return 0; + +error: + // fail the transaction + _evsql_trans_fail(trans); + + return -1; +} + +/* + * The evpq connection was succesfully established. + */ +static void _evsql_evpq_connected (struct evpq_conn *_conn, void *arg) { + struct evsql_conn *conn = arg; + + if (conn->trans) + // notify the transaction + // don't care about errors + (void) _evsql_trans_conn_ready(conn->evsql, conn->trans); + + else + // pump any waiting transactionless queries + _evsql_pump(conn->evsql, conn); +} + +/* + * Got one result on this evpq connection. + */ +static void _evsql_evpq_result (struct evpq_conn *_conn, PGresult *result, void *arg) { + struct evsql_conn *conn = arg; + struct evsql_query *query = conn->query; + + assert(query != NULL); + + // if we get multiple results, only return the first one + if (query->result.evpq) { + WARNING("[evsql] evpq query returned multiple results, discarding previous one"); + + PQclear(query->result.evpq); query->result.evpq = NULL; + } + + // remember the result + query->result.evpq = result; +} + +/* + * No more results for this query. + */ +static void _evsql_evpq_done (struct evpq_conn *_conn, void *arg) { + struct evsql_conn *conn = arg; + struct evsql_query *query = conn->query; + struct evsql_result_info res; ZINIT(res); + + assert(query != NULL); + + // 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 + WARNING("[evsql] evpq query didn't return any results"); + + res.error = 1; + + } else if (strcmp(PQresultErrorMessage(query->result.evpq), "") != 0) { + // the query failed with some error + res.error = 1; + res.result.pq = query->result.evpq; + + } else { + res.error = 0; + res.result.pq = query->result.evpq; + + } + + // de-associate the query from the connection + conn->query = NULL; + + // how we handle query completion depends on if we're a transaction or not + if (conn->trans) { + // 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); + + } else { + // a transactionless query, so just finish it off and pump any other waiting ones + _evsql_query_done(query, &res); + + // pump the next one + _evsql_pump(conn->evsql, conn); + } +} + +/* + * The connection failed. + */ +static void _evsql_evpq_failure (struct evpq_conn *_conn, void *arg) { + struct evsql_conn *conn = arg; + + // just fail the conn + _evsql_conn_fail(conn); +} + +/* + * Our evpq behaviour + */ +static struct evpq_callback_info _evsql_evpq_cb_info = { + .fn_connected = _evsql_evpq_connected, + .fn_result = _evsql_evpq_result, + .fn_done = _evsql_evpq_done, + .fn_failure = _evsql_evpq_failure, +}; + +/* + * Allocate the generic evsql context. + */ +static struct evsql *_evsql_new_base (struct event_base *ev_base, evsql_error_cb error_fn, void *cb_arg) { + struct evsql *evsql = NULL; + + // allocate it + if ((evsql = calloc(1, sizeof(*evsql))) == NULL) + ERROR("calloc"); + + // store + evsql->ev_base = ev_base; + evsql->error_fn = error_fn; + evsql->cb_arg = cb_arg; + + // init + LIST_INIT(&evsql->conn_list); + TAILQ_INIT(&evsql->query_queue); + + // done + return evsql; + +error: + return NULL; +} + +/* + * Start a new connection and add it to the list, it won't be ready until _evsql_evpq_connected is called + */ +static struct evsql_conn *_evsql_conn_new (struct evsql *evsql) { + struct evsql_conn *conn = NULL; + + // allocate + if ((conn = calloc(1, sizeof(*conn))) == NULL) + ERROR("calloc"); + + // init + conn->evsql = evsql; + + // connect the engine + switch (evsql->type) { + case EVSQL_EVPQ: + if ((conn->engine.evpq = evpq_connect(evsql->ev_base, evsql->engine_conf.evpq, _evsql_evpq_cb_info, conn)) == NULL) + goto error; + + break; + + default: + FATAL("evsql->type"); + } + + // add it to the list + LIST_INSERT_HEAD(&evsql->conn_list, conn, entry); + + // success + return conn; + +error: + free(conn); + + return NULL; +} + +struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg) { + struct evsql *evsql = NULL; + + // base init + if ((evsql = _evsql_new_base (ev_base, error_fn, cb_arg)) == NULL) + goto error; + + // store conf + evsql->engine_conf.evpq = pq_conninfo; + + // pre-create one connection + if (_evsql_conn_new(evsql) == NULL) + goto error; + + // done + return evsql; + +error: + // XXX: more complicated than this? + free(evsql); + + return NULL; +} + +/* + * Checks if the connection is already allocated for some other trans/query. + * + * Returns: + * 0 connection idle, can be allocated + * >1 connection busy + */ +static int _evsql_conn_busy (struct evsql_conn *conn) { + // transactions get the connection to themselves + if (conn->trans) + return 1; + + // if it has a query assigned, it's busy + if (conn->query) + return 1; + + // otherwise, it's all idle + return 0; +} + +/* + * Checks if the connection is ready for use (i.e. _evsql_evpq_connected was called). + * + * The connection should not already have a query running. + * + * Returns + * <0 the connection is not valid (failed, query in progress) + * 0 the connection is still pending, and will become ready at some point + * >0 it's ready + */ +static int _evsql_conn_ready (struct evsql_conn *conn) { + switch (conn->evsql->type) { + case EVSQL_EVPQ: { + enum evpq_state state = evpq_state(conn->engine.evpq); + + switch (state) { + case EVPQ_CONNECT: + return 0; + + case EVPQ_CONNECTED: + return 1; + + case EVPQ_QUERY: + case EVPQ_INIT: + case EVPQ_FAILURE: + return -1; + + default: + FATAL("evpq_state: %d", state); + } + + } + + default: + FATAL("evsql->type: %d", conn->evsql->type); + } +} + +/* + * Allocate a connection for use and return it via *conn_ptr, or if may_queue is nonzero and the connection pool is + * getting full, return NULL (query should be queued). + * + * Note that the returned connection might not be ready for use yet (if we created a new one, see _evsql_conn_ready). + * + * Returns zero if a connection was found or the request should be queued, or nonzero if something failed and the + * 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; + + // accept pending conns as long as there are NO enqueued queries (might cause deadlock otherwise) + if (_evsql_conn_ready(*conn_ptr) == 0 && TAILQ_EMPTY(&evsql->query_queue)) + break; + + // accept conns that are in a fully ready state + if (_evsql_conn_ready(*conn_ptr) > 0) + break; + } + + // if we found an idle connection, we can just return that right away + if (*conn_ptr) + return 0; + + // 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 + if ((*conn_ptr = _evsql_conn_new(evsql)) == NULL) + goto error; + + // good + return 0; +error: + return -1; +} + +struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, evsql_trans_error_cb error_fn, evsql_trans_ready_cb ready_fn, evsql_trans_done_cb done_fn, void *cb_arg) { + struct evsql_trans *trans = NULL; + + // allocate it + if ((trans = calloc(1, sizeof(*trans))) == NULL) + ERROR("calloc"); + + // store + trans->evsql = evsql; + trans->ready_fn = ready_fn; + trans->done_fn = done_fn; + trans->cb_arg = cb_arg; + trans->type = type; + + // find a connection + if (_evsql_conn_get(evsql, &trans->conn, 0)) + ERROR("_evsql_conn_get"); + + // associate the conn + trans->conn->trans = trans; + + // is it already ready? + if (_evsql_conn_ready(trans->conn) > 0) { + // 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; + +error: + free(trans); + + return NULL; +} + +/* + * Validate and allocate the basic stuff for a new query. + */ +static struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg) { + struct evsql_query *query = NULL; + + // if it's part of a trans, then make sure the trans is idle + if (trans && trans->query) + ERROR("transaction is busy"); + + // allocate it + if ((query = calloc(1, sizeof(*query))) == NULL) + ERROR("calloc"); + + // store + query->cb_fn = query_fn; + query->cb_arg = cb_arg; + + // success + return query; + +error: + return NULL; +} + +/* + * Handle a new query. + * + * For transactions this will associate the query and then execute it, otherwise this will either find an idle + * connection and send the query, or enqueue it. + */ +static int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command) { + // transaction queries are handled differently + if (trans) { + // it's an in-transaction query + assert(trans->query == NULL); + + // assign the query + trans->query = query; + + // execute directly + if (_evsql_query_exec(trans->conn, query, command)) { + // ack, fail the transaction + _evsql_trans_fail(trans); + + // caller frees query + goto error; + } + + } else { + struct evsql_conn *conn; + + // find an idle connection + if ((_evsql_conn_get(evsql, &conn, 1))) + ERROR("couldn't allocate a connection for the query"); + + // we must enqueue if no idle conn or the conn is not yet ready + if (conn && _evsql_conn_ready(conn) > 0) { + // execute directly + if (_evsql_query_exec(conn, query, command)) { + // ack, fail the connection + _evsql_conn_fail(conn); + + // make sure we don't deadlock any queries, but if this query got a conn directly, then we shouldn't + // have any queries enqueued anyways + assert(TAILQ_EMPTY(&evsql->query_queue)); + + // caller frees query + goto error; + } + + } else { + // copy the command for later execution + if ((query->command = strdup(command)) == NULL) + ERROR("strdup"); + + // enqueue until some connection pumps the queue + TAILQ_INSERT_TAIL(&evsql->query_queue, query, entry); + } + } + + // ok, good + return 0; + +error: + return -1; +} + +struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg) { + struct evsql_query *query = NULL; + + // alloc new query + if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL) + goto error; + + // just execute the command string directly + if (_evsql_query_enqueue(evsql, trans, query, command)) + goto error; + + // ok + return query; + +error: + _evsql_query_free(query); + + return NULL; +} + +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) { + struct evsql_query *query = NULL; + const struct evsql_query_param *param; + int idx; + + // alloc new query + if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL) + goto error; + + // count the params + for (param = params->list; param->type; param++) + query->params.count++; + + // allocate the vertical storage for the parameters + if (0 + + || !(query->params.types = calloc(query->params.count, sizeof(Oid))) + || !(query->params.values = calloc(query->params.count, sizeof(char *))) + || !(query->params.lengths = calloc(query->params.count, sizeof(int))) + || !(query->params.formats = calloc(query->params.count, sizeof(int))) + ) + ERROR("calloc"); + + // transform + for (param = params->list, idx = 0; param->type; param++, idx++) { + // `set for NULLs, otherwise not + query->params.types[idx] = param->data_raw ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID; + + // values + query->params.values[idx] = param->data_raw; + + // lengths + query->params.lengths[idx] = param->length; + + // formats, binary if length is nonzero, but text for NULLs + query->params.formats[idx] = param->length && param->data_raw ? 1 : 0; + } + + // result format + switch (params->result_fmt) { + case EVSQL_FMT_TEXT: + query->params.result_format = 0; break; + + case EVSQL_FMT_BINARY: + query->params.result_format = 1; break; + + default: + FATAL("params.result_fmt: %d", params->result_fmt); + } + + // execute it + if (_evsql_query_enqueue(evsql, trans, query, command)) + goto error; + +#ifdef DEBUG_ENABLED + // debug it? + DEBUG("evsql.%p: enqueued query=%p on trans=%p", evsql, query, trans); + evsql_query_debug(command, params); +#endif /* DEBUG_ENABLED */ + + // ok + return query; + +error: + _evsql_query_free(query); + + 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; + + assert(res->trans); + + // check for errors + if (res->error) + ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res)); + + // transaction is now done + res->trans->done_fn(res->trans, res->trans->cb_arg); + + // release it + _evsql_trans_release(res->trans); + + // success + return; + +error: + _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 + 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) { + (void) arg; + + assert(res->trans); + + // fail the connection on errors + if (res->error) + ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res)); + + // release it + _evsql_trans_release(res->trans); + + // success + return; + +error: + // fail the connection too, errors are supressed + _evsql_trans_fail(res->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 + 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 40a3b13ffc9d -r 9dfc861273e5 src/dbfs.c --- a/src/dbfs.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ - -/* - * A simple PostgreSQL-based filesystem. - */ - -#include -#include -#include -#include - -#include - -#include "dbfs.h" -#include "evfuse.h" -#include "evsql.h" -#include "lib/log.h" -#include "lib/signals.h" -#include "lib/misc.h" - -#define CONNINFO_DEFAULT "dbname=dbfs port=5433" - -int main (int argc, char **argv) { - struct event_base *ev_base = NULL; - struct signals *signals = NULL; - struct dbfs *ctx = NULL; - const char *db_conninfo; - struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv); - - // parse args, XXX: fuse_args - db_conninfo = CONNINFO_DEFAULT; - - // init libevent - if ((ev_base = event_base_new()) == NULL) - ERROR("event_base_new"); - - // setup signals - if ((signals = signals_default(ev_base)) == NULL) - ERROR("signals_default"); - - // setup dbfs - if ((ctx = dbfs_new(ev_base, &fuse_args, db_conninfo)) == NULL) - ERROR("dbfs_new"); - - // run libevent - INFO("running libevent loop"); - - if (event_base_dispatch(ev_base)) - PERROR("event_base_dispatch"); - - // clean shutdown - -error : - if (ctx) - dbfs_free(ctx); - - if (signals) - signals_free(signals); - - if (ev_base) - event_base_free(ev_base); - - fuse_opt_free_args(&fuse_args); -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs.h --- a/src/dbfs.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -#ifndef DBFS_H -#define DBFS_H - -#include "evfuse.h" - -/* - * External interface for dbfs - */ - -/* - * Context struct. - */ -struct dbfs; - -/* - * Create the evsql and evfuse contexts and run the fs - */ -struct dbfs *dbfs_new (struct event_base *ev_base, struct fuse_args *args, const char *db_conninfo); - -/* - * Release the dbfs's resources and free it - */ -void dbfs_free (struct dbfs *ctx); - -#endif /* DBFS_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/attr.c --- a/src/dbfs/attr.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ - -#include "dbfs.h" -#include "../lib/log.h" -#include "../lib/misc.h" - -// max. size for a setattr UPDATE query -#define DBFS_SETATTR_SQL_MAX 512 - -// for building the setattr UPDATE -#define FIELD(to_set, flag, field, value) ((to_set) & (flag)) ? (field " = " value ", ") : "" - -void _dbfs_attr_res (const struct evsql_result_info *res, void *arg) { - struct fuse_req *req = arg; - struct stat st; ZINIT(st); - int err = 0; - - uint32_t ino; - - // check the results - if ((err = _dbfs_check_res(res, 1, 5))) - SERROR(err = (err == 1 ? ENOENT : EIO)); - - // get our data - if (0 - || evsql_result_uint32(res, 0, 0, &ino, 0 ) // inodes.ino - ) - EERROR(err = EIO, "invalid db data"); - - - INFO("\t[dbfs.getattr %p] -> ino=%lu, stat follows", req, (unsigned long int) ino); - - // inode - st.st_ino = ino; - - // stat attrs - if ((err = _dbfs_stat_info(&st, res, 0, 1))) - goto error; - - // reply - if ((err = fuse_reply_attr(req, &st, st.st_nlink ? CACHE_TIMEOUT : 0))) - EERROR(err, "fuse_reply_entry"); - -error: - if (err && (err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - - // free - evsql_result_free(res); -} - -void dbfs_getattr (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct dbfs *ctx = fuse_req_userdata(req); - struct evsql_query *query; - int err; - - (void) fi; - - INFO("[dbfs.getattr %p] ino=%lu", req, ino); - - const char *sql = - "SELECT" - " inodes.ino, " DBFS_STAT_COLS - " FROM inodes" - " WHERE inodes.ino = $1::int4"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, ino) - ) - SERROR(err = EIO); - - // query - if ((query = evsql_query_params(ctx->db, NULL, sql, ¶ms, _dbfs_attr_res, req)) == NULL) - SERROR(err = EIO); - - // handle interrupts - fuse_req_interrupt_func(req, dbfs_interrupt_query, query); - - // wait - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} - - -void dbfs_setattr (struct fuse_req *req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi) { - struct dbfs *ctx = fuse_req_userdata(req); - struct evsql_query *query; - int err; - int ret; - - char sql_buf[DBFS_SETATTR_SQL_MAX]; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT16 ), // inodes.mode - EVSQL_PARAM ( UINT32 ), // inodes.uid - EVSQL_PARAM ( UINT32 ), // inodes.gid - EVSQL_PARAM ( UINT32 ), // data size - EVSQL_PARAM ( UINT32 ), // ino - - EVSQL_PARAMS_END - }; - - // log - INFO("[dbfs.setattr %p] ino=%lu, fileop=%p: ", req, ino, fi && fi->fh ? (void*) fi->fh : NULL); - - if (to_set & FUSE_SET_ATTR_MODE) { - // ignore the S_IFMT - attr->st_mode &= 07777; - - INFO("\tmode = %08o", attr->st_mode); - } - - if (to_set & FUSE_SET_ATTR_UID) - INFO("\tuid = %u", attr->st_uid); - - if (to_set & FUSE_SET_ATTR_GID) - INFO("\tgid = %u", attr->st_gid); - - if (to_set & FUSE_SET_ATTR_SIZE) - INFO("\tsize = %lu", attr->st_size); - - if (to_set & FUSE_SET_ATTR_ATIME) - INFO("\tatime = %lu", attr->st_atime); - - if (to_set & FUSE_SET_ATTR_MTIME) - INFO("\tmtime = %lu", attr->st_mtime); - - // the SQL - if ((ret = snprintf(sql_buf, DBFS_SETATTR_SQL_MAX, - "UPDATE inodes SET" - " %s%s%s%s ino = ino" - " WHERE inodes.ino = $5::int4" - " RETURNING inodes.ino, " DBFS_STAT_COLS, - - FIELD(to_set, FUSE_SET_ATTR_MODE, "mode", "$1::int2"), - FIELD(to_set, FUSE_SET_ATTR_UID, "uid", "$2::int4"), - FIELD(to_set, FUSE_SET_ATTR_GID, "gid", "$3::int4"), - FIELD(to_set, FUSE_SET_ATTR_SIZE, "data", "lo_otruncate(data, $4::int4)") - )) >= DBFS_SETATTR_SQL_MAX && (err = EIO)) - ERROR("sql_buf is too small: %i", ret); - - // the params... - if (0 - || ( evsql_params_clear(¶ms) ) - || ((to_set & FUSE_SET_ATTR_MODE ) && evsql_param_uint16(¶ms, 0, attr->st_mode) ) - || ((to_set & FUSE_SET_ATTR_UID ) && evsql_param_uint32(¶ms, 1, attr->st_uid) ) - || ((to_set & FUSE_SET_ATTR_GID ) && evsql_param_uint32(¶ms, 2, attr->st_gid) ) - || ((to_set & FUSE_SET_ATTR_SIZE ) && evsql_param_uint32(¶ms, 3, attr->st_size) ) - || ( evsql_param_uint32(¶ms, 4, ino) ) - ) - SERROR(err = EIO); - - // trace the query - evsql_query_debug(sql_buf, ¶ms); - - // query... we can pretend it's a getattr :) - if ((query = evsql_query_params(ctx->db, NULL, sql_buf, ¶ms, _dbfs_attr_res, req)) == NULL) - SERROR(err = EIO); - - // handle interrupts - fuse_req_interrupt_func(req, dbfs_interrupt_query, query); - - // wait - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/common.c --- a/src/dbfs/common.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ - -#include - -#include "dbfs.h" -#include "../lib/log.h" - -mode_t _dbfs_mode (const char *type) { - if (!strcmp(type, "DIR")) - return S_IFDIR; - - if (!strcmp(type, "REG")) - return S_IFREG; - - if (!strcmp(type, "LNK")) - return S_IFLNK; - - else { - WARNING("[dbfs] weird mode-type: %s", type); - return 0; - } -} - -int _dbfs_check_res (const struct evsql_result_info *res, size_t rows, size_t cols) { - int err = 0; - - // check if it failed - if (res->error) - NERROR(evsql_result_error(res)); - - // not found? - if (evsql_result_rows(res) == 0 && evsql_result_affected(res) == 0) - SERROR(err = 1); - - // duplicate rows? - if (rows && evsql_result_rows(res) != rows) - ERROR("wrong number of rows returned"); - - // correct number of columns - if (evsql_result_cols(res) != cols) - ERROR("wrong number of columns: %zu", evsql_result_cols(res)); - - // good - return 0; - -error: - if (!err) - err = -1; - - return err; -} - -err_t dbfs_check_result (const struct evsql_result_info *res, size_t rows, size_t cols) { - err_t err; - - // number of rows returned/affected - size_t nrows = evsql_result_rows(res) ? : evsql_result_affected(res); - - // did the query fail outright? - if (res->error) - // dump error message - NXERROR(err = EIO, evsql_result_error(res)); - - // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT - if (nrows == 0) - XERROR(err = ENOENT, "no rows returned/affected"); - - // duplicate rows where one expected? - if (rows && nrows != rows) - XERROR(err = EIO, "wrong number of rows: %zu -> %zu", rows, nrows); - - // correct number of columns - if (evsql_result_cols(res) != cols) - XERROR(err = EIO, "wrong number of columns: %zu -> %zu", cols, evsql_result_cols(res)); - - // good - return 0; - -error: - return err; -} - -int _dbfs_stat_info (struct stat *st, const struct evsql_result_info *res, size_t row, size_t col_offset) { - int err = 0; - - uint16_t mode; - uint32_t size = 0; // NULL for non-REG inodes - uint64_t nlink = 0; // will be NULL for non-GROUP BY queries - const char *type; - - // extract the data - if ((0 - || evsql_result_string(res, row, col_offset + 0, &type, 0 ) // inodes.type - || evsql_result_uint16(res, row, col_offset + 1, &mode, 0 ) // inodes.mode - || evsql_result_uint32(res, row, col_offset + 2, &size, 1 ) // size - || evsql_result_uint64(res, row, col_offset + 3, &nlink, 1 ) // count(*) - ) && (err = EIO)) - ERROR("invalid db data"); - - INFO("\tst_mode=S_IF%s | %ho, st_nlink=%llu, st_size=%llu", type, mode, (long long unsigned int) nlink, (long long unsigned int) size); - - // convert and store - st->st_mode = _dbfs_mode(type) | mode; - st->st_nlink = nlink; - st->st_size = size; - - // good - return 0; - -error: - return err; -} - - - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/dbfs.c --- a/src/dbfs/dbfs.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ - -#include - -#include "dbfs.h" -#include "../dbfs.h" -#include "../lib/log.h" - -static struct fuse_lowlevel_ops dbfs_llops = { - .init = dbfs_init, - .destroy = dbfs_destroy, - .lookup = dbfs_lookup, - // .forget // not needed - .getattr = dbfs_getattr, - .setattr = dbfs_setattr, - .readlink = dbfs_readlink, - .mknod = dbfs_mknod, - .mkdir = dbfs_mkdir, - .unlink = dbfs_unlink, - .rmdir = dbfs_unlink, // this behaves just the same - .symlink = dbfs_symlink, - .rename = dbfs_rename, - .link = dbfs_link, - .open = dbfs_open, - .read = dbfs_read, - .write = dbfs_write, - .flush = dbfs_flush, - .release = dbfs_release, - // .fsync // not needed - .opendir = dbfs_opendir, - .readdir = dbfs_readdir, - .releasedir = dbfs_releasedir, -}; - -void dbfs_init (void *userdata, struct fuse_conn_info *conn) { - INFO("[dbfs.init] userdata=%p, conn=%p", userdata, conn); - -} - -void dbfs_destroy (void *arg) { - struct dbfs *ctx = arg; - INFO("[dbfs.destroy %p]", ctx); - - // exit libevent - event_base_loopexit(ctx->ev_base, NULL); -} - - -void dbfs_sql_error (struct evsql *evsql, void *arg) { - struct dbfs *ctx = arg; - - // AAAAAAAAAA.... panic - WARNING("[dbfs] SQL error: BREAKING MAIN LOOP LIEK NAO"); - - event_base_loopbreak(ctx->ev_base); -} - -struct dbfs *dbfs_new (struct event_base *ev_base, struct fuse_args *args, const char *db_conninfo) { - struct dbfs *ctx = NULL; - - // alloc ctx - if ((ctx = calloc(1, sizeof(*ctx))) == NULL) - ERROR("calloc"); - - ctx->ev_base = ev_base; - ctx->db_conninfo = db_conninfo; - - // open sql - if ((ctx->db = evsql_new_pq(ctx->ev_base, ctx->db_conninfo, dbfs_sql_error, ctx)) == NULL) - ERROR("evsql_new_pq"); - - // open fuse - if ((ctx->ev_fuse = evfuse_new(ctx->ev_base, args, &dbfs_llops, ctx)) == NULL) - ERROR("evfuse_new"); - - // success - return ctx; - -error: - if (ctx) - dbfs_free(ctx); - - return NULL; -} - -void dbfs_free (struct dbfs *ctx) { - // cleanup - if (ctx->ev_fuse) { - evfuse_free(ctx->ev_fuse); - - ctx->ev_fuse = NULL; - } - - if (ctx->db) { - // XXX: not yet implemented - // evsql_close(ctx->db); - // ctx->db = NULL; - } - - free(ctx); -} diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/dbfs.h --- a/src/dbfs/dbfs.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -#ifndef DBFS_DBFS_H -#define DBFS_DBFS_H - -#include -#include - -#include - -#include "ops.h" -#include "../evfuse.h" -#include "../evsql.h" -#include "../lib/error.h" - -/* - * Structs and functions shared between all dbfs components - */ - -struct dbfs { - struct event_base *ev_base; - - const char *db_conninfo; - struct evsql *db; - - struct evfuse *ev_fuse; -}; - -// XXX: not sure how this should work -#define CACHE_TIMEOUT 1.0 - -// columns used for stat_info -#define DBFS_STAT_COLS " inodes.type, inodes.mode, dbfs_size(inodes.type, inodes.data, inodes.link_path), (SELECT COUNT(*) FROM inodes i LEFT JOIN file_tree ft ON (i.ino = ft.ino) WHERE i.ino = inodes.ino) AS nlink" - -/* - * Convert the CHAR(4) inodes.type from SQL into a mode_t. - * - * Returns zero for unknown types. - */ -mode_t _dbfs_mode (const char *type); - -/* - * Check that the number of rows and columns in the result set matches what we expect. - * - * If rows is nonzero, there must be exactly that many rows (mostly useful for rows=1). - * The number of columns must always be given, and match. - * - * Returns; - * -1 if the query failed, the columns/rows do not match - * 0 the results match - * 1 there were no results (zero rows) - */ -int _dbfs_check_res (const struct evsql_result_info *res, size_t rows, size_t cols); - -/* - * Same as _dbfs_check_res, but returns ENOENT/EIO directly - */ -err_t dbfs_check_result (const struct evsql_result_info *res, size_t rows, size_t cols); - -/* - * Fill a `struct state` with info retrieved from a SQL query. - * - * The result must contain four columns, starting at the given offset: - * inodes.type, inodes.mode, inodes.size, count(*) AS nlink - * - * Note that this does not fill the st_ino field - */ -int _dbfs_stat_info (struct stat *st, const struct evsql_result_info *res, size_t row, size_t col_offset); - -/** interrupt.c - * - * Fuse interrupts are handled using fuse_req_interrupt_func. Calling this registers a callback function with the req, - * which may or may not be called either by fuse_req_interrupt_func, or later on via evfuse's event handler. It is - * assumed that this will never be called after a call to fuse_reply_*. - * - * Hence, to handle an interrupt, we must first ensure that fuse_reply_* will not be called afterwards (it'll return - * an error), and then we must call fuse_reply_err(req, EINTR). - * - * In the simplest case, we can simply submit a query, and then abort it once the req is interrupted (now or later). - * In the more complicated case, we can check if the request was interrupted, if not, do the query and handle - * interrupts. - */ - -/* - * Useable as a callback to fuse_req_interrupt_func, will abort the given query and err the req. - * - * Due to a locking bug in libfuse 2.7.4, this will actually delay the fuse_req_err until the next event-loop iteration. - */ -void dbfs_interrupt_query (struct fuse_req *req, void *query_ptr); - -/* - * XXX: More complicated state, is this actually needed? - */ -struct dbfs_interrupt_ctx { - struct fuse_req *req; - struct evsql_query *query; - - int interrupted : 1; -}; - -/* - * Register as a fuse interrupt function for simple requests that only run one query without allocating any resources. - * - * This will abort the query if the interrupt is run, causing it's callback to not be called. - * - * Returns nonzero if the request was already interrupted, zero otherwise. Be careful that the interrupt does not get - * fired between you checking for it and setting query. - */ -int dbfs_interrupt_register (struct fuse_req *req, struct dbfs_interrupt_ctx *ctx); - -#endif /* DBFS_DBFS_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/dirop.c --- a/src/dbfs/dirop.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,303 +0,0 @@ - -#include -#include - -#include "dbfs.h" -#include "op_base.h" -#include "../dirbuf.h" -#include "../lib/log.h" - -/* - * Directory related functionality like opendir, readdir, releasedir - */ -struct dbfs_dirop { - struct dbfs_op base; - - // parent dir inodes - uint32_t parent; - - // for readdir - struct dirbuf dirbuf; -}; - -/* - * Release the dirbuf. - */ -static void _dbfs_dirop_free (struct dbfs_op *op_base) { - struct dbfs_dirop *dirop = (struct dbfs_dirop *) op_base; - - // just release the dirbuf - dirbuf_release(&dirop->dirbuf); -} - -/* - * Handle the results for the initial attribute lookup for the dir itself during opendir ops. - */ -static void dbfs_opendir_res (const struct evsql_result_info *res, void *arg) { - struct dbfs_dirop *dirop = arg; - int err; - - assert(dirop->base.req); - assert(dirop->base.trans); // query callbacks don't get called if the trans fails - assert(!dirop->base.open); - - // 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("\t[dbfs.opendir %p:%p] -> ino=%lu, parent=%lu, type=%s", dirop, dirop->base.req, (unsigned long int) dirop->base.ino, (unsigned long int) dirop->parent, type); - - // open_fn done, do the open_reply - if ((err = dbfs_op_open_reply(&dirop->base))) - goto error; - - // success, fallthrough for evsql_result_free - err = 0; - -error: - if (err) - // fail it - dbfs_op_fail(&dirop->base, err); - - // free - evsql_result_free(res); -} - -/* - * The opendir transaction is ready for use. Query for the given dir's info - */ -static void dbfs_dirop_open (struct dbfs_op *op_base) { - struct dbfs_dirop *dirop = (struct dbfs_dirop *) op_base; - struct dbfs *ctx = fuse_req_userdata(dirop->base.req); - int err; - - assert(dirop->base.trans); - assert(dirop->base.req); - assert(!dirop->base.open); - - INFO("\t[dbfs.opendir %p:%p] -> trans=%p", dirop, dirop->base.req, dirop->base.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.ino = inodes.ino)" - " WHERE file_tree.ino = $1::int4"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, dirop->base.ino) - ) - SERROR(err = EIO); - - // query - if (evsql_query_params(ctx->db, dirop->base.trans, sql, ¶ms, dbfs_opendir_res, dirop) == NULL) - SERROR(err = EIO); - - // ok, wait for the info results - return; - -error: - // fail it - dbfs_op_fail(&dirop->base, err); -} - -/* - * Handle opendir(), this means starting a new op. - */ -void dbfs_opendir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct dbfs *ctx = fuse_req_userdata(req); - struct dbfs_dirop *dirop = NULL; - int err; - - // allocate it - if ((dirop = calloc(1, sizeof(*dirop))) == NULL && (err = EIO)) - ERROR("calloc"); - - // do the op_open - if ((err = dbfs_op_open(ctx, &dirop->base, req, ino, fi, _dbfs_dirop_free, dbfs_dirop_open))) - ERROR("dbfs_op_open"); - - INFO("[dbfs.opendir %p:%p] ino=%lu, fi=%p", dirop, req, ino, fi); - - // wait - return; - -error: - if (dirop) { - // we can fail normally - dbfs_op_fail(&dirop->base, err); - - } else { - // must error out manually as we couldn't alloc the context - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - } -} - -/* - * Got the list of files for our readdir() request. - * - * Fill up the dirbuf, and then send the reply. - * - */ -static void dbfs_readdir_res (const struct evsql_result_info *res, void *arg) { - struct dbfs_dirop *dirop = arg; - int err; - size_t row; - - assert(dirop->base.req); - assert(dirop->base.trans); // query callbacks don't get called if the trans fails - assert(dirop->base.open); - - // check the results - if ((err = _dbfs_check_res(res, 0, 4)) < 0) - SERROR(err = EIO); - - INFO("\t[dbfs.readdir %p:%p] -> files: res_rows=%zu", dirop, dirop->base.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(dirop->base.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(dirop->base.req, &dirop->dirbuf))) - EERROR(err, "failed to send buf"); - - // handled the req - if ((err = dbfs_op_req_done(&dirop->base))) - goto error; - - // good, fallthrough - err = 0; - -error: - if (err) - dbfs_op_fail(&dirop->base, err); - - // free - evsql_result_free(res); -} - -/* - * Handle a readdir request. This will execute a SQL query inside the transaction to get the files at the given offset, - * and dbfs_readdir_res will handle the results. - * - * If trans failed earlier, detect that and return an error. - */ -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; - int err; - - // get the op - if ((dirop = (struct dbfs_dirop *) dbfs_op_req(req, ino, fi)) == NULL) - return; - - INFO("[dbfs.readdir %p:%p] ino=%lu, size=%zu, off=%zu, fi=%p : trans=%p", dirop, req, ino, size, off, fi, dirop->base.trans); - - // 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 - // assume that these two should *always* fit - if ((err = (0 - || dirbuf_add(req, &dirop->dirbuf, 0, 1, ".", dirop->base.ino, S_IFDIR ) - || dirbuf_add(req, &dirop->dirbuf, 1, 2, "..", - dirop->parent ? dirop->parent : dirop->base.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" - " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.ino = inodes.ino)" - " WHERE file_tree.parent = $1::int4 AND file_tree.\"offset\" >= $2::int4" - " ORDER BY file_tree.\"offset\"" - " LIMIT $3::int4"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( UINT32 ), - - EVSQL_PARAMS_END - }; - - // adjust offset to take . and .. into account - if (off > 2) - off -= 2; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, 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->base.trans, sql, ¶ms, dbfs_readdir_res, dirop) == NULL) - SERROR(err = EIO); - - // good, wait - return; - -error: - dbfs_op_fail(&dirop->base, err); -} - -/* - * "For every [succesfull] opendir call there will be exactly one releasedir call." - * - * The dirop may be in a failed state. - */ -void dbfs_releasedir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { - // just passthrough to dbfs_op - dbfs_op_release(req, ino, fi); -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/fileop.c --- a/src/dbfs/fileop.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,310 +0,0 @@ -#include -#include - -#include - -#include "dbfs.h" -#include "op_base.h" -#include "../lib/log.h" - -/* - * A file-operation, i.e. a sequence consisting of an OPEN, a multitude of READ/WRITE, followed by zero or more FLUSHes, and finally a single RELEASE. - * - * For historical reasons this opens a transaction and keeps it between open/release, but reads/writes now use the oid directly and are transactionless. - */ - -struct dbfs_fileop { - struct dbfs_op base; - - uint32_t oid; -// uint32_t lo_fd; -}; - -static void _dbfs_fileop_free (struct dbfs_op *op_base) { - struct dbfs_fileop *fop = (struct dbfs_fileop *) op_base; - - /* no-op */ - (void) fop; -} - -static void dbfs_open_res (const struct evsql_result_info *res, void *arg) { - struct dbfs_fileop *fop = arg; - int err; - - // 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_string(res, 0, 0, &type, 0 ) // inodes.type - || evsql_result_uint32(res, 0, 1, &fop->oid, 0 ) // inodes.data - ) - SERROR(err = EIO); - - // is it a dir? - if (_dbfs_mode(type) != S_IFREG) - EERROR(err = EINVAL, "wrong type: %s", type); - - INFO("\t[dbfs.open %p:%p] -> ino=%lu, type=%s", fop, fop->base.req, (unsigned long int) fop->base.ino, type); - - // open_fn done, do the open_reply - if ((err = dbfs_op_open_reply(&fop->base))) - goto error; - - // success, fallthrough for evsql_result_free - err = 0; - -error: - if (err) - // fail it - dbfs_op_fail(&fop->base, err); - - // free - evsql_result_free(res); -} - -static void dbfs_fileop_open (struct dbfs_op *op_base) { - struct dbfs_fileop *fop = (struct dbfs_fileop *) op_base; - struct dbfs *ctx = fuse_req_userdata(fop->base.req); - int err; - - // make sure the file actually exists - const char *sql = - "SELECT" - " inodes.type, inodes.data" - " FROM inodes" - " WHERE inodes.ino = $1::int4"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, fop->base.ino) - ) - SERROR(err = EIO); - - // query - if (evsql_query_params(ctx->db, fop->base.trans, sql, ¶ms, dbfs_open_res, fop) == NULL) - SERROR(err = EIO); - - // ok, wait for the info results - return; - -error: - // fail it - dbfs_op_fail(&fop->base, err); -} - -void dbfs_open (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct dbfs *ctx = fuse_req_userdata(req); - struct dbfs_fileop *fop = NULL; - int err; - - // allocate it - if ((fop = calloc(1, sizeof(*fop))) == NULL && (err = EIO)) - ERROR("calloc"); - - // do the op_open - if ((err = dbfs_op_open(ctx, &fop->base, req, ino, fi, _dbfs_fileop_free, dbfs_fileop_open))) - ERROR("dbfs_op_open"); - - // log - INFO("[dbfs.open %p:%p] ino=%lu, fi->flags=%04X", fop, req, ino, fi->flags); - - // wait - return; - -error: - if (fop) { - // we can fail normally - dbfs_op_fail(&fop->base, err); - - } else { - // must error out manually as we couldn't alloc the context - if ((err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - } -} - -void dbfs_read_res (const struct evsql_result_info *res, void *arg) { - struct fuse_req *req = arg; - int err; - const char *buf; - size_t size; - - // check the results - if ((err = _dbfs_check_res(res, 1, 1)) < 0) - SERROR(err = EIO); - - // get the data - if (evsql_result_binary(res, 0, 0, &buf, &size, 0)) - SERROR(err = EIO); - - INFO("\t[dbfs.read %p] -> size=%zu", req, size); - - // send it - if ((err = -fuse_reply_buf(req, buf, size))) - EERROR(err, "fuse_reply_buf"); - - // good, fallthrough - err = 0; - -error: - if (err) - fuse_reply_err(req, err); - - - // free - evsql_result_free(res); -} - -void dbfs_read (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); - int err; - - // log - INFO("[dbfs.read %p] ino=%lu, size=%zu, off=%lu, fi->flags=%04X", req, ino, size, off, fi->flags); - - // query - const char *sql = - "SELECT" - " lo_pread_oid(data, $1::int4, $2::int4)" - " FROM inodes" - " WHERE ino = $3::int4"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), // len - EVSQL_PARAM ( UINT32 ), // off - EVSQL_PARAM ( UINT32 ), // ino - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, size) - || evsql_param_uint32(¶ms, 1, off) - || evsql_param_uint32(¶ms, 2, ino) - ) - SERROR(err = EIO); - - // query, transactionless - if (evsql_query_params(ctx->db, NULL, sql, ¶ms, dbfs_read_res, req) == NULL) - SERROR(err = EIO); - - // ok, wait for the info results - return; - -error: - fuse_reply_err(req, err); -} - -void dbfs_write_res (const struct evsql_result_info *res, void *arg) { - struct fuse_req *req = arg; - int err; - uint32_t size; - - // check the results - if ((err = _dbfs_check_res(res, 1, 1)) < 0) - SERROR(err = EIO); - - // get the size - if (evsql_result_uint32(res, 0, 0, &size, 0)) - SERROR(err = EIO); - - INFO("\t[dbfs.write %p] -> size=%lu", req, (long unsigned int) size); - - // send it - if ((err = -fuse_reply_write(req, size))) - EERROR(err, "fuse_reply_write"); - - // good, fallthrough - err = 0; - -error: - if (err) - fuse_reply_err(req, err); - - // free - evsql_result_free(res); -} - -void dbfs_write (struct fuse_req *req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) { - struct dbfs *ctx = fuse_req_userdata(req); - int err; - - // log - INFO("[dbfs.write %p] ino=%lu, size=%zu, off=%lu, fi->flags=%04X", req, ino, size, off, fi->flags); - - // query - const char *sql = - "SELECT" - " lo_pwrite_oid(data, $1::bytea, $2::int4)" - " FROM inodes" - " WHERE ino = $3::int4"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( BINARY ), // buf - EVSQL_PARAM ( UINT32 ), // off - EVSQL_PARAM ( UINT32 ), // oid - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_binary(¶ms, 0, buf, size) - || evsql_param_uint32(¶ms, 1, off) - || evsql_param_uint32(¶ms, 2, ino) - ) - SERROR(err = EIO); - - // query - if (evsql_query_params(ctx->db, NULL, sql, ¶ms, dbfs_write_res, req) == NULL) - SERROR(err = EIO); - - // ok, wait for the info results - return; - -error: - fuse_reply_err(req, err); -} - -void dbfs_flush (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct dbfs_fileop *fop; - int err; - - // get the fop - if ((fop = (struct dbfs_fileop *) dbfs_op_req(req, ino, fi)) == NULL) - return; - - // log - INFO("[dbfs.flush %p:%p] ino=%lu", fop, req, ino); - - // and reply... - if ((err = -fuse_reply_err(req, 0))) - EWARNING(err, "fuse_reply_err"); - - // done - if ((err = dbfs_op_req_done(&fop->base))) - goto error; - - // good - return; - -error: - dbfs_op_fail(&fop->base, err); -} - -void dbfs_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { - // just passthrough to dbfs_op - // the lo_fd will be closed automatically - dbfs_op_release(req, ino, fi); -} diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/interrupt.c --- a/src/dbfs/interrupt.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ - -#include "dbfs.h" - -void _dbfs_interrupt_reply (evutil_socket_t _unused1, short _unused2, void *req_ptr) { - struct fuse_req *req = req_ptr; - err_t err; - - // error the req - if ((err = -fuse_reply_err(req, EINTR))) - EWARNING(err, "fuse_reply_err"); -} - -void dbfs_interrupt_query (struct fuse_req *req, void *query_ptr) { - struct dbfs *ctx = fuse_req_userdata(req); - struct evsql_query *query = query_ptr; - struct timeval tv; - err_t err; - - // abort query - evsql_query_abort(NULL, query); - - /* - * Due to a locking bug in libfuse (at least 2.7.4), we can't call fuse_reply_err from the interrupt function, so we must - * schedule after this function returns. - */ - tv.tv_sec = 0; - tv.tv_usec = 0; - if (event_base_once(ctx->ev_base, -1, EV_TIMEOUT, _dbfs_interrupt_reply, req, &tv)) - PWARNING("event_base_once failed, dropping req reply: %p", req); -} - -void _dbfs_interrupt_ctx (struct fuse_req *req, void *ctx_ptr) { - // dereference ctx - struct dbfs_interrupt_ctx *ctx = ctx_ptr; - - // just cancel query if pending - if (ctx->query) { - evsql_query_abort(NULL, ctx->query); - ctx->query = NULL; - } - - // mark as interrupted - ctx->interrupted = 1; -} - -int dbfs_interrupt_register (struct fuse_req *req, struct dbfs_interrupt_ctx *ctx) { - // initialize - ctx->query = NULL; - ctx->interrupted = 0; - - // just pass over to fuse_req_interrupt_func - fuse_req_interrupt_func(req, _dbfs_interrupt_ctx, ctx); -} diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/link.c --- a/src/dbfs/link.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,279 +0,0 @@ -#include "dbfs.h" - -/* - * Handling simple ino-related ops, like lookup, readlink, unlink and link - */ - -#include "../lib/log.h" -#include "../lib/misc.h" - -/* - * Used for lookup and link - */ -void dbfs_entry_res (const struct evsql_result_info *res, void *arg) { - struct fuse_req *req = arg; - struct fuse_entry_param e; ZINIT(e); - int err = 0; - - uint32_t ino; - - // check the results - if ((err = _dbfs_check_res(res, 1, 5))) - SERROR(err = (err == 1 ? ENOENT : EIO)); - - // get the data - if (0 - || evsql_result_uint32(res, 0, 0, &ino, 0 ) // inodes.ino - ) - EERROR(err = EIO, "invalid db data"); - - INFO("\t[dbfs.lookup] -> ino=%u", ino); - - // stat attrs - if ((err = _dbfs_stat_info(&e.attr, res, 0, 1))) - goto error; - - // other attrs - e.ino = e.attr.st_ino = ino; - e.attr_timeout = CACHE_TIMEOUT; - e.entry_timeout = CACHE_TIMEOUT; - - // reply - if ((err = -fuse_reply_entry(req, &e))) - EERROR(err, "fuse_reply_entry"); - -error: - if (err && (err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - - // free - evsql_result_free(res); -} - -void dbfs_lookup (struct fuse_req *req, fuse_ino_t parent, const char *name) { - struct dbfs *ctx = fuse_req_userdata(req); - struct evsql_query *query; - int err; - - INFO("[dbfs.lookup] parent=%lu name=%s", parent, name); - - // query and params - const char *sql = - "SELECT" - " inodes.ino, " DBFS_STAT_COLS - " FROM file_tree INNER JOIN inodes ON (file_tree.ino = inodes.ino)" - " WHERE file_tree.parent = $1::int4 AND file_tree.name = $2::varchar"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( STRING ), - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, parent) - || evsql_param_string(¶ms, 1, name) - ) - EERROR(err = EIO, "evsql_param_*"); - - // query - if ((query = evsql_query_params(ctx->db, NULL, sql, ¶ms, dbfs_entry_res, req)) == NULL) - EERROR(err = EIO, "evsql_query_params"); - - // handle interrupts - fuse_req_interrupt_func(req, dbfs_interrupt_query, query); - - // wait - return; - -error: - if ((err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} - -void _dbfs_readlink_res (const struct evsql_result_info *res, void *arg) { - struct fuse_req *req = arg; - int err = 0; - - uint32_t ino; - const char *type, *link; - - // check the results - if ((err = _dbfs_check_res(res, 1, 3))) - SERROR(err = (err == 1 ? ENOENT : EIO)); - - // get our data - if (0 - || evsql_result_uint32(res, 0, 0, &ino, 0 ) // inodes.ino - || evsql_result_string(res, 0, 1, &type, 0 ) // inodes.type - || evsql_result_string(res, 0, 2, &link, 1 ) // inodes.link_path - ) - EERROR(err = EIO, "invalid db data"); - - // is it a symlink? - if (_dbfs_mode(type) != S_IFLNK) - EERROR(err = EINVAL, "wrong type: %s", type); - - INFO("\t[dbfs.readlink %p] -> ino=%lu, type=%s, link=%s", req, (unsigned long int) ino, type, link); - - // reply - if ((err = -fuse_reply_readlink(req, link))) - EERROR(err, "fuse_reply_readlink"); - -error: - if (err && (err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - - // free - evsql_result_free(res); -} - -void dbfs_readlink (struct fuse_req *req, fuse_ino_t ino) { - struct dbfs *ctx = fuse_req_userdata(req); - struct evsql_query *query; - int err; - - INFO("[dbfs.readlink %p] ino=%lu", req, ino); - - const char *sql = - "SELECT" - " inodes.ino, inodes.type, inodes.link_path" - " FROM inodes" - " WHERE inodes.ino = $1::int4"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, ino) - ) - SERROR(err = EIO); - - // query - if ((query = evsql_query_params(ctx->db, NULL, sql, ¶ms, _dbfs_readlink_res, req)) == NULL) - SERROR(err = EIO); - - // handle interrupts - fuse_req_interrupt_func(req, dbfs_interrupt_query, query); - - // wait - return; - -error: - if ((err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - -} - -#define SETERR(err_var, err_val, bool_val) ((err_var) = bool_val ? (err_val) : 0) - -void dbfs_unlink_res (const struct evsql_result_info *res, void *arg) { - struct fuse_req *req = arg; - int err = 0; - - // check the results - // XXX: reply with ENOTEMPTY if it fails due to this inode being a dir - if ((err = dbfs_check_result(res, 1, 0))) - goto error; - - INFO("\t[dbfs.unlink %p] -> OK", req); - - // reply - if ((err = -fuse_reply_err(req, 0))) - EERROR(err, "fuse_reply_err"); - -error: - if (err && (err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - - // free - evsql_result_free(res); -} - -void dbfs_unlink (struct fuse_req *req, fuse_ino_t parent, const char *name) { - struct dbfs *ctx = fuse_req_userdata(req); - struct evsql_query *query; - int err; - - INFO("[dbfs.unlink %p] parent=%lu, name=%s", req, parent, name); - - const char *sql = - "DELETE" - " FROM file_tree" - " WHERE parent = $1::int4 AND name = $2::varchar"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( STRING ), - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, parent) - || evsql_param_string(¶ms, 1, name) - ) - SERROR(err = EIO); - - // query - if ((query = evsql_query_params(ctx->db, NULL, sql, ¶ms, dbfs_unlink_res, req)) == NULL) - SERROR(err = EIO); - - // handle interrupts - fuse_req_interrupt_func(req, dbfs_interrupt_query, query); - - // wait - return; - -error: - if ((err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} - -void dbfs_link (struct fuse_req *req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname) { - struct dbfs *ctx = fuse_req_userdata(req); - struct evsql_query *query; - int err; - - INFO("[dbfs.link %p] ino=%lu, newparent=%lu, newname=%s", req, ino, newparent, newname); - - const char *sql = - "SELECT ino, type, mode, size, nlink FROM dbfs_link($1::int4, $2::int4, $3::varchar)"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( STRING ), - - EVSQL_PARAMS_END - }; - - // build params - if (0 - || evsql_param_uint32(¶ms, 0, ino) - || evsql_param_uint32(¶ms, 1, newparent) - || evsql_param_string(¶ms, 2, newname) - ) - SERROR(err = EIO); - - // query - if ((query = evsql_query_params(ctx->db, NULL, sql, ¶ms, dbfs_entry_res, req)) == NULL) - SERROR(err = EIO); - - // handle interrupts - fuse_req_interrupt_func(req, dbfs_interrupt_query, query); - - // wait - return; - -error: - if ((err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/mk.c --- a/src/dbfs/mk.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,243 +0,0 @@ -#include -#include -#include - -#include "trans.h" -#include "../lib/log.h" - -struct dbfs_mk_ctx { - struct dbfs_trans base; - - const char *type, *data_expr; - char *link, *name; - uint16_t mode; - uint32_t ino, parent; - - unsigned char is_dir : 1; -}; - -// default mode for symlinks -#define DBFS_SYMLINK_MODE 0777 - -// max. size for an dbfs_mk INSERT query -#define DBFS_MK_SQL_MAX 512 - -void dbfs_mk_free (struct dbfs_trans *ctx_base) { - struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base; - - free(ctx->link); - free(ctx->name); -} - -void dbfs_mk_commit (struct dbfs_trans *ctx_base) { - struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base; - struct fuse_entry_param e; - int err; - - // build entry - e.ino = e.attr.st_ino = ctx->ino; - e.attr.st_mode = _dbfs_mode(ctx->type) | ctx->mode; - e.attr.st_size = ctx->link ? strlen(ctx->link) : 0; - e.attr.st_nlink = 1; - e.attr_timeout = e.entry_timeout = CACHE_TIMEOUT; - - // reply - if ((err = fuse_reply_entry(ctx_base->req, &e))) - goto error; - - // req good - ctx_base->req = NULL; - - // free - dbfs_trans_free(ctx_base); - - // return - return; - -error: - dbfs_trans_fail(ctx_base, err); -} - -void dbfs_mk_filetree (const struct evsql_result_info *res, void *arg) { - struct dbfs_mk_ctx *ctx = arg; - int err = EIO; - - // check results - if (_dbfs_check_res(res, 0, 0) < 0) - goto error; - - // commit - dbfs_trans_commit(&ctx->base); - - // fallthrough for result_free - err = 0; - -error: - if (err) - dbfs_trans_fail(&ctx->base, err); - - evsql_result_free(res); -} - -void dbfs_mk_inode (const struct evsql_result_info *res, void *arg) { - struct dbfs_mk_ctx *ctx = arg; - struct dbfs *dbfs_ctx = fuse_req_userdata(ctx->base.req); - int err = EIO; - - // check result - if ((err = _dbfs_check_res(res, 1, 1))) - SERROR(err = err > 0 ? ENOENT : EIO); - - // get ino - if (evsql_result_uint32(res, 0, 0, &ctx->ino, 0)) - goto error; - - // insert file_tree entry - const char *sql = - "INSERT" - " INTO file_tree (name, parent, ino, ino_dir)" - " VALUES ($1::varchar, $2::int4, $3::int4, $4::int4)"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( STRING ), - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( UINT32 ), - - EVSQL_PARAMS_END - }; - - if (0 - || evsql_param_string(¶ms, 0, ctx->name) - || evsql_param_uint32(¶ms, 1, ctx->parent) - || evsql_param_uint32(¶ms, 2, ctx->ino) - || ctx->is_dir ? evsql_param_uint32(¶ms, 3, ctx->ino) : evsql_param_null(¶ms, 3) - ) - goto error; - - // query - if (evsql_query_params(dbfs_ctx->db, ctx->base.trans, sql, ¶ms, dbfs_mk_filetree, ctx)) - goto error; - - // fallthrough for result_free - err = 0; - -error: - if (err) - dbfs_trans_fail(&ctx->base, err); - - evsql_result_free(res); -} - -void dbfs_mk_begin (struct dbfs_trans *ctx_base) { - struct dbfs_mk_ctx *ctx = (struct dbfs_mk_ctx *) ctx_base; - struct dbfs *dbfs_ctx = fuse_req_userdata(ctx_base->req); - int ret; - - // insert inode - char sql_buf[DBFS_MK_SQL_MAX]; - - if ((ret = snprintf(sql_buf, DBFS_MK_SQL_MAX, - "INSERT" - " INTO inodes (type, mode, data, link_path)" - " VALUES ($1::char(3), $2::int2, %s, $3::varchar)" - " RETURNING inodes.ino", ctx->data_expr ? ctx->data_expr : "NULL" - )) >= DBFS_MK_SQL_MAX) - ERROR("sql_buf is too small: %d", ret); - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( STRING ), - EVSQL_PARAM ( UINT16 ), - EVSQL_PARAM ( STRING ), - - EVSQL_PARAMS_END - }; - - if (0 - || evsql_param_string(¶ms, 0, ctx->type) - || evsql_param_uint16(¶ms, 1, ctx->mode) - || evsql_param_string(¶ms, 2, ctx->link) - ) - goto error; - - if (evsql_query_params(dbfs_ctx->db, ctx_base->trans, sql_buf, ¶ms, dbfs_mk_inode, ctx) == NULL) - goto error; - - return; - -error: - dbfs_trans_fail(ctx_base, EIO); -} - -/* - * It is assumed that name and link_path must be copied, but type remains useable - */ -void dbfs_mk (struct fuse_req *req, fuse_ino_t parent, const char *name, const char *type, uint16_t mode, const char *data_expr, const char *link, unsigned char is_dir) { - struct dbfs_mk_ctx *ctx = NULL; - - // alloc - if ((ctx = calloc(1, sizeof(*ctx))) == NULL) - ERROR("calloc"); - - // start trans - if (dbfs_trans_init(&ctx->base, req)) - goto error; - - // callbacks - ctx->base.free_fn = dbfs_mk_free; - ctx->base.begin_fn = dbfs_mk_begin; - ctx->base.commit_fn = dbfs_mk_commit; - - // state - ctx->ino = 0; - ctx->parent = parent; - ctx->type = type; - ctx->data_expr = data_expr; - ctx->mode = mode; - ctx->is_dir = is_dir; - - // copy volatile strings - if ( - (link && (ctx->link = strdup(link)) == NULL) - || (name && (ctx->name = strdup(name)) == NULL) - ) - ERROR("strdup"); - - // log - INFO("[dbfs.mk %p:%p] parent=%lu, name=%s, type=%s, mode=%#04o data_expr=%s link=%s is_dir=%hhd", ctx, req, parent, name, type, mode, data_expr, link, is_dir); - - // wait - return; - -error: - if (ctx) - dbfs_trans_fail(&ctx->base, EIO); -} - -/* - * These are all just aliases to dbfs_mk - */ -void dbfs_mknod (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev) { - int err; - - if ((mode & S_IFMT) != S_IFREG) - EERROR(err = EINVAL, "mode is not REG: %#08o", mode); - - dbfs_mk(req, parent, name, "REG", mode & 07777, "lo_create(0)", NULL, 0); - - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_error"); -} - -void dbfs_mkdir (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode) { - dbfs_mk(req, parent, name, "DIR", mode, NULL, NULL, 1); -} - - -void dbfs_symlink (struct fuse_req *req, const char *link, fuse_ino_t parent, const char *name) { - dbfs_mk(req, parent, name, "LNK", DBFS_SYMLINK_MODE, NULL, link, 0); -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/op_base.c --- a/src/dbfs/op_base.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,299 +0,0 @@ -#include -#include - -#include "op_base.h" -#include "../lib/log.h" - -/* - * Free the op. - * - * The op must any oustanding request responded to first, must not be open, and must not have a transaction. - * - * The op will be free'd. - */ -static void dbfs_op_free (struct dbfs_op *op) { - assert(op); - assert(!op->open); - assert(!op->req); - assert(!op->trans); - - // free_fn - if (op->free_fn) - op->free_fn(op); - - // and then free the op - free(op); -} - -void dbfs_op_fail (struct dbfs_op *op, int err) { - assert(op->req); - - if (op->trans) { - // abort the trans - evsql_trans_abort(op->trans); - - op->trans = NULL; - } - - // send an error reply - if ((err = fuse_reply_err(op->req, err))) - // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops - EFATAL(err, "\tdbfs_op.fail %p:%p -> reply with fuse_reply_err", op, op->req); - - // drop the req - op->req = NULL; - - // is it open? - if (!op->open) { - // no, we can free it now and then forget about the whole thing - dbfs_op_free(op); - - } else { - // we need to wait for release - - } -} - -/* - * The op_open transaction is ready for use. - */ -static void dbfs_op_ready (struct evsql_trans *trans, void *arg) { - struct dbfs_op *op = arg; - - assert(trans == op->trans); - assert(op->req); - assert(!op->open); - - INFO("\tdbfs_op.ready %p:%p -> trans=%p", op, op->req, trans); - - // remember the transaction - op->trans = trans; - - // ready - op->open_fn(op); - - // good - return; -} - -/* - * The op trans was committed, i.e. release has completed - */ -static void dbfs_op_done (struct evsql_trans *trans, void *arg) { - struct dbfs_op *op = arg; - int err; - - assert(trans == op->trans); - assert(op->req); - assert(!op->open); // should not be considered as open anymore at this point, as errors should release - - INFO("\tdbfs_op.done %p:%p -> OK", op, op->req); - - // forget trans - op->trans = NULL; - - // just reply - if ((err = fuse_reply_err(op->req, 0))) - // XXX: handle these failures /somehow/, or requests will hang and interrupts might handle invalid ops - EFATAL(err, "dbfs_op.done %p:%p -> reply with fuse_reply_err", op, op->req); - - // req is done - op->req = NULL; - - // then we can just free op - dbfs_op_free(op); -} - -/* - * The op trans has failed, somehow, at some point, some where. - * - * This might happend during the open evsql_trans, during a read evsql_query, during the release - * evsql_trans_commit, or at any point in between. - * - * 1) loose the transaction - * 2) if op has a req, we handle failing it - */ -static void dbfs_op_error (struct evsql_trans *trans, void *arg) { - struct dbfs_op *op = arg; - - // unless we fail - assert(trans == op->trans); - - INFO("\tdbfs_op.error %p:%p -> evsql transaction error: %s", op, op->req, evsql_trans_error(trans)); - - // deassociate the trans - op->trans = NULL; - - // if we were answering a req, error it out, and if the op isn't open, free - // if we didn't have a req outstanding, the op must be open, so we wouldn't free it in any case, and must wait - // for the next read/release to detect this and return an error reply - if (op->req) - dbfs_op_fail(op, EIO); - else - assert(op->open); -} - -int dbfs_op_open (struct dbfs *ctx, struct dbfs_op *op, struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi, dbfs_op_free_cb free_fn, dbfs_op_open_cb open_fn) { - int err; - - assert(op && req && ino && fi); - assert(!(op->req || op->ino)); - - // initialize the op - op->req = req; - op->ino = ino; - // copy *fi since it's on the stack - op->fi = *fi; - op->fi.fh = (uint64_t) op; - op->free_fn = free_fn; - op->open_fn = open_fn; - - // start a new transaction - if ((op->trans = evsql_trans(ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_op_error, dbfs_op_ready, dbfs_op_done, op)) == NULL) - SERROR(err = EIO); - - // XXX: handle interrupts - - // good - return 0; - -error: - // nothing of ours to cleanup - return err; -} - -int dbfs_op_open_reply (struct dbfs_op *op) { - int err; - - // detect earlier failures - if (!op->trans && (err = EIO)) - ERROR("op trans has failed"); - - // send the openddir reply - if ((err = fuse_reply_open(op->req, &op->fi))) - EERROR(err, "fuse_reply_open"); - - // req is done - op->req = NULL; - - // op is now open - op->open = 1; - - // good - return 0; - -error: - return err; -} - -struct dbfs_op *dbfs_op_req (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct dbfs_op *op = (struct dbfs_op *) fi->fh; - int err; - - // validate - assert(op); - assert(op->open); - assert(op->ino == ino); - - // detect concurrent requests - if (op->req) { - // must handle req ourself, shouldn't fail the other req - WARNING("op.%p: concurrent req: %p -> %p", op, op->req, req); - - // XXX: ignore error errors... - fuse_reply_err(req, EBUSY); - - return NULL; - - } else - // store the new req - op->req = req; - - // inodes change? - if (op->ino != ino) - XERROR(err = EBADF, "op.%p: wrong ino: %u -> %lu", op, op->ino, ino); - - // detect earlier failures - if (!op->trans && (err = EIO)) - ERROR("op trans has failed"); - - // good - return op; - -error: - dbfs_op_fail(op, err); - - return NULL; -} - -int dbfs_op_req_done (struct dbfs_op *op) { - // validate - assert(op); - assert(op->req); - assert(op->open); - - // unassign the req - op->req = NULL; - - // k - return 0; -} - -void dbfs_op_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct dbfs_op *op = (struct dbfs_op *) fi->fh; - int err; - - assert(op); - assert(!op->req); - assert(op->ino == ino); - - // update to this req - op->req = req; - - // fi is irrelevant, we don't touch the flags anyways - (void) fi; - - // handle failed trans - if (!op->trans && (err = EIO)) - ERROR("trans has failed"); - - // log - INFO("\tdbfs_op.release %p:%p : ino=%lu, fi=%p : trans=%p", op, req, ino, fi, op->trans); - - // we must commit the transaction. - // Note that this might cause dbfs_op_error to be called, we can tell if that happaned by looking at op->req - // or op->trans - this means that we need to keep the op open when calling trans_commit, so that op_error - // doesn't free it out from underneath us. - if (evsql_trans_commit(op->trans)) - SERROR(err = EIO); - - // fall-through to cleanup - err = 0; - -error: - // the op is not open anymore and can be free'd next, because we either: - // a) already caught an error - // b) we get+send an error later on - // c) we get+send the done/no-error later on - op->open = 0; - - // did the commit/pre-commit-checks fail? - if (err) { - // a) the trans failed earlier (read), so we have a req but no trans - // b) the trans commit failed, op_error got called -> no req and no trans - // c) the trans commit failed, op_error did not get called -> have req and trans - // we either have a req (may or may not have trans), or we don't have a trans either - // i.e. there is no situation where we don't have a req but do have a trans - - if (op->req) - dbfs_op_fail(op, err); - else - assert(!op->trans); - - } else { - // shouldn't slip by, op_done should not get called directly. Once it does, it will handle both. - assert(op->req); - assert(op->trans); - } -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/op_base.h --- a/src/dbfs/op_base.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -#ifndef DBFS_OP_BASE_H -#define DBFS_OP_BASE_H - -#include "dbfs.h" - -// forward-declaration for callbacks -struct dbfs_op; - -/* - * Called by dbfs_op_free to release any resources when the op is free'd (i.e. not open anymore). - */ -typedef void (*dbfs_op_free_cb) (struct dbfs_op *op_base); - -/* - * Called after the transaction has been opened, and before reply_open. - * - * You can do any at-open initialization here. - */ -typedef void (*dbfs_op_open_cb) (struct dbfs_op *op_base); - -// the base op state -struct dbfs_op { - struct fuse_file_info fi; - struct fuse_req *req; - - struct evsql_trans *trans; - - // op target inode - uint32_t ino; - - // open has returned and release hasn't been called yet - int open; - - // callbacks - dbfs_op_free_cb free_fn; - dbfs_op_open_cb open_fn; -}; - -/* - * This will handle failures during requests. - * - * 1) if we have a trans, abort it - * 2) fail the req (mandatory) with the given err - * - * If the op is open, then we don't release it, but if it's not open, then the op will be free'd completely. - * - */ -void dbfs_op_fail (struct dbfs_op *op, int err); - -/* - * Open the op, that is, store all the initial state, and open a new transaction. - * - * The op must be pre-allocated and zero-initialized. - * - * This will always set op->req, so op is safe for dbfs_op_fail after this. - * - * This does not fail the dirop, handle error replies yourself. - * - * Returns zero on success, err on failure. - */ -int dbfs_op_open (struct dbfs *ctx, struct dbfs_op *op, struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi, dbfs_op_free_cb free_fn, dbfs_op_open_cb ready_fn); - -/* - * Should be called from open_fn to send the fuse_reply_open with fi and mark the op as open. - * - * If the op has failed earlier or fuse_reply_open fails, this will return nonzero. Fail the op yourself. - */ -int dbfs_op_open_reply (struct dbfs_op *op); - -/* - * Start handling a normal op requests. - * - * Lookup the op for the given fi, validate params, and assign the new req. - * - * In case the op failed previously, this will error the req and return NULL, indicating that the req has been handled. - * - * Repeat, if this returns NULL, consider req invalid. - */ -struct dbfs_op *dbfs_op_req (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi); - -/* - * Done handling a request, adjust state accordingly. - * - * req *must* have been replied to. - */ -int dbfs_op_req_done (struct dbfs_op *op); - -/* - * Handle the op release. - * - * This will take care of committing the transaction, sending any reply/error, closing the op and freeing it. - */ -void dbfs_op_release (struct fuse_req *req, fuse_ino_t, struct fuse_file_info *fi); - - -#endif /* DBFS_OP_BASE_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/ops.h --- a/src/dbfs/ops.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -#ifndef DBFS_OPS_H -#define DBFS_OPS_H - -#include "../evfuse.h" - -/* dbfs.c */ -void dbfs_init (void *userdata, struct fuse_conn_info *conn); -void dbfs_destroy (void *arg); - -/* attr.c */ -void dbfs_getattr (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi); -void dbfs_setattr(struct fuse_req *req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi); - -/* link.c */ -void dbfs_lookup (struct fuse_req *req, fuse_ino_t parent, const char *name); -void dbfs_readlink (struct fuse_req *req, fuse_ino_t ino); -void dbfs_unlink (struct fuse_req *req, fuse_ino_t parent, const char *name); -void dbfs_link (struct fuse_req *req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname); - -/* dirop.c */ -void dbfs_opendir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi); -void dbfs_readdir (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); -void dbfs_releasedir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi); - -/* mk.c */ -void dbfs_mknod (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev); -void dbfs_mkdir (struct fuse_req *req, fuse_ino_t parent, const char *name, mode_t mode); -void dbfs_symlink (struct fuse_req *req, const char *link, fuse_ino_t parent, const char *name); - -/* tree.c */ -void dbfs_rename (struct fuse_req *req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname); - -/* fileop.c */ -void dbfs_open (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi); -void dbfs_read (struct fuse_req *req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); -void dbfs_write (struct fuse_req *req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi); -void dbfs_flush (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi); -void dbfs_release (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi); - -#endif /* DBFS_OPS_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/trans.c --- a/src/dbfs/trans.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ - -#include -#include - -#include "trans.h" -#include "../lib/log.h" - -void dbfs_trans_free (struct dbfs_trans *ctx) { - assert(!ctx->req); - assert(!ctx->trans); - - if (ctx->free_fn) - ctx->free_fn(ctx); - - free(ctx); -} - -void dbfs_trans_fail (struct dbfs_trans *ctx, int err) { - if (ctx->req) { - if ((err = fuse_reply_err(ctx->req, err))) - EWARNING(err, "fuse_reply_err: request hangs"); - - ctx->req = NULL; - } - - if (ctx->trans) { - evsql_trans_abort(ctx->trans); - - ctx->trans = NULL; - } - - dbfs_trans_free(ctx); -} - -static void dbfs_trans_error (struct evsql_trans *trans, void *arg) { - struct dbfs_trans *ctx = arg; - - // deassociate trans - ctx->trans = NULL; - - // log error - INFO("\t[dbfs_trans.err %p:%p] %s", ctx, ctx->req, evsql_trans_error(trans)); - - // mark - if (ctx->err_ptr) - *ctx->err_ptr = EIO; - - // fail - dbfs_trans_fail(ctx, EIO); -} - -static void dbfs_trans_ready (struct evsql_trans *trans, void *arg) { - struct dbfs_trans *ctx = arg; - - // associate trans - ctx->trans = trans; - - // log - INFO("\t[dbfs_trans.ready %p:%p] -> trans=%p", ctx, ctx->req, trans); - - // trigger the callback - ctx->begin_fn(ctx); -} - -static void dbfs_trans_done (struct evsql_trans *trans, void *arg) { - struct dbfs_trans *ctx = arg; - - // deassociate trans - ctx->trans = NULL; - - // log - INFO("\t[dbfs_trans.done %p:%p]", ctx, ctx->req); - - // trigger the callback - ctx->commit_fn(ctx); -} - -int dbfs_trans_init (struct dbfs_trans *ctx, struct fuse_req *req) { - struct dbfs *dbfs_ctx = fuse_req_userdata(req); - int err; - - // store - ctx->req = req; - - // trans - if ((ctx->trans = evsql_trans(dbfs_ctx->db, EVSQL_TRANS_SERIALIZABLE, dbfs_trans_error, dbfs_trans_ready, dbfs_trans_done, ctx)) == NULL) - EERROR(err = EIO, "evsql_trans"); - - // good - return 0; - -error: - return -1; -} - -struct dbfs_trans *dbfs_trans_new (struct fuse_req *req) { - struct dbfs_trans *ctx = NULL; - - // alloc - if ((ctx = calloc(1, sizeof(*ctx))) == NULL) - ERROR("calloc"); - - // init - if (dbfs_trans_init(ctx, req)) - goto error; - - // good - return ctx; - -error: - free(ctx); - - return NULL; -} - -void dbfs_trans_commit (struct dbfs_trans *ctx) { - int err, trans_err = 0; - - // detect errors - ctx->err_ptr = &trans_err; - - // attempt commit - if (evsql_trans_commit(ctx->trans)) - SERROR(err = EIO); - - // drop err_ptr - ctx->err_ptr = NULL; - - // ok, wait for done or error - return; - -error: - // fail if not already failed - if (!trans_err) - dbfs_trans_fail(ctx, err); -} - - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/trans.h --- a/src/dbfs/trans.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -#ifndef DBFS_TRANS_H -#define DBFS_TRANS_H - -/* - * Support for single-fuse_req transactions. - */ - -#include "dbfs.h" - -// forward-declaration -struct dbfs_trans; - -// generic callback -typedef void (*dbfs_trans_cb) (struct dbfs_trans *ctx); - -/* - * Request/transaction state. - */ -struct dbfs_trans { - struct fuse_req *req; - struct evsql_trans *trans; - - // called when the dbfs_trans is being free'd - dbfs_trans_cb free_fn; - - // called once the transaction is ready - dbfs_trans_cb begin_fn; - - // called once the transaction has been commited - dbfs_trans_cb commit_fn; - - // set by trans_error to EIO if !NULL - int *err_ptr; -}; - -/* - * Call from commit_fn once you've set req to NULL. Also called from trans_fail. - * - * Will call free_fn if present. - */ -void dbfs_trans_free (struct dbfs_trans *ctx); - -/* - * Fail the dbfs_trans, aborting any trans, erroring out any req and freeing the ctx. - */ -void dbfs_trans_fail (struct dbfs_trans *ctx, int err); - -/* - * Initialize the ctx with the given req (stays the same during the lifetime), and opens the transaction. - * - * You should set the callback functions after/before calling this. - * - * begin_fn will be called once the transaction is open, if that fails, the req will be errored for you. - * - * If opening the transaction fails, this will return nonzero and not touch req, otherwise zero. - */ -int dbfs_trans_init (struct dbfs_trans *ctx, struct fuse_req *req); - -/* - * Same as init, but allocates/frees-on-error the dbfs_trans for you. - */ -struct dbfs_trans *dbfs_trans_new (struct fuse_req *req); - -/* - * Commit the dbfs_trans. After calling this, the ctx may or may not be valid, and commit_fn may or may not be called. - */ -void dbfs_trans_commit (struct dbfs_trans *ctx); - -#endif /* DBFS_TRANS_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dbfs/tree.c --- a/src/dbfs/tree.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ - -#include "../lib/log.h" - -#include "dbfs.h" - -void dbfs_rename_res (const struct evsql_result_info *res, void *arg) { - struct fuse_req *req = arg; - int err; - - // check the results - if ((err = _dbfs_check_res(res, 0, 0))) - SERROR(err = (err == 1 ? ENOENT : EIO)); - - // just reply - if ((err = -fuse_reply_err(req, 0))) - EERROR(err, "fuse_reply_err"); - - // log - INFO("[dbfs.rename %p] -> OK", req); - - // fallthrough for result_free - err = 0; - -error: - if (err && (err = -fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - - evsql_result_free(res); -} - -void dbfs_rename (struct fuse_req *req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname) { - struct dbfs *dbfs_ctx = fuse_req_userdata(req); - int err; - - INFO("[dbfs.rename %p] parent=%lu, name=%s, newparent=%lu, newname=%s", req, parent, name, newparent, newname); - - // just one UPDATE - const char *sql = - "UPDATE" - " file_tree" - " SET parent = $1::int4, name = $2::varchar" - " WHERE parent = $3::int4 AND name = $4::varchar"; - - static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( STRING ), - EVSQL_PARAM ( UINT32 ), - EVSQL_PARAM ( STRING ), - - EVSQL_PARAMS_END - }; - - if (0 - || evsql_param_uint32(¶ms, 0, newparent) - || evsql_param_string(¶ms, 1, newname) - || evsql_param_uint32(¶ms, 2, parent) - || evsql_param_string(¶ms, 3, name) - ) - SERROR(err = EIO); - - // query - if (evsql_query_params(dbfs_ctx->db, NULL, sql, ¶ms, dbfs_rename_res, req) == NULL) - SERROR(err = EIO); - - // good, wait - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dirbuf.c --- a/src/dirbuf.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ - -#include - -#include "dirbuf.h" -#include "lib/log.h" -#include "lib/math.h" - -int dirbuf_init (struct dirbuf *buf, size_t req_size, off_t req_off) { - buf->buf = NULL; - buf->len = req_size; - buf->off = 0; - buf->req_off = req_off; - - DEBUG("\tdirbuf.init: req_size=%zu", req_size); - - // allocate the mem - if ((buf->buf = malloc(buf->len)) == NULL) - ERROR("malloc"); - - // ok - return 0; - -error: - return -1; -} - -size_t dirbuf_estimate (struct dirbuf *buf, size_t min_namelen) { - char namebuf[DIRBUF_NAME_MAX]; - int i; - - // build a dummy string of the right length - for (i = 0; i < min_namelen && i < DIRBUF_NAME_MAX - 1; i++) - namebuf[i] = 'x'; - - namebuf[i] = '\0'; - - return buf->len / (fuse_add_direntry(NULL, NULL, 0, namebuf, NULL, 0)); -} - -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; - - 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", - buf->req_off, buf->len, buf->off, ent_off, next_off, ent_name, ent_ino, ent_mode); - - // skip entries as needed - if (ent_off < buf->req_off) - return 0; - - // set ino - stbuf.st_ino = ent_ino; - stbuf.st_mode = ent_mode; - - // try and add the dirent, and see if it fits - if ((ent_size = fuse_add_direntry(req, buf->buf + buf->off, buf->len - buf->off, ent_name, &stbuf, next_off)) > (buf->len - buf->off)) { - // 'tis full - return 1; - - } else { - // it fit - buf->off += ent_size; - } - - // success - return 0; -} - -int dirbuf_done (fuse_req_t req, struct dirbuf *buf) { - int err; - - // send the reply, return the error later - err = -fuse_reply_buf(req, buf->buf, buf->off); - - DEBUG("\tdirbuf.done: size=%zu/%zu, err=%d", buf->off, buf->len, err); - - // free the dirbuf - dirbuf_release(buf); - - // return the error code - return err; -} - -void dirbuf_release (struct dirbuf *buf) { - free(buf->buf); buf->buf = NULL; -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/dirbuf.h --- a/src/dirbuf.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -#ifndef DIRBUF_H -#define DIRBUF_H - -/* - * Simple dirent building - */ - -#include "evfuse.h" - -/* - * Holds the dir entries - */ -struct dirbuf { - char *buf; - size_t len; - 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 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: - * ent_off - the offset of this dirent - * next_off - the offset of the next dirent - * - * Only the S_IFMT bits of ent_mode are relevant. - * - * 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, 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 40a3b13ffc9d -r 9dfc861273e5 src/evfuse.c --- a/src/evfuse.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,153 +0,0 @@ - -#include -#include -#include - -#include "evfuse.h" -#include "lib/log.h" - -struct evfuse { - // our mountpoint - char *mountpoint; - - // the /dev/fuse fd/channel that we get from fuse_mount - struct fuse_chan *chan; - - // the session that we use to process the fuse stuff - struct fuse_session *session; - - // the event that we use to receive requests - struct event *ev; - - // what our receive-message length is - size_t recv_size; - - // the buffer that we use to receive events - 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; - int res; - - // loop until we complete a recv - do { - // a new fuse_req is available - res = fuse_chan_recv(&ch, ctx->recv_buf, ctx->recv_size); - } while (res == -EINTR); - - if (res == 0) - ERROR("fuse_chan_recv gave EOF"); - - if (res < 0 && res != -EAGAIN) - ERROR("fuse_chan_recv failed: %s", strerror(-res)); - - if (res > 0) { - DEBUG("got %d bytes from /dev/fuse", res); - - // received a fuse_req, so process it - fuse_session_process(ctx->session, ctx->recv_buf, res, ch); - } - - // reschedule - if (event_add(ctx->ev, NULL)) - PERROR("event_add"); - - // ok, wait for the next event - return; - -error: - // close, but don't free - evfuse_close(ctx); -} - -struct evfuse *evfuse_new (struct event_base *evbase, struct fuse_args *args, struct fuse_lowlevel_ops *llops, void *cb_data) { - struct evfuse *ctx = NULL; - int multithreaded, foreground; - - // allocate our context - if ((ctx = calloc(1, sizeof(*ctx))) == NULL) - ERROR("calloc"); - - // parse the commandline for the mountpoint - if (fuse_parse_cmdline(args, &ctx->mountpoint, &multithreaded, &foreground) == -1) - ERROR("fuse_parse_cmdline"); - - // mount it - if ((ctx->chan = fuse_mount(ctx->mountpoint, args)) == NULL) - PERROR("fuse_mount_common"); - - // the receive buffer stufff - ctx->recv_size = fuse_chan_bufsize(ctx->chan); - - // allocate the recv buffer - if ((ctx->recv_buf = malloc(ctx->recv_size)) == NULL) - ERROR("malloc"); - - // allocate a low-level session - if ((ctx->session = fuse_lowlevel_new(args, llops, sizeof(*llops), cb_data)) == NULL) - PERROR("fuse_lowlevel_new"); - - // add the channel to the session - // this isn't strictly necessary, but let's do it anyways - fuse_session_add_chan(ctx->session, ctx->chan); - - // now, we can start listening for events on the channel - if ((ctx->ev = event_new(evbase, fuse_chan_fd(ctx->chan), EV_READ, &_evfuse_ev_read, ctx)) == NULL) - ERROR("event_new"); - - if (event_add(ctx->ev, NULL)) - PERROR("event_add"); - - // and then we wait - return ctx; - -error: - evfuse_free(ctx); - - return NULL; -} - -void evfuse_close (struct evfuse *ctx) { - if (ctx->ev) { - // remove our event - if (event_del(ctx->ev)) - PWARNING("event_del"); - - ctx->ev = NULL; - } - - 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); 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 40a3b13ffc9d -r 9dfc861273e5 src/evfuse.h --- a/src/evfuse.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#ifndef EVFUSE_H -#define EVFUSE_H - -#define FUSE_USE_VERSION 26 - -#include -#include - -/* - * A wrapper for the fuse + libevent context - */ -struct evfuse; - -/* - * Create a new new evfuse context. - */ -struct evfuse *evfuse_new (struct event_base *evbase, struct fuse_args *args, struct fuse_lowlevel_ops *llops, void *cb_data); - -/* - * Close and free evfuse context. - * - * Safe to call after errors/llops.destroy - */ -void evfuse_free (struct evfuse *ctx); - -#endif /* EVFUSE_H */ - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/evsql.h --- a/src/evsql.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,276 +0,0 @@ -#ifndef EVSQL_H -#define EVSQL_H - -/* - * An event-based (Postgre)SQL client API using libevent - */ - -// XXX: libpq -#include -#include -#include - -/* - * The generic context handle - */ -struct evsql; - -/* - * A query handle - */ -struct evsql_query; - -/* - * Transaction handle - */ -struct evsql_trans; - -/* - * Transaction type - */ -enum evsql_trans_type { - EVSQL_TRANS_DEFAULT, - EVSQL_TRANS_SERIALIZABLE, - EVSQL_TRANS_REPEATABLE_READ, - EVSQL_TRANS_READ_COMMITTED, - EVSQL_TRANS_READ_UNCOMMITTED, -}; - -/* - * Parameter type - */ -enum evsql_param_format { - EVSQL_FMT_TEXT, - EVSQL_FMT_BINARY, -}; - -enum evsql_param_type { - EVSQL_PARAM_INVALID, - - EVSQL_PARAM_NULL_, - - EVSQL_PARAM_BINARY, - EVSQL_PARAM_STRING, - EVSQL_PARAM_UINT16, - EVSQL_PARAM_UINT32, - EVSQL_PARAM_UINT64, -}; - -/* - * Query parameter info. - * - * Use the EVSQL_PARAM_* macros to define the value of list - */ -struct evsql_query_params { - // nonzero to get results in binary format - enum evsql_param_format result_fmt; - - // the list of parameters, terminated by { 0, 0 } - struct evsql_query_param { - // the param type - enum evsql_param_type type; - - // pointer to the raw data - const char *data_raw; - - // the value - union { - uint16_t uint16; - uint32_t uint32; - uint64_t uint64; - } data; - - // the explicit length of the parameter if it's binary, zero for text. - // set to -1 to indicate that the value is still missing - ssize_t length; - } list[]; -}; - -// macros for defining evsql_query_params -#define EVSQL_PARAMS(result_fmt) { result_fmt, -#define EVSQL_PARAM(typenam) { EVSQL_PARAM_ ## typenam, NULL } -#define EVSQL_PARAMS_END { EVSQL_PARAM_INVALID, NULL } \ - } // <<< - -/* - * Result type - */ -struct evsql_result_info { - struct evsql *evsql; - struct evsql_trans *trans; - - int error; - - union evsql_result { - // libpq - PGresult *pq; - } result; -}; - -/* - * Callback for handling query results. - * - * The query has completed, either succesfully or unsuccesfully (nonzero .error). - * - * Use the evsql_result_* functions to manipulate the results. - */ -typedef void (*evsql_query_cb)(const struct evsql_result_info *res, void *arg); - -/* - * Callback for handling global-level errors. - * - * The evsql is not useable anymore. - * - * XXX: this is not actually called yet, no retry logic implemented. - */ -typedef void (*evsql_error_cb)(struct evsql *evsql, void *arg); - -/* - * Callback for handling transaction-level errors. - * - * The transaction is not useable anymore. - */ -typedef void (*evsql_trans_error_cb)(struct evsql_trans *trans, void *arg); - -/* - * The transaction is ready for use. - */ -typedef void (*evsql_trans_ready_cb)(struct evsql_trans *trans, void *arg); - -/* - * The transaction was commited, and should not be used anymore. - */ -typedef void (*evsql_trans_done_cb)(struct evsql_trans *trans, void *arg); - -/* - * Create a new PostgreSQL/libpq(evpq) -based evsql using the given conninfo. - * - * The given conninfo must stay valid for the duration of the evsql's lifetime. - */ -struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg); - -/* - * Create a new transaction. - * - * Transactions are separate connections that provide transaction-isolation. - * - * Once the transaction is ready for use, ready_fn will be called. If the transaction fails, any pending query will be - * forgotten, and error_fn called. This also includes some (but not all) cases where evsql_query returns nonzero. - * - */ -struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, evsql_trans_error_cb error_fn, evsql_trans_ready_cb ready_fn, evsql_trans_done_cb done_fn, void *cb_arg); - -/* - * Queue the given query for execution. - * - * If trans is specified (not NULL), then the transaction must be idle, and the query will be executed in that - * transaction's context. Otherwise, the query will be executed without a transaction, andmay be executed immediately, - * or if other similar queries are running, it will be queued for later execution. - * - * Once the query is complete (got a result, got an error, the connection failed), then the query_cb will be triggered. - */ -struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg); - -/* - * Same as evsql_query, but uses the SQL-level support for binding parameters. - */ -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. - * - * done_fn will never be called directly, always via the event loop. - * - * 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. - * - * You cannot abort a COMMIT, calling trans_abort on trans after a succesful trans_commit is a FATAL error. - */ -void evsql_trans_abort (struct evsql_trans *trans); - -/* - * Transaction-handling functions - */ - -// error string, meant to be called from evsql_trans_error_cb -const char *evsql_trans_error (struct evsql_trans *trans); - -/* - * Param-building functions - */ -int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len); -int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr); -int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval); -int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval); -int evsql_param_null (struct evsql_query_params *params, size_t param); -int evsql_params_clear (struct evsql_query_params *params); - -/* - * Query-handling functions - */ - -// print out a textual repr of the given query/params via DEBUG -void evsql_query_debug (const char *sql, const struct evsql_query_params *params); - -/* - * Result-handling functions - */ - -// get error message associated with function -const char *evsql_result_error (const struct evsql_result_info *res); - -// number of rows in the result -size_t evsql_result_rows (const struct evsql_result_info *res); - -// number of columns in the result -size_t evsql_result_cols (const struct evsql_result_info *res); - -// number of affected rows for UPDATE/INSERT -size_t evsql_result_affected (const struct evsql_result_info *res); - -// fetch the raw binary value from a result set, and return it via ptr -// if size is nonzero, check that the size of the field data matches -int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok); -int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok); - -// fetch certain kinds of values from a binary result set -int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok); -int evsql_result_uint32 (const struct evsql_result_info *res, size_t row, size_t col, uint32_t *uval, int nullok); -int evsql_result_uint64 (const struct evsql_result_info *res, size_t row, size_t col, uint64_t *uval, int nullok); - -// release the result set, freeing its memory -void evsql_result_free (const struct evsql_result_info *res); - -// platform-dependant aliases -#define evsql_result_ushort evsql_result_uint16 - -#if _LP64 -#define evsql_result_ulong evsql_result_uint64 -#else -#define evsql_result_ulong evsql_result_uint32 -#endif /* _LP64 */ - -/* - * Close a connection. Callbacks for waiting queries will not be run. - * - * XXX: not implemented yet. - */ -void evsql_close (struct evsql *evsql); - -#endif /* EVSQL_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/evsql/evsql.c --- a/src/evsql/evsql.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1025 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include - -#include "evsql.h" -#include "../lib/log.h" -#include "../lib/error.h" -#include "../lib/misc.h" - -/* - * A couple function prototypes - */ -static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn); - -/* - * Actually execute the given query. - * - * The backend should be able to accept the query at this time. - * - * You should assume that if trying to execute a query fails, then the connection should also be considred as failed. - */ -static int _evsql_query_exec (struct evsql_conn *conn, struct evsql_query *query, const char *command) { - int err; - - DEBUG("evsql.%p: exec query=%p on trans=%p on conn=%p:", conn->evsql, query, conn->trans, conn); - - switch (conn->evsql->type) { - case EVSQL_EVPQ: - // got params? - if (query->params.count) { - err = evpq_query_params(conn->engine.evpq, command, - query->params.count, - query->params.types, - query->params.values, - query->params.lengths, - query->params.formats, - query->params.result_format - ); - - } else { - // plain 'ole query - err = evpq_query(conn->engine.evpq, command); - } - - if (err) { - if (PQstatus(evpq_pgconn(conn->engine.evpq)) != CONNECTION_OK) - WARNING("conn failed"); - else - WARNING("query failed, dropping conn as well"); - } - - break; - - default: - FATAL("evsql->type"); - } - - if (!err) - // assign the query - conn->query = query; - - return err; -} - -/* - * Free the query and related resources, doesn't trigger any callbacks or remove from any queues. - * - * 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 - free(query->params.types); - free(query->params.values); - free(query->params.lengths); - free(query->params.formats); - - // free the query itself - free(query); -} - -/* - * 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) { - 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); -} - -/* - * XXX: - * / -static void _evsql_destroy (struct evsql *evsql, const struct evsql_result_info *res) { - struct evsql_query *query; - - // clear the queue - while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) { - _evsql_query_done(query, res); - - TAILQ_REMOVE(&evsql->query_queue, query, entry); - } - - // free - free(evsql); -} -*/ - -/* - * Free the transaction, it should already be deassociated from the query and conn. - */ -static void _evsql_trans_free (struct evsql_trans *trans) { - // ensure we don't leak anything - assert(trans->query == NULL); - assert(trans->conn == NULL); - - // free - free(trans); -} - -/* - * Release a connection. It should already be deassociated from the trans and query. - * - * Releases the engine, removes from the conn_list and frees this. - */ -static void _evsql_conn_release (struct evsql_conn *conn) { - // ensure we don't leak anything - assert(conn->trans == NULL); - assert(conn->query == NULL); - - // release the engine - switch (conn->evsql->type) { - case EVSQL_EVPQ: - evpq_release(conn->engine.evpq); - break; - - default: - FATAL("evsql->type"); - } - - // remove from list - LIST_REMOVE(conn, entry); - - // catch deadlocks - assert(!LIST_EMPTY(&conn->evsql->conn_list) || TAILQ_EMPTY(&conn->evsql->query_queue)); - - // free - free(conn); -} - -/* - * Release a transaction, it should already be deassociated from the query. - * - * Perform a two-way-deassociation with the conn, and then free the trans. - */ -static void _evsql_trans_release (struct evsql_trans *trans) { - assert(trans->query == NULL); - assert(trans->conn != NULL); - - // deassociate the conn - trans->conn->trans = NULL; trans->conn = NULL; - - // free the trans - _evsql_trans_free(trans); -} - -/* - * 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 - _evsql_query_done(query, &res); -} - -/* - * 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) { - 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 (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; - - // pump the queue for requests that were waiting for this connection - _evsql_pump(trans->evsql, NULL); - - // free the trans - _evsql_trans_free(trans); -} - -/* - * Fail a connection. If the connection is transactional, this will just call _evsql_trans_fail, but otherwise it will - * fail any ongoing query, and then release the connection. - */ -static void _evsql_conn_fail (struct evsql_conn *conn) { - if (conn->trans) { - // let transactions handle their connection failures - _evsql_trans_fail(conn->trans); - - } else { - if (conn->query) { - // fail the in-progress query - _evsql_query_fail(conn->evsql, conn->query); conn->query = NULL; - } - - // finish off the whole connection - _evsql_conn_release(conn); - } -} - -/* - * Processes enqueued non-transactional queries until the queue is empty, or we managed to exec a query. - * - * If execing a query on a connection fails, both the query and the connection are failed (in that order). - * - * Any further queries will then also be failed, because there's no reconnection/retry logic yet. - * - * This means that if conn is NULL, all queries are failed. - */ -static void _evsql_pump (struct evsql *evsql, struct evsql_conn *conn) { - struct evsql_query *query; - int err; - - // look for waiting queries - while ((query = TAILQ_FIRST(&evsql->query_queue)) != NULL) { - // dequeue - TAILQ_REMOVE(&evsql->query_queue, query, entry); - - if (conn) { - // try and execute it - err = _evsql_query_exec(conn, query, query->command); - } - - // free the command buf - free(query->command); query->command = NULL; - - if (err || !conn) { - if (!conn) { - // warn when dropping queries - WARNING("failing query becuse there are no conns"); - } - - // fail the query - _evsql_query_fail(evsql, query); - - if (conn) { - // fail the connection - WARNING("failing the connection because a query-exec failed"); - - _evsql_conn_fail(conn); conn = NULL; - } - - } else { - // we have succesfully enqueued a query, and we can wait for this connection to complete - break; - - } - - // handle the rest of the queue - } - - // ok - return; -} - -/* - * Callback for a trans's 'BEGIN' query, which means the transaction is now ready for use. - */ -static void _evsql_trans_ready (const struct evsql_result_info *res, void *arg) { - (void) arg; - - assert(res->trans); - - // check for errors - if (res->error) - ERROR("transaction 'BEGIN' failed: %s", evsql_result_error(res)); - - // transaction is now ready for use - res->trans->ready_fn(res->trans, res->trans->cb_arg); - - // good - return; - -error: - _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 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; - - // determine the isolation_level to use - switch (trans->type) { - case EVSQL_TRANS_DEFAULT: - isolation_level = NULL; break; - - case EVSQL_TRANS_SERIALIZABLE: - isolation_level = "SERIALIZABLE"; break; - - case EVSQL_TRANS_REPEATABLE_READ: - isolation_level = "REPEATABLE READ"; break; - - case EVSQL_TRANS_READ_COMMITTED: - isolation_level = "READ COMMITTED"; break; - - case EVSQL_TRANS_READ_UNCOMMITTED: - isolation_level = "READ UNCOMMITTED"; break; - - default: - FATAL("trans->type: %d", trans->type); - } - - // build the trans_sql - if (isolation_level) - ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION ISOLATION LEVEL %s", isolation_level); - else - ret = snprintf(trans_sql, EVSQL_QUERY_BEGIN_BUF, "BEGIN TRANSACTION"); - - // make sure it wasn't truncated - if (ret >= EVSQL_QUERY_BEGIN_BUF) - 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) == NULL) - ERROR("evsql_query"); - - // success - return 0; - -error: - // fail the transaction - _evsql_trans_fail(trans); - - return -1; -} - -/* - * The evpq connection was succesfully established. - */ -static void _evsql_evpq_connected (struct evpq_conn *_conn, void *arg) { - struct evsql_conn *conn = arg; - - if (conn->trans) - // notify the transaction - // don't care about errors - (void) _evsql_trans_conn_ready(conn->evsql, conn->trans); - - else - // pump any waiting transactionless queries - _evsql_pump(conn->evsql, conn); -} - -/* - * Got one result on this evpq connection. - */ -static void _evsql_evpq_result (struct evpq_conn *_conn, PGresult *result, void *arg) { - struct evsql_conn *conn = arg; - struct evsql_query *query = conn->query; - - assert(query != NULL); - - // if we get multiple results, only return the first one - if (query->result.evpq) { - WARNING("[evsql] evpq query returned multiple results, discarding previous one"); - - PQclear(query->result.evpq); query->result.evpq = NULL; - } - - // remember the result - query->result.evpq = result; -} - -/* - * No more results for this query. - */ -static void _evsql_evpq_done (struct evpq_conn *_conn, void *arg) { - struct evsql_conn *conn = arg; - struct evsql_query *query = conn->query; - struct evsql_result_info res; ZINIT(res); - - assert(query != NULL); - - // 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 - WARNING("[evsql] evpq query didn't return any results"); - - res.error = 1; - - } else if (strcmp(PQresultErrorMessage(query->result.evpq), "") != 0) { - // the query failed with some error - res.error = 1; - res.result.pq = query->result.evpq; - - } else { - res.error = 0; - res.result.pq = query->result.evpq; - - } - - // de-associate the query from the connection - conn->query = NULL; - - // how we handle query completion depends on if we're a transaction or not - if (conn->trans) { - // 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); - - } else { - // a transactionless query, so just finish it off and pump any other waiting ones - _evsql_query_done(query, &res); - - // pump the next one - _evsql_pump(conn->evsql, conn); - } -} - -/* - * The connection failed. - */ -static void _evsql_evpq_failure (struct evpq_conn *_conn, void *arg) { - struct evsql_conn *conn = arg; - - // just fail the conn - _evsql_conn_fail(conn); -} - -/* - * Our evpq behaviour - */ -static struct evpq_callback_info _evsql_evpq_cb_info = { - .fn_connected = _evsql_evpq_connected, - .fn_result = _evsql_evpq_result, - .fn_done = _evsql_evpq_done, - .fn_failure = _evsql_evpq_failure, -}; - -/* - * Allocate the generic evsql context. - */ -static struct evsql *_evsql_new_base (struct event_base *ev_base, evsql_error_cb error_fn, void *cb_arg) { - struct evsql *evsql = NULL; - - // allocate it - if ((evsql = calloc(1, sizeof(*evsql))) == NULL) - ERROR("calloc"); - - // store - evsql->ev_base = ev_base; - evsql->error_fn = error_fn; - evsql->cb_arg = cb_arg; - - // init - LIST_INIT(&evsql->conn_list); - TAILQ_INIT(&evsql->query_queue); - - // done - return evsql; - -error: - return NULL; -} - -/* - * Start a new connection and add it to the list, it won't be ready until _evsql_evpq_connected is called - */ -static struct evsql_conn *_evsql_conn_new (struct evsql *evsql) { - struct evsql_conn *conn = NULL; - - // allocate - if ((conn = calloc(1, sizeof(*conn))) == NULL) - ERROR("calloc"); - - // init - conn->evsql = evsql; - - // connect the engine - switch (evsql->type) { - case EVSQL_EVPQ: - if ((conn->engine.evpq = evpq_connect(evsql->ev_base, evsql->engine_conf.evpq, _evsql_evpq_cb_info, conn)) == NULL) - goto error; - - break; - - default: - FATAL("evsql->type"); - } - - // add it to the list - LIST_INSERT_HEAD(&evsql->conn_list, conn, entry); - - // success - return conn; - -error: - free(conn); - - return NULL; -} - -struct evsql *evsql_new_pq (struct event_base *ev_base, const char *pq_conninfo, evsql_error_cb error_fn, void *cb_arg) { - struct evsql *evsql = NULL; - - // base init - if ((evsql = _evsql_new_base (ev_base, error_fn, cb_arg)) == NULL) - goto error; - - // store conf - evsql->engine_conf.evpq = pq_conninfo; - - // pre-create one connection - if (_evsql_conn_new(evsql) == NULL) - goto error; - - // done - return evsql; - -error: - // XXX: more complicated than this? - free(evsql); - - return NULL; -} - -/* - * Checks if the connection is already allocated for some other trans/query. - * - * Returns: - * 0 connection idle, can be allocated - * >1 connection busy - */ -static int _evsql_conn_busy (struct evsql_conn *conn) { - // transactions get the connection to themselves - if (conn->trans) - return 1; - - // if it has a query assigned, it's busy - if (conn->query) - return 1; - - // otherwise, it's all idle - return 0; -} - -/* - * Checks if the connection is ready for use (i.e. _evsql_evpq_connected was called). - * - * The connection should not already have a query running. - * - * Returns - * <0 the connection is not valid (failed, query in progress) - * 0 the connection is still pending, and will become ready at some point - * >0 it's ready - */ -static int _evsql_conn_ready (struct evsql_conn *conn) { - switch (conn->evsql->type) { - case EVSQL_EVPQ: { - enum evpq_state state = evpq_state(conn->engine.evpq); - - switch (state) { - case EVPQ_CONNECT: - return 0; - - case EVPQ_CONNECTED: - return 1; - - case EVPQ_QUERY: - case EVPQ_INIT: - case EVPQ_FAILURE: - return -1; - - default: - FATAL("evpq_state: %d", state); - } - - } - - default: - FATAL("evsql->type: %d", conn->evsql->type); - } -} - -/* - * Allocate a connection for use and return it via *conn_ptr, or if may_queue is nonzero and the connection pool is - * getting full, return NULL (query should be queued). - * - * Note that the returned connection might not be ready for use yet (if we created a new one, see _evsql_conn_ready). - * - * Returns zero if a connection was found or the request should be queued, or nonzero if something failed and the - * 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; - - // accept pending conns as long as there are NO enqueued queries (might cause deadlock otherwise) - if (_evsql_conn_ready(*conn_ptr) == 0 && TAILQ_EMPTY(&evsql->query_queue)) - break; - - // accept conns that are in a fully ready state - if (_evsql_conn_ready(*conn_ptr) > 0) - break; - } - - // if we found an idle connection, we can just return that right away - if (*conn_ptr) - return 0; - - // 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 - if ((*conn_ptr = _evsql_conn_new(evsql)) == NULL) - goto error; - - // good - return 0; -error: - return -1; -} - -struct evsql_trans *evsql_trans (struct evsql *evsql, enum evsql_trans_type type, evsql_trans_error_cb error_fn, evsql_trans_ready_cb ready_fn, evsql_trans_done_cb done_fn, void *cb_arg) { - struct evsql_trans *trans = NULL; - - // allocate it - if ((trans = calloc(1, sizeof(*trans))) == NULL) - ERROR("calloc"); - - // store - trans->evsql = evsql; - trans->ready_fn = ready_fn; - trans->done_fn = done_fn; - trans->cb_arg = cb_arg; - trans->type = type; - - // find a connection - if (_evsql_conn_get(evsql, &trans->conn, 0)) - ERROR("_evsql_conn_get"); - - // associate the conn - trans->conn->trans = trans; - - // is it already ready? - if (_evsql_conn_ready(trans->conn) > 0) { - // 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; - -error: - free(trans); - - return NULL; -} - -/* - * Validate and allocate the basic stuff for a new query. - */ -static struct evsql_query *_evsql_query_new (struct evsql *evsql, struct evsql_trans *trans, evsql_query_cb query_fn, void *cb_arg) { - struct evsql_query *query = NULL; - - // if it's part of a trans, then make sure the trans is idle - if (trans && trans->query) - ERROR("transaction is busy"); - - // allocate it - if ((query = calloc(1, sizeof(*query))) == NULL) - ERROR("calloc"); - - // store - query->cb_fn = query_fn; - query->cb_arg = cb_arg; - - // success - return query; - -error: - return NULL; -} - -/* - * Handle a new query. - * - * For transactions this will associate the query and then execute it, otherwise this will either find an idle - * connection and send the query, or enqueue it. - */ -static int _evsql_query_enqueue (struct evsql *evsql, struct evsql_trans *trans, struct evsql_query *query, const char *command) { - // transaction queries are handled differently - if (trans) { - // it's an in-transaction query - assert(trans->query == NULL); - - // assign the query - trans->query = query; - - // execute directly - if (_evsql_query_exec(trans->conn, query, command)) { - // ack, fail the transaction - _evsql_trans_fail(trans); - - // caller frees query - goto error; - } - - } else { - struct evsql_conn *conn; - - // find an idle connection - if ((_evsql_conn_get(evsql, &conn, 1))) - ERROR("couldn't allocate a connection for the query"); - - // we must enqueue if no idle conn or the conn is not yet ready - if (conn && _evsql_conn_ready(conn) > 0) { - // execute directly - if (_evsql_query_exec(conn, query, command)) { - // ack, fail the connection - _evsql_conn_fail(conn); - - // make sure we don't deadlock any queries, but if this query got a conn directly, then we shouldn't - // have any queries enqueued anyways - assert(TAILQ_EMPTY(&evsql->query_queue)); - - // caller frees query - goto error; - } - - } else { - // copy the command for later execution - if ((query->command = strdup(command)) == NULL) - ERROR("strdup"); - - // enqueue until some connection pumps the queue - TAILQ_INSERT_TAIL(&evsql->query_queue, query, entry); - } - } - - // ok, good - return 0; - -error: - return -1; -} - -struct evsql_query *evsql_query (struct evsql *evsql, struct evsql_trans *trans, const char *command, evsql_query_cb query_fn, void *cb_arg) { - struct evsql_query *query = NULL; - - // alloc new query - if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL) - goto error; - - // just execute the command string directly - if (_evsql_query_enqueue(evsql, trans, query, command)) - goto error; - - // ok - return query; - -error: - _evsql_query_free(query); - - return NULL; -} - -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) { - struct evsql_query *query = NULL; - const struct evsql_query_param *param; - int idx; - - // alloc new query - if ((query = _evsql_query_new(evsql, trans, query_fn, cb_arg)) == NULL) - goto error; - - // count the params - for (param = params->list; param->type; param++) - query->params.count++; - - // allocate the vertical storage for the parameters - if (0 - - || !(query->params.types = calloc(query->params.count, sizeof(Oid))) - || !(query->params.values = calloc(query->params.count, sizeof(char *))) - || !(query->params.lengths = calloc(query->params.count, sizeof(int))) - || !(query->params.formats = calloc(query->params.count, sizeof(int))) - ) - ERROR("calloc"); - - // transform - for (param = params->list, idx = 0; param->type; param++, idx++) { - // `set for NULLs, otherwise not - query->params.types[idx] = param->data_raw ? 0 : EVSQL_PQ_ARBITRARY_TYPE_OID; - - // values - query->params.values[idx] = param->data_raw; - - // lengths - query->params.lengths[idx] = param->length; - - // formats, binary if length is nonzero, but text for NULLs - query->params.formats[idx] = param->length && param->data_raw ? 1 : 0; - } - - // result format - switch (params->result_fmt) { - case EVSQL_FMT_TEXT: - query->params.result_format = 0; break; - - case EVSQL_FMT_BINARY: - query->params.result_format = 1; break; - - default: - FATAL("params.result_fmt: %d", params->result_fmt); - } - - // execute it - if (_evsql_query_enqueue(evsql, trans, query, command)) - goto error; - -#ifdef DEBUG_ENABLED - // debug it? - DEBUG("evsql.%p: enqueued query=%p on trans=%p", evsql, query, trans); - evsql_query_debug(command, params); -#endif /* DEBUG_ENABLED */ - - // ok - return query; - -error: - _evsql_query_free(query); - - 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; - - assert(res->trans); - - // check for errors - if (res->error) - ERROR("transaction 'COMMIT' failed: %s", evsql_result_error(res)); - - // transaction is now done - res->trans->done_fn(res->trans, res->trans->cb_arg); - - // release it - _evsql_trans_release(res->trans); - - // success - return; - -error: - _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 - 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) { - (void) arg; - - assert(res->trans); - - // fail the connection on errors - if (res->error) - ERROR("transaction 'ROLLBACK' failed: %s", evsql_result_error(res)); - - // release it - _evsql_trans_release(res->trans); - - // success - return; - -error: - // fail the connection too, errors are supressed - _evsql_trans_fail(res->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 - 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 40a3b13ffc9d -r 9dfc861273e5 src/evsql/evsql.h --- a/src/evsql/evsql.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -#ifndef EVSQL_INTERNAL_H -#define EVSQL_INTERNAL_H - -/* - * Internal interfaces - */ - -#include - -#include - -#include "../evsql.h" -#include "../evpq.h" - -/* - * The engine type - */ -enum evsql_type { - EVSQL_EVPQ, // evpq -}; - -/* - * Contains the type, engine configuration, list of connections and waiting query queue. - */ -struct evsql { - // what event_base to use - struct event_base *ev_base; - - // what engine we use - enum evsql_type type; - - // callbacks - evsql_error_cb error_fn; - void *cb_arg; - - // engine-specific connection configuration - union { - const char *evpq; - } engine_conf; - - // list of connections that are open - LIST_HEAD(evsql_conn_list, evsql_conn) conn_list; - - // list of queries running or waiting to run - TAILQ_HEAD(evsql_query_queue, evsql_query) query_queue; -}; - -/* - * A single connection to the server. - * - * Contains the engine connection, may have a transaction associated, and may have a query associated. - */ -struct evsql_conn { - // evsql we belong to - struct evsql *evsql; - - // engine-specific connection info - union { - struct evpq_conn *evpq; - } engine; - - // our position in the conn list - LIST_ENTRY(evsql_conn) entry; - - // are we running a transaction? - struct evsql_trans *trans; - - // are we running a transactionless query? - struct evsql_query *query; -}; - -/* - * A single transaction. - * - * Has a connection associated and possibly a query (which will also be associated with the connection) - */ -struct evsql_trans { - // our evsql_conn/evsql - struct evsql *evsql; - struct evsql_conn *conn; - - // callbacks - evsql_trans_error_cb error_fn; - evsql_trans_ready_cb ready_fn; - evsql_trans_done_cb done_fn; - void *cb_arg; - - // 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 single query. - * - * Has the info needed to exec the query (as these may be queued), and the callback/result info. - */ -struct evsql_query { - // the actual SQL query, this may or may not be ours, see _evsql_query_exec - char *command; - - // possible query params - struct evsql_query_param_info { - int count; - - Oid *types; - const char **values; - int *lengths; - int *formats; - - int result_format; - } params; - - // our callback - evsql_query_cb cb_fn; - void *cb_arg; - - // our position in the query list - TAILQ_ENTRY(evsql_query) entry; - - // the result - union { - PGresult *evpq; - } result; -}; - -// maximum length for a 'BEGIN TRANSACTION ...' query -#define EVSQL_QUERY_BEGIN_BUF 512 - -// the should the OID of some valid psql type... *ANY* valid psql type, doesn't matter, only used for NULLs -// 16 = bool in 8.3 -#define EVSQL_PQ_ARBITRARY_TYPE_OID 16 - -#endif /* EVSQL_INTERNAL_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/evsql/util.c --- a/src/evsql/util.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,331 +0,0 @@ -#include -#include - -#include "evsql.h" -#include "../lib/log.h" -#include "../lib/misc.h" - -#define _PARAM_TYPE_CASE(typenam) case EVSQL_PARAM_ ## typenam: return #typenam - -#define _PARAM_VAL_BUF_MAX 120 -#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_PARAM_ ## typenam: if (param->data_raw) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break - -const char *evsql_param_type (const struct evsql_query_param *param) { - switch (param->type) { - _PARAM_TYPE_CASE (INVALID ); - _PARAM_TYPE_CASE (NULL_ ); - _PARAM_TYPE_CASE (BINARY ); - _PARAM_TYPE_CASE (STRING ); - _PARAM_TYPE_CASE (UINT16 ); - _PARAM_TYPE_CASE (UINT32 ); - _PARAM_TYPE_CASE (UINT64 ); - default: return "???"; - } -} - - -static const char *evsql_param_val (const struct evsql_query_param *param) { - static char buf[_PARAM_VAL_BUF_MAX]; - int ret; - - switch (param->type) { - _PARAM_VAL_CASE (INVALID, "???" ); - _PARAM_VAL_CASE (NULL_, "(null)" ); - _PARAM_VAL_CASE (BINARY, "%zu:%s", param->length, "..." ); - _PARAM_VAL_CASE (STRING, "%s", param->data_raw ); - _PARAM_VAL_CASE (UINT16, "%hu", (unsigned short int) ntohs(param->data.uint16) ); - _PARAM_VAL_CASE (UINT32, "%lu", (unsigned long int) ntohl(param->data.uint32) ); - _PARAM_VAL_CASE (UINT64, "%llu", (unsigned long long int) ntohq(param->data.uint64) ); - default: return "???"; - } - - return buf; -} - -int evsql_params_clear (struct evsql_query_params *params) { - struct evsql_query_param *param; - - for (param = params->list; param->type; param++) - param->data_raw = NULL; - - return 0; -} - -int evsql_param_null (struct evsql_query_params *params, size_t param) { - struct evsql_query_param *p = ¶ms->list[param]; - - p->data_raw = NULL; - - return 0; -} - -int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) { - struct evsql_query_param *p = ¶ms->list[param]; - - assert(p->type == EVSQL_PARAM_BINARY); - - p->data_raw = ptr; - p->length = len; - - return 0; -} - -int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) { - struct evsql_query_param *p = ¶ms->list[param]; - - assert(p->type == EVSQL_PARAM_STRING); - - p->data_raw = ptr; - p->length = 0; - - return 0; -} - -int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) { - struct evsql_query_param *p = ¶ms->list[param]; - - assert(p->type == EVSQL_PARAM_UINT16); - - p->data.uint16 = htons(uval); - p->data_raw = (const char *) &p->data.uint16; - p->length = sizeof(uval); - - return 0; -} - -int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) { - struct evsql_query_param *p = ¶ms->list[param]; - - assert(p->type == EVSQL_PARAM_UINT32); - - p->data.uint32 = htonl(uval); - p->data_raw = (const char *) &p->data.uint32; - p->length = sizeof(uval); - - return 0; -} - -void evsql_query_debug (const char *sql, const struct evsql_query_params *params) { - const struct evsql_query_param *param; - size_t param_count = 0, idx = 0; - - // count the params - for (param = params->list; param->type; param++) - param_count++; - - DEBUG("sql: %s", sql); - DEBUG("params: %zu", param_count); - - for (param = params->list; param->type; param++) { - DEBUG("\t%2zu : %8s = %s", ++idx, evsql_param_type(param), evsql_param_val(param)); - } -} - -const char *evsql_result_error (const struct evsql_result_info *res) { - if (!res->error) - return "No error"; - - switch (res->evsql->type) { - case EVSQL_EVPQ: - if (!res->result.pq) - return "unknown error (no result)"; - - return PQresultErrorMessage(res->result.pq); - - default: - FATAL("res->evsql->type"); - } - -} - -size_t evsql_result_rows (const struct evsql_result_info *res) { - switch (res->evsql->type) { - case EVSQL_EVPQ: - return PQntuples(res->result.pq); - - default: - FATAL("res->evsql->type"); - } -} - -size_t evsql_result_cols (const struct evsql_result_info *res) { - switch (res->evsql->type) { - case EVSQL_EVPQ: - return PQnfields(res->result.pq); - - default: - FATAL("res->evsql->type"); - } -} - -size_t evsql_result_affected (const struct evsql_result_info *res) { - switch (res->evsql->type) { - case EVSQL_EVPQ: - return strtol(PQcmdTuples(res->result.pq), NULL, 10); - - default: - FATAL("res->evsql->type"); - } -} - -int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok) { - *ptr = NULL; - - switch (res->evsql->type) { - case EVSQL_EVPQ: - if (PQgetisnull(res->result.pq, row, col)) { - if (nullok) - return 0; - else - ERROR("[%zu:%zu] field is null", row, col); - } - - if (PQfformat(res->result.pq, col) != 1) - ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col)); - - *size = PQgetlength(res->result.pq, row, col); - *ptr = PQgetvalue(res->result.pq, row, col); - - return 0; - - default: - FATAL("res->evsql->type"); - } - -error: - return -1; -} - -int evsql_result_binlen (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) { - size_t real_size = 0; - - if (evsql_result_binary(res, row, col, ptr, &real_size, nullok)) - goto error; - - if (*ptr == NULL) { - assert(nullok); - return 0; - } - - if (size && real_size != size) - ERROR("[%zu:%zu] field size mismatch: %zu -> %zu", row, col, size, real_size); - - return 0; - -error: - return -1; -} - -int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok) { - size_t real_size; - - if (evsql_result_binary(res, row, col, ptr, &real_size, nullok)) - goto error; - - assert(real_size == strlen(*ptr)); - - return 0; - -error: - return -1; -} - -int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok) { - const char *data; - int16_t sval; - - if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok)) - goto error; - - if (!data) - return 0; - - sval = ntohs(*((int16_t *) data)); - - if (sval < 0) - ERROR("negative value for unsigned: %d", sval); - - *uval = sval; - - return 0; - -error: - return nullok ? 0 : -1; -} - -int evsql_result_uint32 (const struct evsql_result_info *res, size_t row, size_t col, uint32_t *uval, int nullok) { - const char *data; - int32_t sval; - - if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok)) - goto error; - - if (!data) - return 0; - - sval = ntohl(*(int32_t *) data); - - if (sval < 0) - ERROR("negative value for unsigned: %d", sval); - - *uval = sval; - - return 0; - -error: - return nullok ? 0 : -1; -} - -int evsql_result_uint64 (const struct evsql_result_info *res, size_t row, size_t col, uint64_t *uval, int nullok) { - const char *data; - int64_t sval; - - if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok)) - goto error; - - if (!data) - return 0; - - sval = ntohq(*(int64_t *) data); - - if (sval < 0) - ERROR("negative value for unsigned: %ld", sval); - - *uval = sval; - - return 0; - -error: - return nullok ? 0 : -1; -} - -void evsql_result_free (const struct evsql_result_info *res) { - switch (res->evsql->type) { - case EVSQL_EVPQ: - return PQclear(res->result.pq); - - default: - FATAL("res->evsql->type"); - } -} - -const char *evsql_conn_error (struct evsql_conn *conn) { - switch (conn->evsql->type) { - case EVSQL_EVPQ: - if (!conn->engine.evpq) - return "unknown error (no conn)"; - - return evpq_error_message(conn->engine.evpq); - - default: - FATAL("res->evsql->type"); - } -} - -const char *evsql_trans_error (struct evsql_trans *trans) { - if (trans->conn == NULL) - return "unknown error (no trans conn)"; - - return evsql_conn_error(trans->conn); -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/hello.c --- a/src/hello.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,247 +0,0 @@ -#include -#include -#include - -#include -#include - -#include "lib/log.h" -#include "lib/math.h" -#include "lib/signals.h" -#include "evfuse.h" -#include "dirbuf.h" - -const char *file_name = "hello"; -const char *file_data = "Hello World\n"; - -static struct hello { - struct event_base *ev_base; - - struct signals *signals; - - struct evfuse *ev_fuse; - -} ctx; - -void hello_init (void *userdata, struct fuse_conn_info *conn) { - INFO("[hello.init] userdata=%p, conn=%p", userdata, conn); -} - -void hello_destroy (void *userdata) { - INFO("[hello.destroy] userdata=%p", userdata); -} - -void hello_lookup (fuse_req_t req, fuse_ino_t parent, const char *name) { - struct fuse_entry_param e; - - INFO("[hello.lookup] (uid=%d, pid=%d) parent=%lu name=%s", fuse_req_ctx(req)->uid, fuse_req_ctx(req)->pid, parent, name); - - // the world is flat - if (parent != 1 || strcmp(name, file_name)) { - fuse_reply_err(req, ENOENT); - - return; - } - - // set up the entry - memset(&e, 0, sizeof(e)); - e.ino = 2; - e.attr_timeout = 1.0; - e.entry_timeout = 1.0; - e.attr.st_mode = S_IFREG | 0444; - e.attr.st_nlink = 1; - e.attr.st_size = strlen(file_data); - - // reply - fuse_reply_entry(req, &e); -} - -void hello_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct stat stbuf; - - INFO("[hello.getattr] (uid=%d, pid=%d) ino=%lu, fi=%p", fuse_req_ctx(req)->uid, fuse_req_ctx(req)->pid, ino, fi); - - memset(&stbuf, 0, sizeof(stbuf)); - - // the root dir, or the file? - if (ino == 1) { - stbuf.st_mode = S_IFDIR | 0555; - stbuf.st_nlink = 2; - - } else if (ino == 2) { - stbuf.st_mode = S_IFREG | 0444; - stbuf.st_nlink = 1; - stbuf.st_size = strlen(file_data); - - } else { - fuse_reply_err(req, ENOENT); - return; - } - - // reply - fuse_reply_attr(req, &stbuf, 1.0); -} - - -void hello_readdir (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { - int err = 0; - struct dirbuf buf; - - INFO("[hello.readdir] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi); - - // there exists only one dir - if (ino != 1) { - fuse_reply_err(req, ENOTDIR); - return; - } - - // fill in the dirbuf - if (dirbuf_init(&buf, size, off)) - ERROR("failed to init dirbuf"); - - 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"); - - // send it - if ((err = -dirbuf_done(req, &buf))) - EERROR(-err, "failed to send buf"); - - // success - return; - -error: - if ((err = fuse_reply_err(req, err ? err : EIO))) - EWARNING(err, "failed to send error reply"); -} - -void hello_open (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { - int err = 0; - - INFO("[hello.open] ino=%lu, fi=%p, fi->flags=%08X", ino, fi, fi->flags); - - if (ino != 2) { - // must open our only file, not the dir - fuse_reply_err(req, ino == 1 ? EISDIR : ENOENT); - return; - - } else if ((fi->flags & 0x03) != O_RDONLY) { - // "permission denied" - fuse_reply_err(req, EACCES); - return; - } - - // XXX: update fi stuff? - - // open it! - if ((err = fuse_reply_open(req, fi))) - EERROR(err, "fuse_reply_open"); - - // success - return; - -error: - if ((err = fuse_reply_err(req, err ? err : EIO))) - EWARNING(err, "failed to send error reply"); -} - -void hello_read (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { - int err = 0; - - // fi is unused - (void) fi; - - INFO("[hello.read] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi); - - if (ino != 2) { - // EEK! - FATAL("wrong inode"); - } - - if (off >= strlen(file_data)) { - // offset is out-of-file, so return EOF - err = fuse_reply_buf(req, NULL, 0); - - } else { - // reply with the requested file data - err = fuse_reply_buf(req, file_data + off, MIN(strlen(file_data) - off, size)); - } - - // reply - if (err) - PERROR("fuse_reply_buf"); - - // success - return; - -error: - if ((err = fuse_reply_err(req, err ? err : EIO))) - EWARNING(err, "failed to send error reply"); -} - -void hello_getxattr (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) { - INFO("[hello.getxattr] ino=%lu, name=`%s', size=%zu", ino, name, size); - - fuse_reply_err(req, ENOSYS); -} - -struct fuse_lowlevel_ops hello_llops = { - .init = &hello_init, - .destroy = &hello_destroy, - - .lookup = &hello_lookup, - .getattr = &hello_getattr, - - .open = &hello_open, - - .read = &hello_read, - - .readdir = &hello_readdir, - - .getxattr = hello_getxattr, -}; - - -int main (int argc, char **argv) { - struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv); - - // zero - memset(&ctx, 0, sizeof(ctx)); - - // init libevent - if ((ctx.ev_base = event_base_new()) == NULL) - ERROR("event_base_new"); - - // setup signals - if ((ctx.signals = signals_default(ctx.ev_base)) == NULL) - ERROR("signals_default"); - - // open fuse - if ((ctx.ev_fuse = evfuse_new(ctx.ev_base, &fuse_args, &hello_llops, &ctx)) == NULL) - ERROR("evfuse_new"); - - // run libevent - INFO("running libevent loop"); - - if (event_base_dispatch(ctx.ev_base)) - PERROR("event_base_dispatch"); - - // clean shutdown - -error : - // cleanup - if (ctx.ev_fuse) - evfuse_free(ctx.ev_fuse); - - if (ctx.signals) - signals_free(ctx.signals); - - if (ctx.ev_base) - event_base_free(ctx.ev_base); - - fuse_opt_free_args(&fuse_args); -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/helloworld.c --- a/src/helloworld.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -#include - -int main (void) { - printf("Hello World\n"); - - return 0; -} diff -r 40a3b13ffc9d -r 9dfc861273e5 src/internal.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/internal.h Sun Mar 08 00:19:12 2009 +0200 @@ -0,0 +1,140 @@ +#ifndef EVSQL_INTERNAL_H +#define EVSQL_INTERNAL_H + +/* + * Internal interfaces + */ + +#include + +#include + +#include "evsql.h" +#include "evpq.h" + +/* + * The engine type + */ +enum evsql_type { + EVSQL_EVPQ, // evpq +}; + +/* + * Contains the type, engine configuration, list of connections and waiting query queue. + */ +struct evsql { + // what event_base to use + struct event_base *ev_base; + + // what engine we use + enum evsql_type type; + + // callbacks + evsql_error_cb error_fn; + void *cb_arg; + + // engine-specific connection configuration + union { + const char *evpq; + } engine_conf; + + // list of connections that are open + LIST_HEAD(evsql_conn_list, evsql_conn) conn_list; + + // list of queries running or waiting to run + TAILQ_HEAD(evsql_query_queue, evsql_query) query_queue; +}; + +/* + * A single connection to the server. + * + * Contains the engine connection, may have a transaction associated, and may have a query associated. + */ +struct evsql_conn { + // evsql we belong to + struct evsql *evsql; + + // engine-specific connection info + union { + struct evpq_conn *evpq; + } engine; + + // our position in the conn list + LIST_ENTRY(evsql_conn) entry; + + // are we running a transaction? + struct evsql_trans *trans; + + // are we running a transactionless query? + struct evsql_query *query; +}; + +/* + * A single transaction. + * + * Has a connection associated and possibly a query (which will also be associated with the connection) + */ +struct evsql_trans { + // our evsql_conn/evsql + struct evsql *evsql; + struct evsql_conn *conn; + + // callbacks + evsql_trans_error_cb error_fn; + evsql_trans_ready_cb ready_fn; + evsql_trans_done_cb done_fn; + void *cb_arg; + + // 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 single query. + * + * Has the info needed to exec the query (as these may be queued), and the callback/result info. + */ +struct evsql_query { + // the actual SQL query, this may or may not be ours, see _evsql_query_exec + char *command; + + // possible query params + struct evsql_query_param_info { + int count; + + Oid *types; + const char **values; + int *lengths; + int *formats; + + int result_format; + } params; + + // our callback + evsql_query_cb cb_fn; + void *cb_arg; + + // our position in the query list + TAILQ_ENTRY(evsql_query) entry; + + // the result + union { + PGresult *evpq; + } result; +}; + +// maximum length for a 'BEGIN TRANSACTION ...' query +#define EVSQL_QUERY_BEGIN_BUF 512 + +// the should the OID of some valid psql type... *ANY* valid psql type, doesn't matter, only used for NULLs +// 16 = bool in 8.3 +#define EVSQL_PQ_ARBITRARY_TYPE_OID 16 + +#endif /* EVSQL_INTERNAL_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/lib/lex.c --- a/src/lib/lex.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,181 +0,0 @@ - -#include - -#include "lex.h" -#include "error.h" -#include "log.h" - -#define INITIAL_BUF_SIZE 4096 - -int lexer (const struct lex *lex, const char *input, void *arg) { - // handling error returns - int err = -1, cb_err; - - // token buffer - char *buf = NULL, *buf_ptr; - size_t buf_size = INITIAL_BUF_SIZE; - - // state - int prev_state = LEX_INITIAL, cur_state = lex->initial_state, next_state = LEX_INITIAL; - - // input chars - const char *c = input; - - // lookups - const struct lex_transition *trans = NULL; - - // allocate the buffer - if ((buf = malloc(sizeof(char) * buf_size)) == NULL) - goto error; - - // set buf_ptr initial position - buf_ptr = buf; - - // clear input - DEBUG("*cough*"); - DEBUGN("%s", ""); - - // process input - do { - if (*c) { - // look up the next state - for (trans = lex->state_list[cur_state - 1].trans_list; trans->next_state > 0 || trans->flags; trans++) { - // accept defaults - if (trans->flags & LEX_TRANS_DEFAULT) - break; - - // disregard non-matches - if (trans->left > *c || *c > trans->right) - continue; - - // abort on invalids - if (trans->flags & LEX_TRANS_INVALID) { - goto error; - - } else { - // accept it - break; - } - } - - // did we find a transition with a valid next state? - if (!(next_state = trans->next_state)) - goto error; - - // call the char handler - if (lex->char_fn && (cb_err = lex->char_fn(*c, cur_state, next_state, arg))) - goto error; - - } else { - // EOF! - next_state = LEX_EOF; - - // is cur_state a valid end state? - if (!(lex->state_list[cur_state - 1].flags & LEX_STATE_END)) - goto error; - - // note: we don't pass the NUL byte to the char handler - } - - // if this char is part of the next token... - if (next_state != cur_state) { - // terminate the buffer and reset buf_ptr - *buf_ptr = 0; buf_ptr = buf; - - // dump state transitions - DEBUGF("\n\t%25s -> %25s -> %25s", - LEX_STATE_NAME(lex, prev_state), - LEX_STATE_NAME(lex, cur_state), - LEX_STATE_NAME(lex, next_state) - ); - - // pass in the complete token to the handler - if (lex->token_fn && (cb_err = lex->token_fn(cur_state, buf, next_state, prev_state, arg))) - goto error; - - // update states - prev_state = cur_state; - cur_state = next_state; - next_state = LEX_INITIAL; - } - - // dump chars - if (next_state == LEX_INITIAL) - DEBUGN("%c", *c); - else - DEBUGNF("%c", *c); - - // store this char in the buffer - *(buf_ptr++) = *c; - - // grow the buffer if needed - if (buf_ptr - buf >= buf_size) { - // remember the offset, as buf_ptr might get invalidated if buf is moved - size_t buf_offset = buf_ptr - buf; - - // calc new size - buf_size *= 2; - - // grow/move - if ((buf = realloc(buf, buf_size)) == NULL) - goto error; - - // fix buf_ptr - buf_ptr = buf + buf_offset; - } - } while (*(c++)); - - // call the end handler - if (lex->end_fn && (cb_err = lex->end_fn(cur_state, arg))) - goto error; - - // successfully parsed! - err = 0; - -error: - DEBUGNF("\n"); - - if (cb_err) - err = cb_err; - - // dump debug info on error - if (err) { - const char *cc; - - // figure out the error - if (!buf) - WARNING("malloc/realloc"); - - else if (trans && trans->flags & LEX_TRANS_INVALID) - WARNING("hit invalid transition match"); - - else if (!next_state) - WARNING("no valid transition found"); - - else if (next_state == LEX_EOF && !(lex->state_list[cur_state - 1].flags & LEX_STATE_END)) - WARNING("invalid end state"); - - else - WARNING("unknown error condition (!?)"); - - DEBUG("%s", input); - DEBUGN("%s", ""); - - for (cc = input; cc < c; cc++) - DEBUGNF(" "); - - DEBUGF("^\t%s -> %s -> %s", - LEX_STATE_NAME(lex, prev_state), - LEX_STATE_NAME(lex, cur_state), - LEX_STATE_NAME(lex, next_state) - ); - } - - // free stuff - free(buf); - - // return - return err; -} - - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/lib/lex.h --- a/src/lib/lex.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -#ifndef LIB_LEXER_H -#define LIB_LEXER_H - -/* - * Simple FSM lexing - * - * The lexer is implemented as a Finite State Machine, consisting for a number of states, which then contain a set of - * transitions, which move the lexer from state to state based on each char of input at a time. - * - * Whenever the state changes, the token callback is triggered with the collected token data. - */ - -#include - -/* - * Transition flags - */ -enum lex_transition_flags { - LEX_TRANS_DEFAULT = 0x01, - /* not supported - LEX_TRANS_FINAL = 0x02, */ - LEX_TRANS_INVALID = 0x04, -}; - -/* - * A transition from one state to another. - */ -struct lex_transition { - // applies to chars [left, right] - char left, right; - - // flags from lex_transition_flags - char flags; - - // next state to enter - int next_state; -}; - -/* - * State flags - */ -enum lex_state_flags { - LEX_STATE_END = 0x01, -}; - -/* - * A state - */ -struct lex_state { - // the state name (for debugging) - const char *name; - - // flags from lex_state_flags - char flags; - - // list of transitions for this state, terminated by a transition with next_state=0 - struct lex_transition trans_list[15]; -}; - -/* - * Special states, these are all defined as zero - */ - -// shows up in token_fn as the value of next_token when this_token is the last token. -#define LEX_EOF 0 - -// shows up as the initial value of prev_token -#define LEX_INITIAL 0 - -/* - * Lex machine - */ -struct lex { - /* - * Core token handler. Everytime a full token is lexed (i.e. the state changes), this will be called. - * `this_token` represents the full token that was parsed, and `token_data` is the token's value. `next_token` - * is the state that terminated this token, and `prev_token` was the token before this one. - * - * `token_data` is a buffer allocated by the lexer that the actual input data is copied into. Thence, it can be - * modified, as its contents will be replaced by the next token. Hence, if you need to keep hold of it, copy it. - * - * Return zero to have lexing continue, nonzero to stop lexing. - */ - int (*token_fn) (int this_token, char *token_data, int next_token, int prev_token, void *arg); - - /* - * Called on every char handled by the lexer. - * - * The NUL byte at the end of the input string is not passed to char_fn (why not?). - * - * Return zero to have lexing continue, nonzero to stop lexing. - */ - int (*char_fn) (char token_char, int from_token, int to_token, void *arg); - - /* - * Called when the end of input has been reached, `last_token` is the state that we terminated in. - * - * Return zero to indiciate that the input was valid, nonzero to indicate an error. - */ - int (*end_fn) (int last_token, void *arg); - - // number of states - size_t state_count; - - // initial state - int initial_state; - - // array of lex_states, indexable by the state id. - struct lex_state state_list[]; -}; - -/* - * Helper macros for building the state_list - */ -#define LEX_STATE(enum_val) { #enum_val, 0, -#define LEX_STATE_END(enum_val) { #enum_val, LEX_STATE_END, - - #define LEX_CHAR(c, to) { c, c, 0, to } - #define LEX_RANGE(l, r, to) { l, r, 0, to } - #define LEX_ALPHA(to) LEX_RANGE('a', 'z', to), LEX_RANGE('A', 'Z', to) - #define LEX_NUMBER(to) LEX_RANGE('0', '9', to) - #define LEX_ALNUM(to) LEX_ALPHA(to), LEX_NUMBER(to), LEX_CHAR('-', to), LEX_CHAR('_', to) - #define LEX_WHITESPACE(to) LEX_CHAR(' ', to), LEX_CHAR('\n', to), LEX_CHAR('\t', to) - #define LEX_INVALID(c) { c, c, LEX_TRANS_INVALID, 0 } - - #define LEX_DEFAULT(to) { 0, 0, LEX_TRANS_DEFAULT, to } \ - } - #define LEX_END { 0, 0, 0, 0 } \ - } - -/* - * Helpers for handling states - */ -#define LEX_STATE_NAME(lex, state) ((state) ? (lex)->state_list[(state) - 1].name : "...") - -/* - * Lex it! - * - * Return zero to indiciate that the input was valid, nonzero otherwise. - */ -int lexer (const struct lex *lex, const char *input, void *arg); - -#endif /* LIB_LEXER_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/lib/signals.c --- a/src/lib/signals.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include - -#include "signals.h" -#include "log.h" - -struct signals { - struct event_base *ev_base; - - struct signal { - struct event *ev; - } sig_list[MAX_SIGNALS]; - - int sig_count; -}; - -void signals_loopexit (int signal, short event, void *arg) { - struct signals *signals = arg; - - INFO("[signal] caught %s: exiting the event loop", strsignal(signal)); - - if (event_base_loopexit(signals->ev_base, NULL)) - FATAL("event_base_loopexit"); -} - -void signals_ignore (int signal, short event, void *arg) { - struct signals *signals = arg; - - (void) signals; - - /* ignore */ -} - -struct signals *signals_alloc (struct event_base *ev_base) { - struct signals *signals = NULL; - - if ((signals = calloc(1, sizeof(*signals))) == NULL) - ERROR("calloc"); - - // simple attributes - signals->ev_base = ev_base; - - // done - return signals; - -error: - return NULL; -} - -int signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *)) { - struct signal *sig_info; - - // find our sig_info - assert(signals->sig_count < MAX_SIGNALS); - sig_info = &signals->sig_list[signals->sig_count++]; - - // set up the libevent signal events - if ((sig_info->ev = signal_new(signals->ev_base, sigval, sig_handler, signals)) == NULL) - PERROR("signal_new"); - - if (signal_add(sig_info->ev, NULL)) - PERROR("signal_add"); - - // success - return 0; - -error: - return -1; -} - -struct signals *signals_default (struct event_base *ev_base) { - struct signals *signals = NULL; - - // alloc signals - if ((signals = signals_alloc(ev_base)) == NULL) - return NULL; - - // add the set of default signals - if ( signals_add(signals, SIGPIPE, &signals_ignore) - || signals_add(signals, SIGINT, &signals_loopexit) - ) ERROR("signals_add"); - - // success - return signals; - -error: - if (signals) - signals_free(signals); - - return NULL; -} - -void signals_free (struct signals *signals) { - int i; - - // free all events - for (i = 0; i < signals->sig_count; i++) { - if (signal_del(signals->sig_list[i].ev)) - PWARNING("signal_del"); - - } - - // free the info itself - free(signals); -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/lib/signals.h --- a/src/lib/signals.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -#ifndef LIB_SIGNAL_H -#define LIB_SIGNAL_H - -/* - * Handle signals in a libevent-sane way - */ - -#include - -/* - * How many signals we can define actions for - */ -#define MAX_SIGNALS 8 - -/* - * info about a set of signals - */ -struct signals; - -/* - * Used as a handler for signals that should cause a loopexit. - */ -void signals_loopexit (int signal, short event, void *arg); - -/* - * Used to receive signals, but discard them. - */ -void signals_ignore (int signal, short event, void *arg); - -/* - * Allocate a signals struct, acting on the given ev_base. - * - * Returns NULL on failure - */ -struct signals *signals_alloc (struct event_base *ev_base); - -/* - * Add a signal to be handled by the given signals struct with the given handler. - */ -int signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *)); - -/* - * Add a set of default signals - * SIGPIPE signals_ignore - * SIGINT signals_loopexit - */ -struct signals *signals_default (struct event_base *ev_base); - -/* - * Free the resources/handlers associated with the given signal handler - */ -void signals_free (struct signals *signals); - -#endif /* LIB_SIGNAL_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/lib/url.c --- a/src/lib/url.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,587 +0,0 @@ -#define _GNU_SOURCE -#include -#include - -#include "url.h" -#include "lex.h" -#include "error.h" -#include "log.h" -#include "misc.h" - -enum url_token { - URL_INVALID, - - URL_BEGIN, - - // kludge to resolve ambiguous URL_SCHEME/URL_USERNAME+URL_PASSWORD/URL_HOSTNAME+URL_SERVICE at the beginning - URL_BEGIN_ALNUM, - URL_BEGIN_COLON, - - URL_SCHEME, - URL_SCHEME_SEP, - URL_SCHEME_END_COL, - URL_SCHEME_END_SLASH1, - URL_SCHEME_END_SLASH2, - - // kludge to resolve ambiguous URL_USERNAME+URL_PASSWORD/URL_HOSTNAME+URL_SERVICE after a scheme - URL_USERHOST_ALNUM, - URL_USERHOST_COLON, - URL_USERHOST_ALNUM2, - - URL_USERNAME, - URL_PASSWORD_SEP, - URL_PASSWORD, - URL_USERNAME_END, - - URL_HOSTNAME, - - URL_SERVICE_SEP, - URL_SERVICE, - - URL_PATH_START, - URL_PATH, - - URL_OPT_START, - URL_OPT_KEY, - URL_OPT_EQ, - URL_OPT_VAL, - URL_OPT_SEP, - - URL_MAX, -}; - -/* - * Parser state - */ -struct url_state { - // the URL to parse into - struct url *url; - - // our lookahead-kludge - const char *alnum, *alnum2; - -}; - -static int _url_append_scheme (struct url *url, const char *data, int copy) { - if (!url->schema) { - if ((url->schema = malloc(sizeof(struct url_schema) + (1 * sizeof(const char *)))) == NULL) - ERROR("malloc"); - - url->schema->count = 1; - - } else { - url->schema->count++; - - // I'm starting to hate flexible array members... - if ((url->schema = realloc(url->schema, sizeof(struct url_schema) + url->schema->count * sizeof(const char *))) == NULL) - ERROR("realloc"); - } - - if ((url->schema->list[url->schema->count - 1] = copy ? strdup(data) : data) == NULL) - ERROR("strdup"); - - // k - return 0; - -error: - return -1; -} - -static struct url_opt *_url_get_opt (struct url *url, int new) { - if (!url->opts) { - if ((url->opts = malloc(sizeof(struct url_opts) + (1 * sizeof(struct url_opt)))) == NULL) - ERROR("malloc"); - - url->opts->count = 1; - - } else if (new) { - url->opts->count++; - - if ((url->opts = realloc(url->opts, sizeof(struct url_opts) + url->opts->count * sizeof(struct url_opt))) == NULL) - ERROR("realloc"); - } - - // success - return &url->opts->list[url->opts->count - 1]; - -error: - return NULL; -} - -static int _url_append_opt_key (struct url *url, const char *key) { - struct url_opt *opt; - - if ((opt = _url_get_opt(url, 1)) == NULL) - goto error; - - if ((opt->key = strdup(key)) == NULL) - ERROR("strdup"); - - opt->value = NULL; - - return 0; - -error: - return -1; -} - -static int _url_append_opt_val (struct url *url, const char *value) { - struct url_opt *opt; - - if ((opt = _url_get_opt(url, 0)) == NULL) - goto error; - - if ((opt->value = strdup(value)) == NULL) - ERROR("strdup"); - - return 0; - -error: - return -1; -} - -static int url_lex_token (int _this_token, char *token_data, int _next_token, int _prev_token, void *arg); - -static struct lex url_lex = { - .token_fn = url_lex_token, - .char_fn = NULL, - .end_fn = NULL, - - .state_count = URL_MAX, - .initial_state = URL_BEGIN, - .state_list = { - LEX_STATE ( URL_BEGIN ) { - LEX_ALNUM ( URL_BEGIN_ALNUM ), - LEX_CHAR ( ':', URL_SERVICE_SEP ), - LEX_CHAR ( '/', URL_PATH_START ), - LEX_CHAR ( '?', URL_OPT_START ), - LEX_END - }, - - // this can be URL_SCHEME, URL_USERNAME or URL_HOSTNAME - LEX_STATE_END ( URL_BEGIN_ALNUM ) { - LEX_CHAR ( '+', URL_SCHEME_SEP ), // it was URL_SCHEME - LEX_CHAR ( ':', URL_BEGIN_COLON ), - LEX_CHAR ( '@', URL_USERNAME_END ), // it was URL_USERNAME - LEX_CHAR ( '/', URL_PATH_START ), // it was URL_HOSTNAME - LEX_CHAR ( '?', URL_OPT_START ), // it was URL_HOSTNAME - LEX_DEFAULT ( URL_BEGIN_ALNUM ) - }, - - // this can be URL_SCHEME_END_COL, URL_USERNAME_END or URL_SERVICE_SEP - LEX_STATE ( URL_BEGIN_COLON ) { - LEX_CHAR ( '/', URL_SCHEME_END_SLASH1 ), // it was URL_SCHEME - LEX_ALNUM ( URL_USERHOST_ALNUM2 ), - LEX_END - }, - - - LEX_STATE ( URL_SCHEME ) { - LEX_ALNUM ( URL_SCHEME ), - LEX_CHAR ( '+', URL_SCHEME_SEP ), - LEX_CHAR ( ':', URL_SCHEME_END_COL ), - LEX_END - }, - - LEX_STATE ( URL_SCHEME_SEP ) { - LEX_ALNUM ( URL_SCHEME ), - LEX_END - }, - - LEX_STATE ( URL_SCHEME_END_COL ) { - LEX_CHAR ( '/', URL_SCHEME_END_SLASH1 ), - LEX_END - }, - - LEX_STATE ( URL_SCHEME_END_SLASH1 ) { - LEX_CHAR ( '/', URL_SCHEME_END_SLASH2 ), - LEX_END - }, - - LEX_STATE_END ( URL_SCHEME_END_SLASH2 ) { - LEX_ALNUM ( URL_USERHOST_ALNUM ), - LEX_CHAR ( ':', URL_SERVICE_SEP ), - LEX_CHAR ( '/', URL_PATH_START ), - LEX_CHAR ( '?', URL_OPT_START ), - LEX_END - }, - - // this can be URL_USERNAME or URL_HOSTNAME - LEX_STATE_END ( URL_USERHOST_ALNUM ) { - LEX_CHAR ( ':', URL_USERHOST_COLON ), - LEX_CHAR ( '@', URL_USERNAME_END ), // it was URL_USERNAME - LEX_CHAR ( '/', URL_PATH_START ), // it was URL_HOSTNAME - LEX_CHAR ( '?', URL_OPT_START ), // it was URL_HOSTNAME - LEX_DEFAULT ( URL_USERHOST_ALNUM ), - }, - - // this can be URL_USERNAME_END or URL_SERVICE_SEP - LEX_STATE ( URL_USERHOST_COLON ) { - LEX_ALNUM ( URL_USERHOST_ALNUM2 ), - LEX_END - }, - - // this can be URL_PASSWORD or URL_SERVICE - LEX_STATE_END ( URL_USERHOST_ALNUM2 ) { - LEX_CHAR ( '@', URL_USERNAME_END ), // it was URL_PASSSWORD - LEX_CHAR ( '/', URL_PATH_START ), // it was URL_SERVICE - LEX_CHAR ( '?', URL_OPT_START ), // it was URL_SERVICE - LEX_DEFAULT ( URL_USERHOST_ALNUM2 ), - }, - - // dummy states, covered by URL_USERHOST_ALNUM/URL_USERHOST_COLON/URL_USERHOST_ALNUM2 - LEX_STATE ( URL_USERNAME ) { - LEX_END - }, - - LEX_STATE ( URL_PASSWORD_SEP ) { - LEX_END - }, - - LEX_STATE ( URL_PASSWORD ) { - LEX_END - }, - - - LEX_STATE_END ( URL_USERNAME_END ) { - LEX_ALNUM ( URL_HOSTNAME ), - LEX_CHAR ( ':', URL_SERVICE_SEP ), - LEX_CHAR ( '/', URL_PATH_START ), - LEX_CHAR ( '?', URL_OPT_START ), - LEX_END - }, - - - LEX_STATE_END ( URL_HOSTNAME ) { - LEX_ALNUM ( URL_HOSTNAME ), - LEX_CHAR ( ':', URL_SERVICE_SEP ), - LEX_CHAR ( '/', URL_PATH_START ), - LEX_CHAR ( '?', URL_OPT_START ), - LEX_END - }, - - - LEX_STATE ( URL_SERVICE_SEP ) { - LEX_ALNUM ( URL_SERVICE ), - LEX_CHAR ( '/', URL_PATH_START ), - LEX_CHAR ( '?', URL_OPT_START ), - LEX_END - }, - - LEX_STATE_END ( URL_SERVICE ) { - LEX_ALNUM ( URL_SERVICE ), - LEX_CHAR ( '/', URL_PATH_START ), - LEX_CHAR ( '?', URL_OPT_START ), - LEX_END - }, - - - LEX_STATE_END ( URL_PATH_START ) { - LEX_CHAR ( '?', URL_OPT_START ), - LEX_DEFAULT ( URL_PATH ), - }, - - LEX_STATE_END ( URL_PATH ) { - LEX_CHAR ( '?', URL_OPT_START ), - LEX_DEFAULT ( URL_PATH ), - }, - - - LEX_STATE_END ( URL_OPT_START ) { - LEX_CHAR ( '&', URL_OPT_SEP ), - LEX_INVALID ( '=' ), - LEX_DEFAULT ( URL_OPT_KEY ), - }, - - LEX_STATE_END ( URL_OPT_KEY ) { - LEX_CHAR ( '&', URL_OPT_SEP ), - LEX_CHAR ( '=', URL_OPT_EQ ), - LEX_DEFAULT ( URL_OPT_KEY ), - }, - - LEX_STATE_END ( URL_OPT_EQ ) { - LEX_CHAR ( '&', URL_OPT_SEP ), - LEX_INVALID ( '=' ), - LEX_DEFAULT ( URL_OPT_VAL ), - }, - - LEX_STATE_END ( URL_OPT_VAL ) { - LEX_CHAR ( '&', URL_OPT_SEP ), - LEX_INVALID ( '=' ), - LEX_DEFAULT ( URL_OPT_VAL ), - }, - - LEX_STATE_END ( URL_OPT_SEP ) { - LEX_CHAR ( '&', URL_OPT_SEP ), - LEX_INVALID ( '=' ), - LEX_DEFAULT ( URL_OPT_KEY ), - }, - - LEX_STATE ( URL_ERROR ) { - LEX_END - }, - } -}; - -static int url_lex_token (int _this_token, char *token_data, int _next_token, int _prev_token, void *arg) { - enum url_token this_token = _this_token, next_token = _next_token, prev_token = _prev_token; - struct url_state *state = arg; - const char **copy_to = NULL; - - (void) prev_token; - - switch (this_token) { - case URL_BEGIN: - // irrelevant - break; - - case URL_BEGIN_ALNUM: - switch (next_token) { - case URL_SCHEME_SEP: - // store the scheme - if (_url_append_scheme(state->url, token_data, 1)) - goto error; - - break; - - case URL_USERNAME_END: - // store the username - copy_to = &state->url->username; break; - - case URL_PATH_START: - case URL_OPT_START: - case LEX_EOF: - // store the hostname - copy_to = &state->url->hostname; break; - - case URL_BEGIN_COLON: - // gah... - copy_to = &state->alnum; break; - - - default: - FATAL("weird next token"); - } - - break; - - case URL_BEGIN_COLON: - switch (next_token) { - case URL_SCHEME_END_SLASH1: - // store the schema - if (_url_append_scheme(state->url, state->alnum, 0)) - goto error; - - state->alnum = NULL; - - break; - - case URL_USERHOST_ALNUM2: - // gah.. - break; - - default: - FATAL("weird next token"); - } - - break; - - case URL_SCHEME: - // store the scheme - if (_url_append_scheme(state->url, token_data, 1)) - goto error; - - break; - - case URL_SCHEME_SEP: - // ignore - break; - - case URL_SCHEME_END_COL: - case URL_SCHEME_END_SLASH1: - case URL_SCHEME_END_SLASH2: - // ignore - break; - - case URL_USERHOST_ALNUM: - switch (next_token) { - case URL_USERNAME_END: - // store the username - copy_to = &state->url->username; break; - - case URL_PATH_START: - case URL_OPT_START: - case LEX_EOF: - // store the hostname - copy_to = &state->url->hostname; break; - - case URL_USERHOST_COLON: - // gah... - copy_to = &state->alnum; break; - - default: - FATAL("weird next token"); - } - - break; - - case URL_USERHOST_COLON: - // ignore - break; - - case URL_USERHOST_ALNUM2: - switch (next_token) { - case URL_USERNAME_END: - // store the username and password - state->url->username = state->alnum; state->alnum = NULL; - copy_to = &state->url->password; - - break; - - case URL_PATH_START: - case URL_OPT_START: - case LEX_EOF: - // store the hostname and service - state->url->hostname = state->alnum; state->alnum = NULL; - copy_to = &state->url->service; break; - - default: - FATAL("weird next token"); - } - - break; - - case URL_USERNAME: - case URL_PASSWORD_SEP: - case URL_PASSWORD: - FATAL("these should be overshadowed"); - - case URL_USERNAME_END: - // ignore - break; - - case URL_HOSTNAME: - // store - copy_to = &state->url->hostname; break; - - case URL_SERVICE_SEP: - // ignore - break; - - case URL_SERVICE: - // store - copy_to = &state->url->service; break; - - case URL_PATH_START: - // ignore - break; - - case URL_PATH: - // store - copy_to = &state->url->path; break; - - case URL_OPT_START: - // ignore - break; - - case URL_OPT_KEY: - // store - if (_url_append_opt_key(state->url, token_data)) - goto error; - - break; - - case URL_OPT_EQ: - // ignore - break; - - case URL_OPT_VAL: - // store - if (_url_append_opt_val(state->url, token_data)) - goto error; - - break; - - case URL_OPT_SEP: - // ignore - break; - - default: - ERROR("invalid token"); - } - - if (copy_to) { - // copy the token data - if ((*copy_to = strdup(token_data)) == NULL) - ERROR("strdup"); - } - - // good - return 0; - -error: - DEBUG("token: %s -> %s -> %s: %s", - LEX_STATE_NAME(&url_lex, prev_token), LEX_STATE_NAME(&url_lex, this_token), LEX_STATE_NAME(&url_lex, next_token), - token_data - ); - return -1; -} - - -int url_parse (struct url *url, const char *text) { - struct url_state state; ZINIT(state); - int ret; - - // set up state - state.url = url; - - // parse it - if ((ret = lexer(&url_lex, text, &state))) - ERROR("invalid URL"); - - // success - return 0; - -error: - return -1; -} - -static void _url_dump_part (const char *field, const char *val, FILE *stream) { - if (val) { - fprintf(stream, "%s=%s ", field, val); - } -} - -void url_dump (const struct url *url, FILE *stream) { - int i; - - if (url->schema) { - fprintf(stream, "schema=("); - - for (i = 0; i < url->schema->count; i++) { - if (i > 0) - fprintf(stream, ","); - - fprintf(stream, "%s", url->schema->list[i]); - } - - fprintf(stream, ") "); - } - - _url_dump_part("username", url->username, stream); - _url_dump_part("password", url->password, stream); - _url_dump_part("hostname", url->hostname, stream); - _url_dump_part("service", url->service, stream); - _url_dump_part("path", url->path, stream); - - if (url->opts) { - fprintf(stream, "opts: "); - - for (i = 0; i < url->opts->count; i++) { - fprintf(stream, "%s=%s ", url->opts->list[i].key, url->opts->list[i].value); - } - } - - fprintf(stream, "\n"); -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/lib/url.h --- a/src/lib/url.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -#ifndef LIB_URL_H -#define LIB_URL_H - -/* - * A trivial parser for simple URLs - * - * [ [ "+" [ ... ] ] "://" ] [ [ ":" ] "@" ] [  ] [ ":" ] [ "/" ] [ "?" [ [ "=" ] ] [ "&" [ [ "=" ] ] [ ... ] ] - * - * example.com - * tcp://example.com:7348/ - * psql://postgres@localhost/test_db?charset=utf8 - * - */ - -#include -#include - -/* - * The schema - */ -struct url_schema { - size_t count; - const char *list[]; -}; - -/* - * The options at the end - */ -struct url_opts { - size_t count; - struct url_opt { - const char *key; - const char *value; - } list[]; -}; - -/* - * A parsed URL - */ -struct url { - struct url_schema *schema; - const char *username; - const char *password; - const char *hostname; - const char *service; - const char *path; - struct url_opts *opts; -}; - -/* - * Parse the given `text` as an URL, returning the result in `url`. Optional fields that are missing in the text will - * cause those values to be returned unmodified. - * - * Returns zero if the url was valid and was parsed, nonzero if it was invalid. - */ -int url_parse (struct url *url, const char *text); - -/* - * Prints a url in a debug-output format. - */ -void url_dump (const struct url *url, FILE *stream); - -#endif /* LIB_URL_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/simple.c --- a/src/simple.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -#include -#include -#include -#include - -#include "simple.h" -#include "dirbuf.h" -#include "lib/log.h" -#include "lib/math.h" -#include "lib/misc.h" - -struct simple_fs { - const struct simple_node *inode_table; - - size_t inode_count; -}; - -/* - * Used for stat/entry timeouts... not sure how this should really be set. - */ -#define CACHE_TIMEOUT 1.0 - -static void _simple_stat (struct stat *stat, const struct simple_node *node) { - stat->st_ino = node->inode; - stat->st_mode = node->mode_type | node->mode_perm; - stat->st_nlink = 1; - stat->st_size = node->data ? strlen(node->data) : 0; -} - -/* - * Fetch the simple_node for the given inode. - * - * Returns NULL for invalid inodes. - */ -static const struct simple_node *_simple_get_ino (struct simple_fs *fs, fuse_ino_t ino) { - // make sure it's a valid inode - if (ino < 1 || ino > fs->inode_count) { - WARNING("invalid inode=%zu", ino); - return NULL; - } - - // return the node - return fs->inode_table + (ino - 1); -} - -static void simple_lookup (fuse_req_t req, fuse_ino_t parent, const char *name) { - struct simple_fs *fs = fuse_req_userdata(req); - const struct simple_node *node; - struct fuse_entry_param e; ZINIT(e); - int err; - - INFO("[simple.lookup %p] parent=%lu, name=`%s'", fs, parent, name); - - // find the matching node - for (node = fs->inode_table; node->inode > 0; node++) { - if (node->parent == parent && strcmp(node->name, name) == 0) - break; - - } - - // did we find it? - if (node->inode) { - // set up the entry - e.ino = node->inode; - e.generation = 0x01; - _simple_stat(&e.attr, node); - e.attr_timeout = CACHE_TIMEOUT; - e.entry_timeout = CACHE_TIMEOUT; - - // reply - if ((err = fuse_reply_entry(req, &e))) - EERROR(err, "fuse_reply_entry"); - - } else { - // not found - err = ENOENT; - goto error; - } - - // success - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} - -static void simple_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct simple_fs *fs = fuse_req_userdata(req); - const struct simple_node *node; - struct stat stbuf; ZINIT(stbuf); - int err; - - INFO("[simple.getattr %p] ino=%lu", fs, ino); - - // look up the node - if ((node = _simple_get_ino(fs, ino)) == NULL) - EERROR(err = EINVAL, "bad inode"); - - // set up the stbuf - _simple_stat(&stbuf, node); - - // reply - if ((err = fuse_reply_attr(req, &stbuf, CACHE_TIMEOUT))) - EERROR(err, "fuse_reply_attr"); - - // suceccss - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} - -static void simple_readlink (fuse_req_t req, fuse_ino_t ino) { - struct simple_fs *fs = fuse_req_userdata(req); - const struct simple_node *node; - int err; - - INFO("[simple.readlink %p] ino=%lu", fs, ino); - - // look up the node - if ((node = _simple_get_ino(fs, ino)) == NULL) - EERROR(err = EINVAL, "bad inode"); - - // check that it's a symlink - if (node->mode_type != S_IFLNK) - EERROR(err = EINVAL, "bad mode"); - - // return the contents - if ((err = fuse_reply_readlink(req, node->data))) - EERROR(err, "fuse_reply_readlink"); - - // suceccss - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); - -} - -static void simple_readdir (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { - struct simple_fs *fs = fuse_req_userdata(req); - const struct simple_node *dir_node, *node; - struct dirbuf buf; - int err; - - INFO("[simple.readdir] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi); - - // look up the inode - if ((dir_node = _simple_get_ino(fs, ino)) == NULL) - EERROR(err = EINVAL, "bad inode"); - - // check that it's a dir - if (dir_node->mode_type != S_IFDIR) - EERROR(err = ENOTDIR, "bad mode"); - - // fill in the dirbuf - 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, &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"); - - // look up all child nodes - for (node = fs->inode_table; node->inode; node++) { - // skip non-children - if (node->parent != dir_node->inode) - continue; - - // child node offsets are just inode + 2 - 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 - if (err > 0) - break; - } - - // send it - if ((err = -dirbuf_done(req, &buf))) - EERROR(err, "failed to send buf"); - - // success - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} - -static void simple_read (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { - struct simple_fs *fs = fuse_req_userdata(req); - const struct simple_node *node; - int err ; - - // fi is unused - (void) fi; - - INFO("[simple.read] ino=%lu, size=%zu, off=%zu, fi=%p", ino, size, off, fi); - - // look up the inode - if ((node = _simple_get_ino(fs, ino)) == NULL) - EERROR(err = EINVAL, "bad inode"); - - // check that it's a dir - if (node->mode_type != S_IFREG) - EERROR(err = (node->mode_type == S_IFDIR ? EISDIR : EINVAL), "bad mode"); - - // seek past EOF? - if (off >= strlen(node->data)) { - // offset is out-of-file, so return EOF - if ((err = fuse_reply_buf(req, NULL, 0))) - EERROR(err, "fuse_reply_buf size=0"); - - } else { - // reply with the requested file data - if ((err = fuse_reply_buf(req, node->data + off, MIN(strlen(node->data) - off, size)))) - EERROR(err, "fuse_reply_buf buf=%p + %zu, size=MIN(%zu, %zu)", node->data, off, strlen(node->data) - off, size); - } - - // success - return; - -error: - if ((err = fuse_reply_err(req, err))) - EWARNING(err, "fuse_reply_err"); -} - - -/* - * Define our fuse_lowlevel_ops struct. - */ -static struct fuse_lowlevel_ops simple_ops = { - .lookup = simple_lookup, - - .getattr = simple_getattr, - - .readlink = simple_readlink, - - .readdir = simple_readdir, - - .read = simple_read, -}; - -struct fuse_lowlevel_ops *simple_init () { - return &simple_ops; -} - -struct simple_fs *simple_new (const struct simple_node *node_list) { - struct simple_fs *fs = NULL; - const struct simple_node *node; - - // generate - if ((fs = calloc(1, sizeof(*fs))) == NULL) - ERROR("calloc"); - - // remember node_list - fs->inode_count = 0; - fs->inode_table = node_list; - - // validate it - for (node = fs->inode_table; node->inode; node++) { - // update inode_count - fs->inode_count++; - - // check that parent is valid - assert(node->inode == fs->inode_count); - assert(node->parent < node->inode); - } - - // success - return fs; - -error: - return NULL; -} diff -r 40a3b13ffc9d -r 9dfc861273e5 src/simple.h --- a/src/simple.h Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -#ifndef SIMPLE_H -#define SIMPLE_H - -/* - * A simple static in-memory filesystem structure. - */ - -#include "evfuse.h" - -/* - * A simple file/dir. - */ -struct simple_node { - // inode number - fuse_ino_t inode; - - // mode - mode_t mode_type; - mode_t mode_perm; - - // parent node - fuse_ino_t parent; - - // name - const char *name; - - // data - const char *data; -}; - -/* - * General information. - */ -struct simple_fs; - -/* - * Initialize simple, and get the fuse_lowlevel_ops. - */ -struct fuse_lowlevel_ops *simple_init (); - -/* - * Create a new simple_fs. - */ -struct simple_fs *simple_new (const struct simple_node *node_list); - -#endif /* SIMPLE_H */ diff -r 40a3b13ffc9d -r 9dfc861273e5 src/simple_hello.c --- a/src/simple_hello.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ - -#include - -#include "lib/log.h" -#include "lib/signals.h" -#include "evfuse.h" -#include "simple.h" - -static struct hello { - struct event_base *ev_base; - - struct signals *signals; - - struct simple_fs *fs; - - struct evfuse *ev_fuse; - -} ctx; - -static struct simple_node node_list[] = { - { 1, S_IFDIR, 0555, 0, NULL, NULL }, - { 2, S_IFREG, 0444, 1, "hello", "Hello World!\n" }, - { 3, S_IFREG, 0444, 1, "foo", "Foo\n" }, - { 4, S_IFREG, 0444, 1, "bar", "Bar\n" }, - { 5, S_IFDIR, 0555, 1, "test", NULL }, - { 6, S_IFREG, 0444, 5, "file0", "data0\n" }, - { 7, S_IFREG, 0444, 5, "file1", "data1\n" }, - { 8, S_IFLNK, 0444, 1, "lnk0", "test/file0" }, - { 0, 0, 0, 0, NULL, NULL }, -}; - -int main (int argc, char **argv) { - struct fuse_args fuse_args = FUSE_ARGS_INIT(argc, argv); - - // init libevent - if ((ctx.ev_base = event_base_new()) == NULL) - ERROR("event_base_new"); - - // setup signals - if ((ctx.signals = signals_default(ctx.ev_base)) == NULL) - ERROR("signals_default"); - - // setup fs - if ((ctx.fs = simple_new(node_list)) == NULL) - ERROR("simple_new"); - - // open fuse - if ((ctx.ev_fuse = evfuse_new(ctx.ev_base, &fuse_args, simple_init(), ctx.fs)) == NULL) - ERROR("evfuse_new"); - - // run libevent - INFO("running libevent loop"); - - if (event_base_dispatch(ctx.ev_base)) - PERROR("event_base_dispatch"); - - // clean shutdown - -error : - // cleanup - if (ctx.ev_fuse) - evfuse_free(ctx.ev_fuse); - -/* - if (ctx.fs) - simple_close(ctx.fs); -*/ - - if (ctx.signals) - signals_free(ctx.signals); - - if (ctx.ev_base) - event_base_free(ctx.ev_base); - - fuse_opt_free_args(&fuse_args); -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/url_test.c --- a/src/url_test.c Tue Nov 18 02:06:52 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,198 +0,0 @@ - -#include -#include -#include - -#include "lib/url.h" - -#define FAIL(...) do { printf("FAIL: "); printf(__VA_ARGS__); printf("\n"); return -1; } while (0) - -struct url_schema - basic_http = { 1, { "http" } }, - svn_ssh = { 2, { "svn", "ssh" } }, - schema_unix = { 1, { "unix" } } - ; - -struct url_opts - opts_single = { 1, { { "key0", "val0" } } }, - opts_multi = { 2, { { "key0", "val0" }, { "key1", "val1" } } }, - opts_nullval = { 1, { { "keyN", NULL } } } - ; - -struct url_test { - const char *url; - const struct url expected; -} url_tests[] = { - { "localhost:http", { - NULL, NULL, NULL, "localhost", "http", NULL, NULL - } }, - - { "http://example.com/path", { - &basic_http, NULL, NULL, "example.com", NULL, "path", NULL - } }, - - { "svn+ssh://user:passwd@someplace:someport/something", { - &svn_ssh, "user", "passwd", "someplace", "someport", "something" - } }, - - { "user@:service/", { - NULL, "user", NULL, NULL, "service", NULL - } }, - - { "unix:////tmp/foo.sock", { - &schema_unix, NULL, NULL, NULL, NULL, "/tmp/foo.sock" - } }, - - { "unix:///tmp/foo.sock", { - &schema_unix, NULL, NULL, NULL, NULL, "tmp/foo.sock" - } }, - - { "/tmp/foo.sock", { - NULL, NULL, NULL, NULL, NULL, "tmp/foo.sock" - } }, - - { "?key0=val0", { - NULL, NULL, NULL, NULL, NULL, NULL, &opts_single - } }, - - { "http://foo.com/index.php?key0=val0&key1=val1", { - &basic_http, NULL, NULL, "foo.com", NULL, "index.php", &opts_multi - } }, - - { "example.org:81/?keyN", { - NULL, NULL, NULL, "example.org", "81", NULL, &opts_nullval - } }, - - { NULL, { } }, -}; - -int cmp_url_str (const char *field, const char *test, const char *real) { - if (!test) { - if (real) - FAIL("%s shouldn't be present", field); - - } else if (!real) { - FAIL("%s is missing", field); - - } else { - if (strcmp(test, real) != 0) - FAIL("%s differs: %s -> %s", field, test, real); - } - - // ok - return 0; -} - -int cmp_url (const struct url *test, const struct url *real) { - int i; - - // test schema - if (!test->schema) { - if (real->schema) - FAIL("test has no schema, but real does"); - - } else if (!real->schema) { - FAIL("test has a schema, but real doesn't"); - - } else { - if (test->schema->count != test->schema->count) - FAIL("inconsistent scheme count"); - - for (i = 0; i < test->schema->count; i++) { - if (strcmp(test->schema->list[i], real->schema->list[i]) != 0) - FAIL("differing scheme #%d", i); - } - } - - // test username - if (cmp_url_str("username", test->username, real->username)) - goto error; - - // test password - if (cmp_url_str("password", test->password, real->password)) - goto error; - - // test hostname - if (cmp_url_str("hostname", test->hostname, real->hostname)) - goto error; - - // test service - if (cmp_url_str("service", test->service, real->service)) - goto error; - - // test path - if (cmp_url_str("path", test->path, real->path)) - goto error; - - // test query - if (!test->opts) { - if (real->opts) - FAIL("test has no opts, but real does"); - - } else if (!real->opts) { - FAIL("test has opts, but real doesn't"); - - } else { - if (test->opts->count != test->opts->count) - FAIL("inconsistent opts count"); - - for (i = 0; i < test->opts->count; i++) { - if (cmp_url_str("opt key", test->opts->list[i].key, real->opts->list[i].key)) - FAIL("differing opt key #%d", i); - - if (cmp_url_str("opt value", test->opts->list[i].value, real->opts->list[i].value)) - FAIL("differing opt value #%d", i); - } - } - - // ok - return 0; - -error: - return -1; -} - -void usage (const char *exec_name) { - printf("Usage: %s\n\n\tNo arguments are accepted\n", exec_name); - - exit(EXIT_FAILURE); -} - -int main (int argc, char **argv) { - const struct url_test *test; - struct url url; - - if (argc > 1) - usage(argv[0]); - - // run the tests - for (test = url_tests; test->url; test++) { - // first output the URL we are handling... - printf("%-80s - ", test->url); - fflush(stdout); - - // parse the URL - memset(&url, 0, sizeof(url)); - - if (url_parse(&url, test->url)) { - printf("FATAL: url_parse failed\n"); - return EXIT_FAILURE; - } - - // compare it - if (cmp_url(&test->expected, &url)) { - printf("\texpected: "); - url_dump(&test->expected, stdout); - - printf("\tresult: "); - url_dump(&url, stdout); - - } else { - printf("OK\n\t"); - url_dump(&url, stdout); - } - - printf("\n"); - } -} - diff -r 40a3b13ffc9d -r 9dfc861273e5 src/util.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/util.c Sun Mar 08 00:19:12 2009 +0200 @@ -0,0 +1,331 @@ +#include +#include + +#include "internal.h" +#include "lib/log.h" +#include "lib/misc.h" + +#define _PARAM_TYPE_CASE(typenam) case EVSQL_PARAM_ ## typenam: return #typenam + +#define _PARAM_VAL_BUF_MAX 120 +#define _PARAM_VAL_CASE(typenam, ...) case EVSQL_PARAM_ ## typenam: if (param->data_raw) ret = snprintf(buf, _PARAM_VAL_BUF_MAX, __VA_ARGS__); else return "(null)"; break + +const char *evsql_param_type (const struct evsql_query_param *param) { + switch (param->type) { + _PARAM_TYPE_CASE (INVALID ); + _PARAM_TYPE_CASE (NULL_ ); + _PARAM_TYPE_CASE (BINARY ); + _PARAM_TYPE_CASE (STRING ); + _PARAM_TYPE_CASE (UINT16 ); + _PARAM_TYPE_CASE (UINT32 ); + _PARAM_TYPE_CASE (UINT64 ); + default: return "???"; + } +} + + +static const char *evsql_param_val (const struct evsql_query_param *param) { + static char buf[_PARAM_VAL_BUF_MAX]; + int ret; + + switch (param->type) { + _PARAM_VAL_CASE (INVALID, "???" ); + _PARAM_VAL_CASE (NULL_, "(null)" ); + _PARAM_VAL_CASE (BINARY, "%zu:%s", param->length, "..." ); + _PARAM_VAL_CASE (STRING, "%s", param->data_raw ); + _PARAM_VAL_CASE (UINT16, "%hu", (unsigned short int) ntohs(param->data.uint16) ); + _PARAM_VAL_CASE (UINT32, "%lu", (unsigned long int) ntohl(param->data.uint32) ); + _PARAM_VAL_CASE (UINT64, "%llu", (unsigned long long int) ntohq(param->data.uint64) ); + default: return "???"; + } + + return buf; +} + +int evsql_params_clear (struct evsql_query_params *params) { + struct evsql_query_param *param; + + for (param = params->list; param->type; param++) + param->data_raw = NULL; + + return 0; +} + +int evsql_param_null (struct evsql_query_params *params, size_t param) { + struct evsql_query_param *p = ¶ms->list[param]; + + p->data_raw = NULL; + + return 0; +} + +int evsql_param_binary (struct evsql_query_params *params, size_t param, const char *ptr, size_t len) { + struct evsql_query_param *p = ¶ms->list[param]; + + assert(p->type == EVSQL_PARAM_BINARY); + + p->data_raw = ptr; + p->length = len; + + return 0; +} + +int evsql_param_string (struct evsql_query_params *params, size_t param, const char *ptr) { + struct evsql_query_param *p = ¶ms->list[param]; + + assert(p->type == EVSQL_PARAM_STRING); + + p->data_raw = ptr; + p->length = 0; + + return 0; +} + +int evsql_param_uint16 (struct evsql_query_params *params, size_t param, uint16_t uval) { + struct evsql_query_param *p = ¶ms->list[param]; + + assert(p->type == EVSQL_PARAM_UINT16); + + p->data.uint16 = htons(uval); + p->data_raw = (const char *) &p->data.uint16; + p->length = sizeof(uval); + + return 0; +} + +int evsql_param_uint32 (struct evsql_query_params *params, size_t param, uint32_t uval) { + struct evsql_query_param *p = ¶ms->list[param]; + + assert(p->type == EVSQL_PARAM_UINT32); + + p->data.uint32 = htonl(uval); + p->data_raw = (const char *) &p->data.uint32; + p->length = sizeof(uval); + + return 0; +} + +void evsql_query_debug (const char *sql, const struct evsql_query_params *params) { + const struct evsql_query_param *param; + size_t param_count = 0, idx = 0; + + // count the params + for (param = params->list; param->type; param++) + param_count++; + + DEBUG("sql: %s", sql); + DEBUG("params: %zu", param_count); + + for (param = params->list; param->type; param++) { + DEBUG("\t%2zu : %8s = %s", ++idx, evsql_param_type(param), evsql_param_val(param)); + } +} + +const char *evsql_result_error (const struct evsql_result_info *res) { + if (!res->error) + return "No error"; + + switch (res->evsql->type) { + case EVSQL_EVPQ: + if (!res->result.pq) + return "unknown error (no result)"; + + return PQresultErrorMessage(res->result.pq); + + default: + FATAL("res->evsql->type"); + } + +} + +size_t evsql_result_rows (const struct evsql_result_info *res) { + switch (res->evsql->type) { + case EVSQL_EVPQ: + return PQntuples(res->result.pq); + + default: + FATAL("res->evsql->type"); + } +} + +size_t evsql_result_cols (const struct evsql_result_info *res) { + switch (res->evsql->type) { + case EVSQL_EVPQ: + return PQnfields(res->result.pq); + + default: + FATAL("res->evsql->type"); + } +} + +size_t evsql_result_affected (const struct evsql_result_info *res) { + switch (res->evsql->type) { + case EVSQL_EVPQ: + return strtol(PQcmdTuples(res->result.pq), NULL, 10); + + default: + FATAL("res->evsql->type"); + } +} + +int evsql_result_binary (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t *size, int nullok) { + *ptr = NULL; + + switch (res->evsql->type) { + case EVSQL_EVPQ: + if (PQgetisnull(res->result.pq, row, col)) { + if (nullok) + return 0; + else + ERROR("[%zu:%zu] field is null", row, col); + } + + if (PQfformat(res->result.pq, col) != 1) + ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col)); + + *size = PQgetlength(res->result.pq, row, col); + *ptr = PQgetvalue(res->result.pq, row, col); + + return 0; + + default: + FATAL("res->evsql->type"); + } + +error: + return -1; +} + +int evsql_result_binlen (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, size_t size, int nullok) { + size_t real_size = 0; + + if (evsql_result_binary(res, row, col, ptr, &real_size, nullok)) + goto error; + + if (*ptr == NULL) { + assert(nullok); + return 0; + } + + if (size && real_size != size) + ERROR("[%zu:%zu] field size mismatch: %zu -> %zu", row, col, size, real_size); + + return 0; + +error: + return -1; +} + +int evsql_result_string (const struct evsql_result_info *res, size_t row, size_t col, const char **ptr, int nullok) { + size_t real_size; + + if (evsql_result_binary(res, row, col, ptr, &real_size, nullok)) + goto error; + + assert(real_size == strlen(*ptr)); + + return 0; + +error: + return -1; +} + +int evsql_result_uint16 (const struct evsql_result_info *res, size_t row, size_t col, uint16_t *uval, int nullok) { + const char *data; + int16_t sval; + + if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok)) + goto error; + + if (!data) + return 0; + + sval = ntohs(*((int16_t *) data)); + + if (sval < 0) + ERROR("negative value for unsigned: %d", sval); + + *uval = sval; + + return 0; + +error: + return nullok ? 0 : -1; +} + +int evsql_result_uint32 (const struct evsql_result_info *res, size_t row, size_t col, uint32_t *uval, int nullok) { + const char *data; + int32_t sval; + + if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok)) + goto error; + + if (!data) + return 0; + + sval = ntohl(*(int32_t *) data); + + if (sval < 0) + ERROR("negative value for unsigned: %d", sval); + + *uval = sval; + + return 0; + +error: + return nullok ? 0 : -1; +} + +int evsql_result_uint64 (const struct evsql_result_info *res, size_t row, size_t col, uint64_t *uval, int nullok) { + const char *data; + int64_t sval; + + if (evsql_result_binlen(res, row, col, &data, sizeof(*uval), nullok)) + goto error; + + if (!data) + return 0; + + sval = ntohq(*(int64_t *) data); + + if (sval < 0) + ERROR("negative value for unsigned: %ld", sval); + + *uval = sval; + + return 0; + +error: + return nullok ? 0 : -1; +} + +void evsql_result_free (const struct evsql_result_info *res) { + switch (res->evsql->type) { + case EVSQL_EVPQ: + return PQclear(res->result.pq); + + default: + FATAL("res->evsql->type"); + } +} + +const char *evsql_conn_error (struct evsql_conn *conn) { + switch (conn->evsql->type) { + case EVSQL_EVPQ: + if (!conn->engine.evpq) + return "unknown error (no conn)"; + + return evpq_error_message(conn->engine.evpq); + + default: + FATAL("res->evsql->type"); + } +} + +const char *evsql_trans_error (struct evsql_trans *trans) { + if (trans->conn == NULL) + return "unknown error (no trans conn)"; + + return evsql_conn_error(trans->conn); +} +