# HG changeset patch # User Tero Marttila # Date 1224258785 -10800 # Node ID c71f3053c7148ee1afc9f275dd5b42ed7cd0197f # Parent 90e14e0df133e8f076b8c615c2ca9e5d1eb35cb0 working symlink diff -r 90e14e0df133 -r c71f3053c714 Makefile --- a/Makefile Fri Oct 17 16:09:35 2008 +0300 +++ b/Makefile Fri Oct 17 18:53:05 2008 +0300 @@ -20,7 +20,7 @@ # complex modules EVSQL_OBJS = obj/evsql/evsql.o obj/evsql/util.o obj/evpq.o -DBFS_OBJS = obj/dbfs/dbfs.o obj/dbfs/common.o obj/dbfs/core.o obj/dbfs/op_base.o obj/dbfs/dirop.o obj/dirbuf.o obj/dbfs/fileop.o obj/dbfs/attr.o obj/dbfs/link.o +DBFS_OBJS = obj/dbfs/dbfs.o obj/dbfs/common.o obj/dbfs/core.o obj/dbfs/op_base.o obj/dbfs/trans.o obj/dbfs/dirop.o obj/dirbuf.o obj/dbfs/fileop.o obj/dbfs/attr.o obj/dbfs/link.o # first target all: ${BIN_PATHS} diff -r 90e14e0df133 -r c71f3053c714 doc/fuse_db.sql --- a/doc/fuse_db.sql Fri Oct 17 16:09:35 2008 +0300 +++ b/doc/fuse_db.sql Fri Oct 17 18:53:05 2008 +0300 @@ -25,4 +25,20 @@ CREATE FUNCTION lo_pwrite (IN fd int4, IN buf bytea, IN "off" int4) RETURNS int4 LANGUAGE SQL STRICT AS 'select lo_lseek($1, $3, 0); select lowrite($1, $2);'; CREATE FUNCTION lo_otruncate (IN oid, IN len int4) RETURNS oid LANGUAGE SQL STRICT AS 'select lo_truncate(lo_open($1, 393216), $2); select $1;'; -ALTER TABLE inodes ADD COLUMN symlink varchar(512); +ALTER TABLE inodes ADD COLUMN link varchar(512); + +CREATE SEQUENCE ino_seq START 64; +ALTER TABLE inodes ALTER COLUMN ino SET DEFAULT nextval('ino_seq'::regclass); + +ALTER TABLE file_tree ADD COLUMN ino int4; +UPDATE file_tree SET ino = inode; +ALTER TABLE file_tree DROP COLUMN inode; + +CREATE FUNCTION dbfs_size (type char, oid, link varchar) RETURNS int4 LANGUAGE SQL STABLE AS $$ + SELECT CASE $1 + WHEN 'LNK' THEN char_length($3) + WHEN 'REG' THEN lo_size($2) + ELSE 0 + END; +$$; + diff -r 90e14e0df133 -r c71f3053c714 src/dbfs/attr.c --- a/src/dbfs/attr.c Fri Oct 17 16:09:35 2008 +0300 +++ b/src/dbfs/attr.c Fri Oct 17 18:53:05 2008 +0300 @@ -60,8 +60,7 @@ "SELECT" " inodes.ino, " DBFS_STAT_COLS " FROM inodes" - " WHERE inodes.ino = $1::int4" - " GROUP BY inodes.ino, inodes.type, inodes.mode, data"; + " WHERE inodes.ino = $1::int4"; static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { EVSQL_PARAM ( UINT32 ), @@ -137,7 +136,7 @@ "UPDATE inodes SET" " %s%s%s%s ino = ino" " WHERE inodes.ino = $5::int4" - " RETURNING inodes.ino, " DBFS_STAT_COLS_NOAGGREGATE, + " 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"), diff -r 90e14e0df133 -r c71f3053c714 src/dbfs/core.c --- a/src/dbfs/core.c Fri Oct 17 16:09:35 2008 +0300 +++ b/src/dbfs/core.c Fri Oct 17 18:53:05 2008 +0300 @@ -57,9 +57,8 @@ const char *sql = "SELECT" " inodes.ino, " DBFS_STAT_COLS - " FROM file_tree INNER JOIN inodes ON (file_tree.inode = inodes.ino)" - " WHERE file_tree.parent = $1::int4 AND file_tree.name = $2::varchar" - " GROUP BY inodes.ino, inodes.type, inodes.mode, data"; + " 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 ), diff -r 90e14e0df133 -r c71f3053c714 src/dbfs/dbfs.c --- a/src/dbfs/dbfs.c Fri Oct 17 16:09:35 2008 +0300 +++ b/src/dbfs/dbfs.c Fri Oct 17 18:53:05 2008 +0300 @@ -16,6 +16,8 @@ .setattr = dbfs_setattr, .readlink = dbfs_readlink, + .symlink = dbfs_symlink, + .open = dbfs_open, .read = dbfs_read, .write = dbfs_write, diff -r 90e14e0df133 -r c71f3053c714 src/dbfs/dbfs.h --- a/src/dbfs/dbfs.h Fri Oct 17 16:09:35 2008 +0300 +++ b/src/dbfs/dbfs.h Fri Oct 17 18:53:05 2008 +0300 @@ -14,8 +14,6 @@ * Structs and functions shared between all dbfs components */ -#define SERROR(val) do { (val); goto error; } while(0) - struct dbfs { struct event_base *ev_base; @@ -29,8 +27,7 @@ #define CACHE_TIMEOUT 1.0 // columns used for stat_info -#define DBFS_STAT_COLS " inodes.type, inodes.mode, lo_size(data), count(*) " -#define DBFS_STAT_COLS_NOAGGREGATE " inodes.type, inodes.mode, lo_size(data), NULL " +#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. diff -r 90e14e0df133 -r c71f3053c714 src/dbfs/dirop.c --- a/src/dbfs/dirop.c Fri Oct 17 16:09:35 2008 +0300 +++ b/src/dbfs/dirop.c Fri Oct 17 18:53:05 2008 +0300 @@ -94,8 +94,8 @@ const char *sql = "SELECT" " file_tree.parent, inodes.type" - " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.inode = inodes.ino)" - " WHERE file_tree.inode = $1::int4"; + " 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 ), @@ -255,7 +255,7 @@ const char *sql = "SELECT" " file_tree.\"offset\", file_tree.name, inodes.ino, inodes.type" - " FROM file_tree LEFT OUTER JOIN inodes ON (file_tree.inode = inodes.ino)" + " 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" " LIMIT $3::int4"; diff -r 90e14e0df133 -r c71f3053c714 src/dbfs/link.c --- a/src/dbfs/link.c Fri Oct 17 16:09:35 2008 +0300 +++ b/src/dbfs/link.c Fri Oct 17 18:53:05 2008 +0300 @@ -1,4 +1,9 @@ +#include +#include +#include + #include "dbfs.h" +#include "trans.h" #include "../lib/log.h" @@ -17,7 +22,7 @@ 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.symlink + || evsql_result_string(res, 0, 2, &link, 1 ) // inodes.link_path ) EERROR(err = EIO, "invalid db data"); @@ -47,7 +52,7 @@ const char *sql = "SELECT" - " inodes.ino, inodes.type, inodes.symlink" + " inodes.ino, inodes.type, inodes.link_path" " FROM inodes" " WHERE inodes.ino = $1::int4"; @@ -77,3 +82,184 @@ EWARNING(err, "fuse_reply_err"); } + +struct dbfs_symlink_ctx { + struct dbfs_trans base; + + char *link, *name; + uint32_t ino, parent; +}; + +#define DBFS_SYMLINK_MODE 0777 + +void dbfs_symlink_free (struct dbfs_trans *ctx_base) { + struct dbfs_symlink_ctx *ctx = (struct dbfs_symlink_ctx *) ctx_base; + + free(ctx->link); + free(ctx->name); +} + +void dbfs_symlink_commit (struct dbfs_trans *ctx_base) { + struct dbfs_symlink_ctx *ctx = (struct dbfs_symlink_ctx *) ctx_base; + struct fuse_entry_param e; + int err; + + // build entry + e.ino = e.attr.st_ino = ctx->ino; + e.attr.st_mode = S_IFLNK | DBFS_SYMLINK_MODE; + e.attr.st_size = strlen(ctx->link); + 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_symlink_filetree (const struct evsql_result_info *res, void *arg) { + struct dbfs_symlink_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_symlink_inode (const struct evsql_result_info *res, void *arg) { + struct dbfs_symlink_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)" + " VALUES ($1::varchar, $2::int4, $3::int4)"; + + static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { + EVSQL_PARAM ( STRING ), + 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) + ) + goto error; + + // query + if (evsql_query_params(dbfs_ctx->db, ctx->base.trans, sql, ¶ms, dbfs_symlink_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_symlink_begin (struct dbfs_trans *ctx_base) { + struct dbfs_symlink_ctx *ctx = (struct dbfs_symlink_ctx *) ctx_base; + struct dbfs *dbfs_ctx = fuse_req_userdata(ctx_base->req); + + // insert inode + const char *sql = + "INSERT" + " INTO inodes (type, mode, link_path)" + " VALUES ('LNK', $1::int2, $2::varchar)" + " RETURNING inodes.ino"; + + static struct evsql_query_params params = EVSQL_PARAMS(EVSQL_FMT_BINARY) { + EVSQL_PARAM ( UINT16 ), + EVSQL_PARAM ( STRING ), + + EVSQL_PARAMS_END + }; + + if (0 + || evsql_param_uint16(¶ms, 0, DBFS_SYMLINK_MODE) + || evsql_param_string(¶ms, 1, ctx->link) + ) + goto error; + + if (evsql_query_params(dbfs_ctx->db, ctx_base->trans, sql, ¶ms, dbfs_symlink_inode, ctx) == NULL) + goto error; + + return; + +error: + dbfs_trans_fail(ctx_base, EIO); +} + +void dbfs_symlink (struct fuse_req *req, const char *link, fuse_ino_t parent, const char *name) { + struct dbfs_symlink_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_symlink_free; + ctx->base.begin_fn = dbfs_symlink_begin; + ctx->base.commit_fn = dbfs_symlink_commit; + + // state + ctx->ino = 0; + ctx->parent = parent; + if (!((ctx->link = strdup(link)) && (ctx->name = strdup(name)))) + ERROR("strdup"); + + // log + INFO("[dbfs.symlink %p:%p] link=%s, parent=%lu, name=%s", ctx, req, link, parent, name); + + // wait + return; + +error: + if (ctx) + dbfs_trans_fail(&ctx->base, EIO); +} + diff -r 90e14e0df133 -r c71f3053c714 src/dbfs/ops.h --- a/src/dbfs/ops.h Fri Oct 17 16:09:35 2008 +0300 +++ b/src/dbfs/ops.h Fri Oct 17 18:53:05 2008 +0300 @@ -14,8 +14,9 @@ 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); -/* symlink.c */ +/* link.c */ void dbfs_readlink (struct fuse_req *req, fuse_ino_t ino); +void dbfs_symlink (struct fuse_req *req, const char *link, fuse_ino_t parent, const char *name); /* dirop.c */ void dbfs_opendir (struct fuse_req *req, fuse_ino_t ino, struct fuse_file_info *fi); diff -r 90e14e0df133 -r c71f3053c714 src/dbfs/trans.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dbfs/trans.c Fri Oct 17 18:53:05 2008 +0300 @@ -0,0 +1,138 @@ + +#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 90e14e0df133 -r c71f3053c714 src/dbfs/trans.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dbfs/trans.h Fri Oct 17 18:53:05 2008 +0300 @@ -0,0 +1,69 @@ +#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 90e14e0df133 -r c71f3053c714 src/lib/error.h --- a/src/lib/error.h Fri Oct 17 16:09:35 2008 +0300 +++ b/src/lib/error.h Fri Oct 17 18:53:05 2008 +0300 @@ -7,6 +7,7 @@ #define PERROR(...) do { perr_func(__func__, __VA_ARGS__); goto error; } while (0) #define EERROR(_err, ...) do { eerr_func(__func__, (_err), __VA_ARGS__); goto error; } while (0) #define NERROR(...) do { err_func_nonl(__func__, __VA_ARGS__); goto error; } while (0) +#define SERROR(_err) do { (_err); goto error; } while (0) // XXX: replace with *err_func(...) + exit(EXIT_FAILURE) #define FATAL(...) err_func_exit(__func__, __VA_ARGS__) diff -r 90e14e0df133 -r c71f3053c714 src/lib/log.c --- a/src/lib/log.c Fri Oct 17 16:09:35 2008 +0300 +++ b/src/lib/log.c Fri Oct 17 18:53:05 2008 +0300 @@ -3,12 +3,16 @@ #include #include #include +#include #include "log.h" static void _generic_err_vargs (int flags, const char *func, int err, const char *fmt, va_list va) { FILE *stream = flags & LOG_DISPLAY_STDERR ? stderr : stdout; + if (!fmt) + return; + if (flags & LOG_DISPLAY_FATAL) fprintf(stream, "FATAL: "); @@ -35,6 +39,8 @@ void _generic_err_exit (int flags, const char *func, int err, const char *fmt, ...) { va_list va; + assert(fmt); + va_start(va, fmt); _generic_err_vargs(flags | LOG_DISPLAY_FATAL, func, err, fmt, va); va_end(va);