From a97da100b00206544c7a68593b64a49f2b854f7e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 17 Mar 2011 22:12:21 +1030 Subject: [PATCH 1/1] tdb2: implement tdb_exists and tdb_parse_record These are the same as the TDB1 functions; but note that there is no reliable way to tell if tdb_exists() fails due to an error. This simplifies the API, but means you have to use tdb_fetch() if you really care. --- ccan/tdb2/tdb.c | 54 +++++++++++++++++++++ ccan/tdb2/tdb2.h | 44 +++++++++++++++++ ccan/tdb2/test/run-14-exists.c | 57 ++++++++++++++++++++++ ccan/tdb2/test/run-16-wipe_all.c | 50 +++++++++++++++++++ ccan/tdb2/test/run-21-parse_record.c | 72 ++++++++++++++++++++++++++++ ccan/tdb2/traverse.c | 24 ++++++++++ 6 files changed, 301 insertions(+) create mode 100644 ccan/tdb2/test/run-14-exists.c create mode 100644 ccan/tdb2/test/run-16-wipe_all.c create mode 100644 ccan/tdb2/test/run-21-parse_record.c diff --git a/ccan/tdb2/tdb.c b/ccan/tdb2/tdb.c index a1ec332a..d3d12250 100644 --- a/ccan/tdb2/tdb.c +++ b/ccan/tdb2/tdb.c @@ -243,6 +243,21 @@ enum TDB_ERROR tdb_fetch(struct tdb_context *tdb, struct tdb_data key, return ecode; } +bool tdb_exists(struct tdb_context *tdb, TDB_DATA key) +{ + tdb_off_t off; + struct tdb_used_record rec; + struct hash_info h; + + off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL); + if (TDB_OFF_IS_ERR(off)) { + return false; + } + tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK); + + return off ? true : false; +} + enum TDB_ERROR tdb_delete(struct tdb_context *tdb, struct tdb_data key) { tdb_off_t off; @@ -377,3 +392,42 @@ enum TDB_ERROR COLD tdb_logerr(struct tdb_context *tdb, errno = saved_errno; return ecode; } + +enum TDB_ERROR tdb_parse_record_(struct tdb_context *tdb, + TDB_DATA key, + enum TDB_ERROR (*parse)(TDB_DATA key, + TDB_DATA data, + void *p), + void *p) +{ + tdb_off_t off; + struct tdb_used_record rec; + struct hash_info h; + TDB_DATA data; + enum TDB_ERROR ecode; + + off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL); + if (TDB_OFF_IS_ERR(off)) { + return off; + } + + if (!off) { + ecode = TDB_ERR_NOEXIST; + } else { + data.dsize = rec_data_length(&rec); + data.dptr = (void *)tdb_access_read(tdb, + off + sizeof(rec) + + key.dsize, + data.dsize, false); + if (TDB_PTR_IS_ERR(data.dptr)) { + ecode = TDB_PTR_ERR(data.dptr); + } else { + ecode = parse(key, data, p); + tdb_access_release(tdb, data.dptr); + } + } + + tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK); + return ecode; +} + diff --git a/ccan/tdb2/tdb2.h b/ccan/tdb2/tdb2.h index 47661da2..50903a76 100644 --- a/ccan/tdb2/tdb2.h +++ b/ccan/tdb2/tdb2.h @@ -38,6 +38,8 @@ extern "C" { #include /* For uint64_t */ #include +/* For bool */ +#include #endif #include #include @@ -197,6 +199,15 @@ enum TDB_ERROR tdb_append(struct tdb_context *tdb, */ enum TDB_ERROR tdb_delete(struct tdb_context *tdb, struct tdb_data key); +/** + * tdb_exists - does a key exist in the database? + * @tdb: the tdb context returned from tdb_open() + * @key: the key to search for. + * + * Returns true if it exists, or false if it doesn't or any other error. + */ +bool tdb_exists(struct tdb_context *tdb, TDB_DATA key); + /** * tdb_transaction_start - start a transaction * @tdb: the tdb context returned from tdb_open() @@ -276,6 +287,30 @@ int64_t tdb_traverse_(struct tdb_context *tdb, int (*fn)(struct tdb_context *, TDB_DATA, TDB_DATA, void *), void *p); +/** + * tdb_parse_record - operate directly on data in the database. + * @tdb: the tdb context returned from tdb_open() + * @key: the key whose record we should hand to @parse + * @parse: the function to call for the data + * @p: the private pointer to hand to @parse (types must match). + * + * This avoids a copy for many cases, by handing you a pointer into + * the memory-mapped database. It also locks the record to prevent + * other accesses at the same time. + * + * Do not alter the data handed to parse()! + */ +#define tdb_parse_record(tdb, key, parse, p) \ + tdb_parse_record_((tdb), (key), \ + typesafe_cb_preargs(enum TDB_ERROR, (parse), (p), \ + TDB_DATA, TDB_DATA), (p)) + +enum TDB_ERROR tdb_parse_record_(struct tdb_context *tdb, + TDB_DATA key, + enum TDB_ERROR (*parse)(TDB_DATA key, + TDB_DATA data, + void *p), + void *p); /** * tdb_firstkey - get the "first" key in a TDB * @tdb: the tdb context returned from tdb_open() @@ -328,6 +363,15 @@ enum TDB_ERROR tdb_chainlock(struct tdb_context *tdb, TDB_DATA key); */ enum TDB_ERROR tdb_chainunlock(struct tdb_context *tdb, TDB_DATA key); +/** + * tdb_wipe_all - wipe the database clean + * @tdb: the tdb context returned from tdb_open() + * + * Completely erase the database. This is faster than iterating through + * each key and doing tdb_delete. + */ +enum TDB_ERROR tdb_wipe_all(struct tdb_context *tdb); + /** * tdb_check - check a TDB for consistency * @tdb: the tdb context returned from tdb_open() diff --git a/ccan/tdb2/test/run-14-exists.c b/ccan/tdb2/test/run-14-exists.c new file mode 100644 index 00000000..f264a6f2 --- /dev/null +++ b/ccan/tdb2/test/run-14-exists.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "logging.h" + +static bool test_records(struct tdb_context *tdb) +{ + int i; + struct tdb_data key = { (unsigned char *)&i, sizeof(i) }; + struct tdb_data data = { (unsigned char *)&i, sizeof(i) }; + + for (i = 0; i < 1000; i++) { + if (tdb_exists(tdb, key)) + return false; + if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) + return false; + if (!tdb_exists(tdb, key)) + return false; + } + + for (i = 0; i < 1000; i++) { + if (!tdb_exists(tdb, key)) + return false; + if (tdb_delete(tdb, key) != 0) + return false; + if (tdb_exists(tdb, key)) + return false; + } + return true; +} + +int main(int argc, char *argv[]) +{ + unsigned int i; + struct tdb_context *tdb; + int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, + TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, + TDB_NOMMAP|TDB_CONVERT }; + + plan_tests(sizeof(flags) / sizeof(flags[0]) * 2 + 1); + for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { + tdb = tdb_open("run-14-exists.tdb", flags[i], + O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); + if (ok1(tdb)) + ok1(test_records(tdb)); + tdb_close(tdb); + } + + ok1(tap_log_messages == 0); + return exit_status(); +} diff --git a/ccan/tdb2/test/run-16-wipe_all.c b/ccan/tdb2/test/run-16-wipe_all.c new file mode 100644 index 00000000..d9c5128e --- /dev/null +++ b/ccan/tdb2/test/run-16-wipe_all.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "logging.h" + +static bool add_records(struct tdb_context *tdb) +{ + int i; + struct tdb_data key = { (unsigned char *)&i, sizeof(i) }; + struct tdb_data data = { (unsigned char *)&i, sizeof(i) }; + + for (i = 0; i < 1000; i++) { + if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) + return false; + } + return true; +} + + +int main(int argc, char *argv[]) +{ + unsigned int i; + struct tdb_context *tdb; + int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, + TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, + TDB_NOMMAP|TDB_CONVERT }; + + plan_tests(sizeof(flags) / sizeof(flags[0]) * 4 + 1); + for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { + tdb = tdb_open("run-16-wipe_all.tdb", flags[i], + O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); + if (ok1(tdb)) { + struct tdb_data key; + ok1(add_records(tdb)); + ok1(tdb_wipe_all(tdb) == TDB_SUCCESS); + ok1(tdb_firstkey(tdb, &key) == TDB_ERR_NOEXIST); + tdb_close(tdb); + } + } + + ok1(tap_log_messages == 0); + return exit_status(); +} diff --git a/ccan/tdb2/test/run-21-parse_record.c b/ccan/tdb2/test/run-21-parse_record.c new file mode 100644 index 00000000..b6601d01 --- /dev/null +++ b/ccan/tdb2/test/run-21-parse_record.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "logging.h" + +static enum TDB_ERROR parse(TDB_DATA key, TDB_DATA data, TDB_DATA *expected) +{ + if (data.dsize != expected->dsize) + return TDB_ERR_EINVAL; + if (memcmp(data.dptr, expected->dptr, data.dsize) != 0) + return TDB_ERR_EINVAL; + return TDB_SUCCESS; +} + +static enum TDB_ERROR parse_err(TDB_DATA key, TDB_DATA data, void *unused) +{ + return 100; +} + +static bool test_records(struct tdb_context *tdb) +{ + int i; + struct tdb_data key = { (unsigned char *)&i, sizeof(i) }; + struct tdb_data data = { (unsigned char *)&i, sizeof(i) }; + + for (i = 0; i < 1000; i++) { + if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) + return false; + } + + for (i = 0; i < 1000; i++) { + if (tdb_parse_record(tdb, key, parse, &data) != TDB_SUCCESS) + return false; + } + + if (tdb_parse_record(tdb, key, parse, &data) != TDB_ERR_NOEXIST) + return false; + + /* Test error return from parse function. */ + i = 0; + if (tdb_parse_record(tdb, key, parse_err, NULL) != 100) + return false; + + return true; +} + +int main(int argc, char *argv[]) +{ + unsigned int i; + struct tdb_context *tdb; + int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, + TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, + TDB_NOMMAP|TDB_CONVERT }; + + plan_tests(sizeof(flags) / sizeof(flags[0]) * 2 + 1); + for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { + tdb = tdb_open("run-14-exists.tdb", flags[i], + O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); + if (ok1(tdb)) + ok1(test_records(tdb)); + tdb_close(tdb); + } + + ok1(tap_log_messages == 0); + return exit_status(); +} diff --git a/ccan/tdb2/traverse.c b/ccan/tdb2/traverse.c index 9736e9f5..71a889e5 100644 --- a/ccan/tdb2/traverse.c +++ b/ccan/tdb2/traverse.c @@ -71,3 +71,27 @@ enum TDB_ERROR tdb_nextkey(struct tdb_context *tdb, struct tdb_data *key) return next_in_hash(tdb, &tinfo, key, NULL); } + +static int wipe_one(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, enum TDB_ERROR *ecode) +{ + *ecode = tdb_delete(tdb, key); + return (*ecode != TDB_SUCCESS); +} + +enum TDB_ERROR tdb_wipe_all(struct tdb_context *tdb) +{ + enum TDB_ERROR ecode; + int64_t count; + + ecode = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false); + if (ecode != TDB_SUCCESS) + return ecode; + + /* FIXME: Be smarter. */ + count = tdb_traverse(tdb, wipe_one, &ecode); + if (count < 0) + ecode = count; + tdb_allrecord_unlock(tdb, F_WRLCK); + return ecode; +} -- 2.39.2