From b91dafc6ab5075355d508f051977a15dd7794b0e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 9 Mar 2012 13:25:03 +1030 Subject: [PATCH] tdb2: remove: it's now in SAMBA where it belongs. --- ccan/tdb2/LICENSE | 1 - ccan/tdb2/_info | 95 - ccan/tdb2/check.c | 870 --- ccan/tdb2/doc/TDB1_porting.txt | 72 - ccan/tdb2/doc/design-1.3.txt | 1050 ---- ccan/tdb2/doc/design.lyx | 2689 ---------- ccan/tdb2/doc/design.lyx,v | 4679 ----------------- ccan/tdb2/doc/design.pdf | Bin 240440 -> 0 bytes ccan/tdb2/doc/design.txt | 1259 ----- ccan/tdb2/free.c | 975 ---- ccan/tdb2/hash.c | 913 ---- ccan/tdb2/io.c | 640 --- ccan/tdb2/lock.c | 895 ---- ccan/tdb2/open.c | 884 ---- ccan/tdb2/private.h | 762 --- ccan/tdb2/summary.c | 356 -- ccan/tdb2/tdb.c | 642 --- ccan/tdb2/tdb1_check.c | 478 -- ccan/tdb2/tdb1_freelist.c | 322 -- ccan/tdb2/tdb1_hash.c | 347 -- ccan/tdb2/tdb1_io.c | 543 -- ccan/tdb2/tdb1_lock.c | 560 -- ccan/tdb2/tdb1_open.c | 234 - ccan/tdb2/tdb1_private.h | 179 - ccan/tdb2/tdb1_summary.c | 202 - ccan/tdb2/tdb1_tdb.c | 829 --- ccan/tdb2/tdb1_transaction.c | 1339 ----- ccan/tdb2/tdb1_traverse.c | 373 -- ccan/tdb2/tdb2.h | 924 ---- ccan/tdb2/test/api-12-store.c | 56 - ccan/tdb2/test/api-13-delete.c | 210 - ccan/tdb2/test/api-14-exists.c | 58 - ccan/tdb2/test/api-16-wipe_all.c | 50 - ccan/tdb2/test/api-21-parse_record.c | 71 - ccan/tdb2/test/api-55-transaction.c | 76 - ccan/tdb2/test/api-80-tdb_fd.c | 36 - ccan/tdb2/test/api-81-seqnum.c | 79 - ccan/tdb2/test/api-82-lockattr.c | 248 - ccan/tdb2/test/api-83-openhook.c | 99 - ccan/tdb2/test/api-91-get-stats.c | 59 - ccan/tdb2/test/api-92-get-set-readonly.c | 120 - ccan/tdb2/test/api-93-repack.c | 82 - ccan/tdb2/test/api-add-remove-flags.c | 94 - ccan/tdb2/test/api-check-callback.c | 90 - ccan/tdb2/test/api-firstkey-nextkey.c | 163 - ccan/tdb2/test/api-fork-test.c | 204 - ccan/tdb2/test/api-locktimeout.c | 194 - ccan/tdb2/test/api-missing-entries.c | 43 - ccan/tdb2/test/api-open-multiple-times.c | 93 - ccan/tdb2/test/api-record-expand.c | 55 - ccan/tdb2/test/api-simple-delete.c | 43 - ccan/tdb2/test/api-summary.c | 63 - ccan/tdb2/test/api-tdb1-flag-removal.c | 38 - ccan/tdb2/test/external-agent.c | 256 - ccan/tdb2/test/external-agent.h | 46 - ccan/tdb2/test/failtest_helper.c | 96 - ccan/tdb2/test/failtest_helper.h | 19 - ccan/tdb2/test/jenkins-be-hash.tdb1 | Bin 696 -> 0 bytes ccan/tdb2/test/jenkins-le-hash.tdb1 | Bin 696 -> 0 bytes ccan/tdb2/test/layout.c | 402 -- ccan/tdb2/test/layout.h | 87 - ccan/tdb2/test/lock-tracking.c | 158 - ccan/tdb2/test/lock-tracking.h | 25 - ccan/tdb2/test/logging.c | 31 - ccan/tdb2/test/logging.h | 17 - ccan/tdb2/test/old-nohash-be.tdb1 | Bin 696 -> 0 bytes ccan/tdb2/test/old-nohash-le.tdb1 | Bin 696 -> 0 bytes ccan/tdb2/test/run-001-encode.c | 41 - ccan/tdb2/test/run-001-fls.c | 33 - ccan/tdb2/test/run-01-new_database.c | 39 - ccan/tdb2/test/run-02-expand.c | 62 - ccan/tdb2/test/run-03-coalesce.c | 178 - ccan/tdb2/test/run-04-basichash.c | 260 - ccan/tdb2/test/run-05-readonly-open.c | 75 - ccan/tdb2/test/run-10-simple-store.c | 63 - ccan/tdb2/test/run-11-simple-fetch.c | 63 - ccan/tdb2/test/run-12-check.c | 50 - ccan/tdb2/test/run-15-append.c | 153 - ccan/tdb2/test/run-20-growhash.c | 137 - ccan/tdb2/test/run-25-hashoverload.c | 113 - ccan/tdb2/test/run-30-exhaust-before-expand.c | 72 - ccan/tdb2/test/run-35-convert.c | 57 - ccan/tdb2/test/run-50-multiple-freelists.c | 70 - .../test/run-56-open-during-transaction.c | 169 - .../tdb2/test/run-57-die-during-transaction.c | 296 -- ccan/tdb2/test/run-64-bit-tdb.c | 72 - ccan/tdb2/test/run-90-get-set-attributes.c | 186 - ccan/tdb2/test/run-capabilities.c | 272 - ccan/tdb2/test/run-expand-in-transaction.c | 39 - ccan/tdb2/test/run-features.c | 64 - ccan/tdb2/test/run-lockall.c | 74 - ccan/tdb2/test/run-remap-in-read_traverse.c | 57 - ccan/tdb2/test/run-seed.c | 61 - ccan/tdb2/test/run-tdb1-3G-file.c | 125 - ccan/tdb2/test/run-tdb1-bad-tdb-header.c | 52 - ccan/tdb2/test/run-tdb1-check.c | 59 - ccan/tdb2/test/run-tdb1-corrupt.c | 123 - ccan/tdb2/test/run-tdb1-endian.c | 56 - ccan/tdb2/test/run-tdb1-hashsize.c | 61 - ccan/tdb2/test/run-tdb1-incompatible.c | 213 - ccan/tdb2/test/run-tdb1-nested-transactions.c | 73 - ccan/tdb2/test/run-tdb1-nested-traverse.c | 87 - .../test/run-tdb1-no-lock-during-traverse.c | 111 - ccan/tdb2/test/run-tdb1-oldhash.c | 45 - ccan/tdb2/test/run-tdb1-readonly-check.c | 47 - ccan/tdb2/test/run-tdb1-rwlock-check.c | 42 - ccan/tdb2/test/run-tdb1-seqnum-wrap.c | 39 - ccan/tdb2/test/run-tdb1-summary.c | 56 - .../test/run-tdb1-traverse-in-transaction.c | 85 - ccan/tdb2/test/run-tdb1-wronghash-fail.c | 143 - ccan/tdb2/test/run-tdb1-zero-append.c | 36 - ccan/tdb2/test/run-tdb1.c | 42 - ccan/tdb2/test/run-tdb_errorstr.c | 52 - ccan/tdb2/test/run-tdb_foreach.c | 86 - ccan/tdb2/test/run-traverse.c | 203 - ccan/tdb2/test/rwlock-be.tdb1 | Bin 696 -> 0 bytes ccan/tdb2/test/rwlock-le.tdb1 | Bin 696 -> 0 bytes ccan/tdb2/test/tdb1-external-agent.c | 188 - ccan/tdb2/test/tdb1-external-agent.h | 40 - ccan/tdb2/test/tdb1-lock-tracking.c | 146 - ccan/tdb2/test/tdb1-lock-tracking.h | 26 - ccan/tdb2/test/tdb1.corrupt | Bin 192512 -> 0 bytes ccan/tdb2/test/tdb2-source.h | 21 - ccan/tdb2/tools/Makefile | 16 - ccan/tdb2/tools/growtdb-bench.c | 114 - ccan/tdb2/tools/mktdb2.c | 29 - ccan/tdb2/tools/speed.c | 443 -- ccan/tdb2/tools/tdb2dump.c | 115 - ccan/tdb2/tools/tdb2restore.c | 227 - ccan/tdb2/tools/tdb2tool.c | 802 --- ccan/tdb2/tools/tdb2torture.c | 498 -- ccan/tdb2/transaction.c | 1343 ----- ccan/tdb2/traverse.c | 134 - 133 files changed, 35307 deletions(-) delete mode 120000 ccan/tdb2/LICENSE delete mode 100644 ccan/tdb2/_info delete mode 100644 ccan/tdb2/check.c delete mode 100644 ccan/tdb2/doc/TDB1_porting.txt delete mode 100644 ccan/tdb2/doc/design-1.3.txt delete mode 100644 ccan/tdb2/doc/design.lyx delete mode 100644 ccan/tdb2/doc/design.lyx,v delete mode 100644 ccan/tdb2/doc/design.pdf delete mode 100644 ccan/tdb2/doc/design.txt delete mode 100644 ccan/tdb2/free.c delete mode 100644 ccan/tdb2/hash.c delete mode 100644 ccan/tdb2/io.c delete mode 100644 ccan/tdb2/lock.c delete mode 100644 ccan/tdb2/open.c delete mode 100644 ccan/tdb2/private.h delete mode 100644 ccan/tdb2/summary.c delete mode 100644 ccan/tdb2/tdb.c delete mode 100644 ccan/tdb2/tdb1_check.c delete mode 100644 ccan/tdb2/tdb1_freelist.c delete mode 100644 ccan/tdb2/tdb1_hash.c delete mode 100644 ccan/tdb2/tdb1_io.c delete mode 100644 ccan/tdb2/tdb1_lock.c delete mode 100644 ccan/tdb2/tdb1_open.c delete mode 100644 ccan/tdb2/tdb1_private.h delete mode 100644 ccan/tdb2/tdb1_summary.c delete mode 100644 ccan/tdb2/tdb1_tdb.c delete mode 100644 ccan/tdb2/tdb1_transaction.c delete mode 100644 ccan/tdb2/tdb1_traverse.c delete mode 100644 ccan/tdb2/tdb2.h delete mode 100644 ccan/tdb2/test/api-12-store.c delete mode 100644 ccan/tdb2/test/api-13-delete.c delete mode 100644 ccan/tdb2/test/api-14-exists.c delete mode 100644 ccan/tdb2/test/api-16-wipe_all.c delete mode 100644 ccan/tdb2/test/api-21-parse_record.c delete mode 100644 ccan/tdb2/test/api-55-transaction.c delete mode 100644 ccan/tdb2/test/api-80-tdb_fd.c delete mode 100644 ccan/tdb2/test/api-81-seqnum.c delete mode 100644 ccan/tdb2/test/api-82-lockattr.c delete mode 100644 ccan/tdb2/test/api-83-openhook.c delete mode 100644 ccan/tdb2/test/api-91-get-stats.c delete mode 100644 ccan/tdb2/test/api-92-get-set-readonly.c delete mode 100644 ccan/tdb2/test/api-93-repack.c delete mode 100644 ccan/tdb2/test/api-add-remove-flags.c delete mode 100644 ccan/tdb2/test/api-check-callback.c delete mode 100644 ccan/tdb2/test/api-firstkey-nextkey.c delete mode 100644 ccan/tdb2/test/api-fork-test.c delete mode 100644 ccan/tdb2/test/api-locktimeout.c delete mode 100644 ccan/tdb2/test/api-missing-entries.c delete mode 100644 ccan/tdb2/test/api-open-multiple-times.c delete mode 100644 ccan/tdb2/test/api-record-expand.c delete mode 100644 ccan/tdb2/test/api-simple-delete.c delete mode 100644 ccan/tdb2/test/api-summary.c delete mode 100644 ccan/tdb2/test/api-tdb1-flag-removal.c delete mode 100644 ccan/tdb2/test/external-agent.c delete mode 100644 ccan/tdb2/test/external-agent.h delete mode 100644 ccan/tdb2/test/failtest_helper.c delete mode 100644 ccan/tdb2/test/failtest_helper.h delete mode 100644 ccan/tdb2/test/jenkins-be-hash.tdb1 delete mode 100644 ccan/tdb2/test/jenkins-le-hash.tdb1 delete mode 100644 ccan/tdb2/test/layout.c delete mode 100644 ccan/tdb2/test/layout.h delete mode 100644 ccan/tdb2/test/lock-tracking.c delete mode 100644 ccan/tdb2/test/lock-tracking.h delete mode 100644 ccan/tdb2/test/logging.c delete mode 100644 ccan/tdb2/test/logging.h delete mode 100644 ccan/tdb2/test/old-nohash-be.tdb1 delete mode 100644 ccan/tdb2/test/old-nohash-le.tdb1 delete mode 100644 ccan/tdb2/test/run-001-encode.c delete mode 100644 ccan/tdb2/test/run-001-fls.c delete mode 100644 ccan/tdb2/test/run-01-new_database.c delete mode 100644 ccan/tdb2/test/run-02-expand.c delete mode 100644 ccan/tdb2/test/run-03-coalesce.c delete mode 100644 ccan/tdb2/test/run-04-basichash.c delete mode 100644 ccan/tdb2/test/run-05-readonly-open.c delete mode 100644 ccan/tdb2/test/run-10-simple-store.c delete mode 100644 ccan/tdb2/test/run-11-simple-fetch.c delete mode 100644 ccan/tdb2/test/run-12-check.c delete mode 100644 ccan/tdb2/test/run-15-append.c delete mode 100644 ccan/tdb2/test/run-20-growhash.c delete mode 100644 ccan/tdb2/test/run-25-hashoverload.c delete mode 100644 ccan/tdb2/test/run-30-exhaust-before-expand.c delete mode 100644 ccan/tdb2/test/run-35-convert.c delete mode 100644 ccan/tdb2/test/run-50-multiple-freelists.c delete mode 100644 ccan/tdb2/test/run-56-open-during-transaction.c delete mode 100644 ccan/tdb2/test/run-57-die-during-transaction.c delete mode 100644 ccan/tdb2/test/run-64-bit-tdb.c delete mode 100644 ccan/tdb2/test/run-90-get-set-attributes.c delete mode 100644 ccan/tdb2/test/run-capabilities.c delete mode 100644 ccan/tdb2/test/run-expand-in-transaction.c delete mode 100644 ccan/tdb2/test/run-features.c delete mode 100644 ccan/tdb2/test/run-lockall.c delete mode 100644 ccan/tdb2/test/run-remap-in-read_traverse.c delete mode 100644 ccan/tdb2/test/run-seed.c delete mode 100644 ccan/tdb2/test/run-tdb1-3G-file.c delete mode 100644 ccan/tdb2/test/run-tdb1-bad-tdb-header.c delete mode 100644 ccan/tdb2/test/run-tdb1-check.c delete mode 100644 ccan/tdb2/test/run-tdb1-corrupt.c delete mode 100644 ccan/tdb2/test/run-tdb1-endian.c delete mode 100644 ccan/tdb2/test/run-tdb1-hashsize.c delete mode 100644 ccan/tdb2/test/run-tdb1-incompatible.c delete mode 100644 ccan/tdb2/test/run-tdb1-nested-transactions.c delete mode 100644 ccan/tdb2/test/run-tdb1-nested-traverse.c delete mode 100644 ccan/tdb2/test/run-tdb1-no-lock-during-traverse.c delete mode 100644 ccan/tdb2/test/run-tdb1-oldhash.c delete mode 100644 ccan/tdb2/test/run-tdb1-readonly-check.c delete mode 100644 ccan/tdb2/test/run-tdb1-rwlock-check.c delete mode 100644 ccan/tdb2/test/run-tdb1-seqnum-wrap.c delete mode 100644 ccan/tdb2/test/run-tdb1-summary.c delete mode 100644 ccan/tdb2/test/run-tdb1-traverse-in-transaction.c delete mode 100644 ccan/tdb2/test/run-tdb1-wronghash-fail.c delete mode 100644 ccan/tdb2/test/run-tdb1-zero-append.c delete mode 100644 ccan/tdb2/test/run-tdb1.c delete mode 100644 ccan/tdb2/test/run-tdb_errorstr.c delete mode 100644 ccan/tdb2/test/run-tdb_foreach.c delete mode 100644 ccan/tdb2/test/run-traverse.c delete mode 100644 ccan/tdb2/test/rwlock-be.tdb1 delete mode 100644 ccan/tdb2/test/rwlock-le.tdb1 delete mode 100644 ccan/tdb2/test/tdb1-external-agent.c delete mode 100644 ccan/tdb2/test/tdb1-external-agent.h delete mode 100644 ccan/tdb2/test/tdb1-lock-tracking.c delete mode 100644 ccan/tdb2/test/tdb1-lock-tracking.h delete mode 100644 ccan/tdb2/test/tdb1.corrupt delete mode 100644 ccan/tdb2/test/tdb2-source.h delete mode 100644 ccan/tdb2/tools/Makefile delete mode 100644 ccan/tdb2/tools/growtdb-bench.c delete mode 100644 ccan/tdb2/tools/mktdb2.c delete mode 100644 ccan/tdb2/tools/speed.c delete mode 100644 ccan/tdb2/tools/tdb2dump.c delete mode 100644 ccan/tdb2/tools/tdb2restore.c delete mode 100644 ccan/tdb2/tools/tdb2tool.c delete mode 100644 ccan/tdb2/tools/tdb2torture.c delete mode 100644 ccan/tdb2/transaction.c delete mode 100644 ccan/tdb2/traverse.c diff --git a/ccan/tdb2/LICENSE b/ccan/tdb2/LICENSE deleted file mode 120000 index 74550445..00000000 --- a/ccan/tdb2/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../licenses/LGPL-3 \ No newline at end of file diff --git a/ccan/tdb2/_info b/ccan/tdb2/_info deleted file mode 100644 index d26e06ba..00000000 --- a/ccan/tdb2/_info +++ /dev/null @@ -1,95 +0,0 @@ -#include -#include - -/** - * tdb2 - [[WORK IN PROGRESS!]] The trivial (64bit transactional) database - * - * The tdb2 module provides an efficient keyword data mapping (usually - * within a file). It supports transactions, so the contents of the - * database is reliable even across crashes. - * - * Example: - * #include - * #include - * #include - * #include - * - * static void usage(const char *argv0) - * { - * errx(1, "Usage: %s fetch \n" - * "OR %s store ", argv0, argv0); - * } - * - * int main(int argc, char *argv[]) - * { - * struct tdb_context *tdb; - * TDB_DATA key, value; - * enum TDB_ERROR error; - * - * if (argc < 4) - * usage(argv[0]); - * - * tdb = tdb_open(argv[2], TDB_DEFAULT, O_CREAT|O_RDWR,0600, NULL); - * if (!tdb) - * err(1, "Opening %s", argv[2]); - * - * key.dptr = (void *)argv[3]; - * key.dsize = strlen(argv[3]); - * - * if (streq(argv[1], "fetch")) { - * if (argc != 4) - * usage(argv[0]); - * error = tdb_fetch(tdb, key, &value); - * if (error) - * errx(1, "fetch %s: %s", - * argv[3], tdb_errorstr(error)); - * printf("%.*s\n", value.dsize, (char *)value.dptr); - * free(value.dptr); - * } else if (streq(argv[1], "store")) { - * if (argc != 5) - * usage(argv[0]); - * value.dptr = (void *)argv[4]; - * value.dsize = strlen(argv[4]); - * error = tdb_store(tdb, key, value, 0); - * if (error) - * errx(1, "store %s: %s", - * argv[3], tdb_errorstr(error)); - * } else - * usage(argv[0]); - * - * return 0; - * } - * - * Maintainer: Rusty Russell - * - * Author: Rusty Russell - * - * License: LGPL (v3 or any later version) - * - * Ccanlint: - * // valgrind breaks fcntl locks. - * tests_pass_valgrind test/api-83-openhook.c:FAIL - */ -int main(int argc, char *argv[]) -{ - if (argc != 2) - return 1; - - if (strcmp(argv[1], "depends") == 0) { - printf("ccan/asprintf\n"); - printf("ccan/hash\n"); - printf("ccan/likely\n"); - printf("ccan/asearch\n"); - printf("ccan/compiler\n"); - printf("ccan/build_assert\n"); - printf("ccan/ilog\n"); - printf("ccan/failtest\n"); - printf("ccan/tally\n"); - printf("ccan/typesafe_cb\n"); - printf("ccan/cast\n"); - printf("ccan/endian\n"); - return 0; - } - - return 1; -} diff --git a/ccan/tdb2/check.c b/ccan/tdb2/check.c deleted file mode 100644 index ecd6c13c..00000000 --- a/ccan/tdb2/check.c +++ /dev/null @@ -1,870 +0,0 @@ - /* - Trivial Database 2: free list/block handling - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "private.h" -#include -#include - -/* We keep an ordered array of offsets. */ -static bool append(tdb_off_t **arr, size_t *num, tdb_off_t off) -{ - tdb_off_t *new = realloc(*arr, (*num + 1) * sizeof(tdb_off_t)); - if (!new) - return false; - new[(*num)++] = off; - *arr = new; - return true; -} - -static enum TDB_ERROR check_header(struct tdb_context *tdb, tdb_off_t *recovery, - uint64_t *features, size_t *num_capabilities) -{ - uint64_t hash_test; - struct tdb_header hdr; - enum TDB_ERROR ecode; - tdb_off_t off, next; - - ecode = tdb_read_convert(tdb, 0, &hdr, sizeof(hdr)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - /* magic food should not be converted, so convert back. */ - tdb_convert(tdb, hdr.magic_food, sizeof(hdr.magic_food)); - - hash_test = TDB_HASH_MAGIC; - hash_test = tdb_hash(tdb, &hash_test, sizeof(hash_test)); - if (hdr.hash_test != hash_test) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "check: hash test %llu should be %llu", - (long long)hdr.hash_test, - (long long)hash_test); - } - - if (strcmp(hdr.magic_food, TDB_MAGIC_FOOD) != 0) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "check: bad magic '%.*s'", - (unsigned)sizeof(hdr.magic_food), - hdr.magic_food); - } - - /* Features which are used must be a subset of features offered. */ - if (hdr.features_used & ~hdr.features_offered) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "check: features used (0x%llx) which" - " are not offered (0x%llx)", - (long long)hdr.features_used, - (long long)hdr.features_offered); - } - - *features = hdr.features_offered; - *recovery = hdr.recovery; - if (*recovery) { - if (*recovery < sizeof(hdr) - || *recovery > tdb->file->map_size) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check:" - " invalid recovery offset %zu", - (size_t)*recovery); - } - } - - for (off = hdr.capabilities; off && ecode == TDB_SUCCESS; off = next) { - const struct tdb_capability *cap; - enum TDB_ERROR err; - - cap = tdb_access_read(tdb, off, sizeof(*cap), true); - if (TDB_PTR_IS_ERR(cap)) { - return TDB_PTR_ERR(cap); - } - - /* All capabilities are unknown. */ - err = unknown_capability(tdb, "tdb_check", cap->type); - next = cap->next; - tdb_access_release(tdb, cap); - if (err) - return err; - (*num_capabilities)++; - } - - /* Don't check reserved: they *can* be used later. */ - return TDB_SUCCESS; -} - -static enum TDB_ERROR check_hash_tree(struct tdb_context *tdb, - tdb_off_t off, unsigned int group_bits, - uint64_t hprefix, - unsigned hprefix_bits, - tdb_off_t used[], - size_t num_used, - size_t *num_found, - enum TDB_ERROR (*check)(TDB_DATA, - TDB_DATA, void *), - void *data); - -static enum TDB_ERROR check_hash_chain(struct tdb_context *tdb, - tdb_off_t off, - uint64_t hash, - tdb_off_t used[], - size_t num_used, - size_t *num_found, - enum TDB_ERROR (*check)(TDB_DATA, - TDB_DATA, - void *), - void *data) -{ - struct tdb_used_record rec; - enum TDB_ERROR ecode; - - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - if (rec_magic(&rec) != TDB_CHAIN_MAGIC) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Bad hash chain magic %llu", - (long long)rec_magic(&rec)); - } - - if (rec_data_length(&rec) != sizeof(struct tdb_chain)) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check:" - " Bad hash chain length %llu vs %zu", - (long long)rec_data_length(&rec), - sizeof(struct tdb_chain)); - } - if (rec_key_length(&rec) != 0) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Bad hash chain key length %llu", - (long long)rec_key_length(&rec)); - } - if (rec_hash(&rec) != 0) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Bad hash chain hash value %llu", - (long long)rec_hash(&rec)); - } - - off += sizeof(rec); - ecode = check_hash_tree(tdb, off, 0, hash, 64, - used, num_used, num_found, check, data); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - off = tdb_read_off(tdb, off + offsetof(struct tdb_chain, next)); - if (TDB_OFF_IS_ERR(off)) { - return TDB_OFF_TO_ERR(off); - } - if (off == 0) - return TDB_SUCCESS; - (*num_found)++; - return check_hash_chain(tdb, off, hash, used, num_used, num_found, - check, data); -} - -static enum TDB_ERROR check_hash_record(struct tdb_context *tdb, - tdb_off_t off, - uint64_t hprefix, - unsigned hprefix_bits, - tdb_off_t used[], - size_t num_used, - size_t *num_found, - enum TDB_ERROR (*check)(TDB_DATA, - TDB_DATA, - void *), - void *data) -{ - struct tdb_used_record rec; - enum TDB_ERROR ecode; - - if (hprefix_bits >= 64) - return check_hash_chain(tdb, off, hprefix, used, num_used, - num_found, check, data); - - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - if (rec_magic(&rec) != TDB_HTABLE_MAGIC) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Bad hash table magic %llu", - (long long)rec_magic(&rec)); - } - if (rec_data_length(&rec) - != sizeof(tdb_off_t) << TDB_SUBLEVEL_HASH_BITS) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check:" - " Bad hash table length %llu vs %llu", - (long long)rec_data_length(&rec), - (long long)sizeof(tdb_off_t) - << TDB_SUBLEVEL_HASH_BITS); - } - if (rec_key_length(&rec) != 0) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Bad hash table key length %llu", - (long long)rec_key_length(&rec)); - } - if (rec_hash(&rec) != 0) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Bad hash table hash value %llu", - (long long)rec_hash(&rec)); - } - - off += sizeof(rec); - return check_hash_tree(tdb, off, - TDB_SUBLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS, - hprefix, hprefix_bits, - used, num_used, num_found, check, data); -} - -static int off_cmp(const tdb_off_t *a, const tdb_off_t *b) -{ - /* Can overflow an int. */ - return *a > *b ? 1 - : *a < *b ? -1 - : 0; -} - -static uint64_t get_bits(uint64_t h, unsigned num, unsigned *used) -{ - *used += num; - - return (h >> (64 - *used)) & ((1U << num) - 1); -} - -static enum TDB_ERROR check_hash_tree(struct tdb_context *tdb, - tdb_off_t off, unsigned int group_bits, - uint64_t hprefix, - unsigned hprefix_bits, - tdb_off_t used[], - size_t num_used, - size_t *num_found, - enum TDB_ERROR (*check)(TDB_DATA, - TDB_DATA, void *), - void *data) -{ - unsigned int g, b; - const tdb_off_t *hash; - struct tdb_used_record rec; - enum TDB_ERROR ecode; - - hash = tdb_access_read(tdb, off, - sizeof(tdb_off_t) - << (group_bits + TDB_HASH_GROUP_BITS), - true); - if (TDB_PTR_IS_ERR(hash)) { - return TDB_PTR_ERR(hash); - } - - for (g = 0; g < (1 << group_bits); g++) { - const tdb_off_t *group = hash + (g << TDB_HASH_GROUP_BITS); - for (b = 0; b < (1 << TDB_HASH_GROUP_BITS); b++) { - unsigned int bucket, i, used_bits; - uint64_t h; - tdb_off_t *p; - if (group[b] == 0) - continue; - - off = group[b] & TDB_OFF_MASK; - p = asearch(&off, used, num_used, off_cmp); - if (!p) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: Invalid offset" - " %llu in hash", - (long long)off); - goto fail; - } - /* Mark it invalid. */ - *p ^= 1; - (*num_found)++; - - if (hprefix_bits == 64) { - /* Chained entries are unordered. */ - if (is_subhash(group[b])) { - ecode = TDB_ERR_CORRUPT; - tdb_logerr(tdb, ecode, - TDB_LOG_ERROR, - "tdb_check: Invalid chain" - " entry subhash"); - goto fail; - } - h = hash_record(tdb, off); - if (h != hprefix) { - ecode = TDB_ERR_CORRUPT; - tdb_logerr(tdb, ecode, - TDB_LOG_ERROR, - "check: bad hash chain" - " placement" - " 0x%llx vs 0x%llx", - (long long)h, - (long long)hprefix); - goto fail; - } - ecode = tdb_read_convert(tdb, off, &rec, - sizeof(rec)); - if (ecode != TDB_SUCCESS) { - goto fail; - } - goto check; - } - - if (is_subhash(group[b])) { - uint64_t subprefix; - subprefix = (hprefix - << (group_bits + TDB_HASH_GROUP_BITS)) - + g * (1 << TDB_HASH_GROUP_BITS) + b; - - ecode = check_hash_record(tdb, - group[b] & TDB_OFF_MASK, - subprefix, - hprefix_bits - + group_bits - + TDB_HASH_GROUP_BITS, - used, num_used, num_found, - check, data); - if (ecode != TDB_SUCCESS) { - goto fail; - } - continue; - } - /* A normal entry */ - - /* Does it belong here at all? */ - h = hash_record(tdb, off); - used_bits = 0; - if (get_bits(h, hprefix_bits, &used_bits) != hprefix - && hprefix_bits) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "check: bad hash placement" - " 0x%llx vs 0x%llx", - (long long)h, - (long long)hprefix); - goto fail; - } - - /* Does it belong in this group? */ - if (get_bits(h, group_bits, &used_bits) != g) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "check: bad group %llu" - " vs %u", - (long long)h, g); - goto fail; - } - - /* Are bucket bits correct? */ - bucket = group[b] & TDB_OFF_HASH_GROUP_MASK; - if (get_bits(h, TDB_HASH_GROUP_BITS, &used_bits) - != bucket) { - used_bits -= TDB_HASH_GROUP_BITS; - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "check: bad bucket %u vs %u", - (unsigned)get_bits(h, - TDB_HASH_GROUP_BITS, - &used_bits), - bucket); - goto fail; - } - - /* There must not be any zero entries between - * the bucket it belongs in and this one! */ - for (i = bucket; - i != b; - i = (i + 1) % (1 << TDB_HASH_GROUP_BITS)) { - if (group[i] == 0) { - ecode = TDB_ERR_CORRUPT; - tdb_logerr(tdb, ecode, - TDB_LOG_ERROR, - "check: bad group placement" - " %u vs %u", - b, bucket); - goto fail; - } - } - - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - goto fail; - } - - /* Bottom bits must match header. */ - if ((h & ((1 << 11)-1)) != rec_hash(&rec)) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: Bad hash magic" - " at offset %llu" - " (0x%llx vs 0x%llx)", - (long long)off, - (long long)h, - (long long)rec_hash(&rec)); - goto fail; - } - - check: - if (check) { - TDB_DATA k, d; - const unsigned char *kptr; - - kptr = tdb_access_read(tdb, - off + sizeof(rec), - rec_key_length(&rec) - + rec_data_length(&rec), - false); - if (TDB_PTR_IS_ERR(kptr)) { - ecode = TDB_PTR_ERR(kptr); - goto fail; - } - - k = tdb_mkdata(kptr, rec_key_length(&rec)); - d = tdb_mkdata(kptr + k.dsize, - rec_data_length(&rec)); - ecode = check(k, d, data); - tdb_access_release(tdb, kptr); - if (ecode != TDB_SUCCESS) { - goto fail; - } - } - } - } - tdb_access_release(tdb, hash); - return TDB_SUCCESS; - -fail: - tdb_access_release(tdb, hash); - return ecode; -} - -static enum TDB_ERROR check_hash(struct tdb_context *tdb, - tdb_off_t used[], - size_t num_used, size_t num_other_used, - enum TDB_ERROR (*check)(TDB_DATA, TDB_DATA, void *), - void *data) -{ - /* Free tables and capabilities also show up as used. */ - size_t num_found = num_other_used; - enum TDB_ERROR ecode; - - ecode = check_hash_tree(tdb, offsetof(struct tdb_header, hashtable), - TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS, - 0, 0, used, num_used, &num_found, - check, data); - if (ecode == TDB_SUCCESS) { - if (num_found != num_used) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Not all entries" - " are in hash"); - } - } - return ecode; -} - -static enum TDB_ERROR check_free(struct tdb_context *tdb, - tdb_off_t off, - const struct tdb_free_record *frec, - tdb_off_t prev, unsigned int ftable, - unsigned int bucket) -{ - enum TDB_ERROR ecode; - - if (frec_magic(frec) != TDB_FREE_MAGIC) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: offset %llu bad magic 0x%llx", - (long long)off, - (long long)frec->magic_and_prev); - } - if (frec_ftable(frec) != ftable) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: offset %llu bad freetable %u", - (long long)off, frec_ftable(frec)); - - } - - ecode = tdb->tdb2.io->oob(tdb, off, - frec_len(frec) - + sizeof(struct tdb_used_record), - false); - if (ecode != TDB_SUCCESS) { - return ecode; - } - if (size_to_bucket(frec_len(frec)) != bucket) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: offset %llu in wrong bucket" - " (%u vs %u)", - (long long)off, - bucket, size_to_bucket(frec_len(frec))); - } - if (prev && prev != frec_prev(frec)) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: offset %llu bad prev" - " (%llu vs %llu)", - (long long)off, - (long long)prev, (long long)frec_len(frec)); - } - return TDB_SUCCESS; -} - -static enum TDB_ERROR check_free_table(struct tdb_context *tdb, - tdb_off_t ftable_off, - unsigned ftable_num, - tdb_off_t fr[], - size_t num_free, - size_t *num_found) -{ - struct tdb_freetable ft; - tdb_off_t h; - unsigned int i; - enum TDB_ERROR ecode; - - ecode = tdb_read_convert(tdb, ftable_off, &ft, sizeof(ft)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - if (rec_magic(&ft.hdr) != TDB_FTABLE_MAGIC - || rec_key_length(&ft.hdr) != 0 - || rec_data_length(&ft.hdr) != sizeof(ft) - sizeof(ft.hdr) - || rec_hash(&ft.hdr) != 0) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Invalid header on free table"); - } - - for (i = 0; i < TDB_FREE_BUCKETS; i++) { - tdb_off_t off, prev = 0, *p, first = 0; - struct tdb_free_record f; - - h = bucket_off(ftable_off, i); - for (off = tdb_read_off(tdb, h); off; off = f.next) { - if (TDB_OFF_IS_ERR(off)) { - return TDB_OFF_TO_ERR(off); - } - if (!first) { - off &= TDB_OFF_MASK; - first = off; - } - ecode = tdb_read_convert(tdb, off, &f, sizeof(f)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - ecode = check_free(tdb, off, &f, prev, ftable_num, i); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* FIXME: Check hash bits */ - p = asearch(&off, fr, num_free, off_cmp); - if (!p) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: Invalid offset" - " %llu in free table", - (long long)off); - } - /* Mark it invalid. */ - *p ^= 1; - (*num_found)++; - prev = off; - } - - if (first) { - /* Now we can check first back pointer. */ - ecode = tdb_read_convert(tdb, first, &f, sizeof(f)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - ecode = check_free(tdb, first, &f, prev, ftable_num, i); - if (ecode != TDB_SUCCESS) { - return ecode; - } - } - } - return TDB_SUCCESS; -} - -/* Slow, but should be very rare. */ -tdb_off_t dead_space(struct tdb_context *tdb, tdb_off_t off) -{ - size_t len; - enum TDB_ERROR ecode; - - for (len = 0; off + len < tdb->file->map_size; len++) { - char c; - ecode = tdb->tdb2.io->tread(tdb, off, &c, 1); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - if (c != 0 && c != 0x43) - break; - } - return len; -} - -static enum TDB_ERROR check_linear(struct tdb_context *tdb, - tdb_off_t **used, size_t *num_used, - tdb_off_t **fr, size_t *num_free, - uint64_t features, tdb_off_t recovery) -{ - tdb_off_t off; - tdb_len_t len; - enum TDB_ERROR ecode; - bool found_recovery = false; - - for (off = sizeof(struct tdb_header); - off < tdb->file->map_size; - off += len) { - union { - struct tdb_used_record u; - struct tdb_free_record f; - struct tdb_recovery_record r; - } rec; - /* r is larger: only get that if we need to. */ - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec.f)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* If we crash after ftruncate, we can get zeroes or fill. */ - if (rec.r.magic == TDB_RECOVERY_INVALID_MAGIC - || rec.r.magic == 0x4343434343434343ULL) { - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec.r)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - if (recovery == off) { - found_recovery = true; - len = sizeof(rec.r) + rec.r.max_len; - } else { - len = dead_space(tdb, off); - if (TDB_OFF_IS_ERR(len)) { - return TDB_OFF_TO_ERR(len); - } - if (len < sizeof(rec.r)) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: invalid" - " dead space at %zu", - (size_t)off); - } - - tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING, - "Dead space at %zu-%zu (of %zu)", - (size_t)off, (size_t)(off + len), - (size_t)tdb->file->map_size); - } - } else if (rec.r.magic == TDB_RECOVERY_MAGIC) { - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec.r)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - if (recovery != off) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: unexpected" - " recovery record at offset" - " %zu", - (size_t)off); - } - if (rec.r.len > rec.r.max_len) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: invalid recovery" - " length %zu", - (size_t)rec.r.len); - } - if (rec.r.eof > tdb->file->map_size) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: invalid old EOF" - " %zu", (size_t)rec.r.eof); - } - found_recovery = true; - len = sizeof(rec.r) + rec.r.max_len; - } else if (frec_magic(&rec.f) == TDB_FREE_MAGIC) { - len = sizeof(rec.u) + frec_len(&rec.f); - if (off + len > tdb->file->map_size) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: free overlength" - " %llu at offset %llu", - (long long)len, - (long long)off); - } - /* This record should be in free lists. */ - if (frec_ftable(&rec.f) != TDB_FTABLE_NONE - && !append(fr, num_free, off)) { - return tdb_logerr(tdb, TDB_ERR_OOM, - TDB_LOG_ERROR, - "tdb_check: tracking %zu'th" - " free record.", *num_free); - } - } else if (rec_magic(&rec.u) == TDB_USED_MAGIC - || rec_magic(&rec.u) == TDB_CHAIN_MAGIC - || rec_magic(&rec.u) == TDB_HTABLE_MAGIC - || rec_magic(&rec.u) == TDB_FTABLE_MAGIC - || rec_magic(&rec.u) == TDB_CAP_MAGIC) { - uint64_t klen, dlen, extra; - - /* This record is used! */ - if (!append(used, num_used, off)) { - return tdb_logerr(tdb, TDB_ERR_OOM, - TDB_LOG_ERROR, - "tdb_check: tracking %zu'th" - " used record.", *num_used); - } - - klen = rec_key_length(&rec.u); - dlen = rec_data_length(&rec.u); - extra = rec_extra_padding(&rec.u); - - len = sizeof(rec.u) + klen + dlen + extra; - if (off + len > tdb->file->map_size) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: used overlength" - " %llu at offset %llu", - (long long)len, - (long long)off); - } - - if (len < sizeof(rec.f)) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: too short record" - " %llu at %llu", - (long long)len, - (long long)off); - } - - /* Check that records have correct 0 at end (but may - * not in future). */ - if (extra && !features - && rec_magic(&rec.u) != TDB_CAP_MAGIC) { - const char *p; - char c; - p = tdb_access_read(tdb, off + sizeof(rec.u) - + klen + dlen, 1, false); - if (TDB_PTR_IS_ERR(p)) - return TDB_PTR_ERR(p); - c = *p; - tdb_access_release(tdb, p); - - if (c != '\0') { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check:" - " non-zero extra" - " at %llu", - (long long)off); - } - } - } else { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb_check: Bad magic 0x%llx" - " at offset %zu", - (long long)rec_magic(&rec.u), - (size_t)off); - } - } - - /* We must have found recovery area if there was one. */ - if (recovery != 0 && !found_recovery) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: expected a recovery area at %zu", - (size_t)recovery); - } - - return TDB_SUCCESS; -} - -enum TDB_ERROR tdb_check_(struct tdb_context *tdb, - enum TDB_ERROR (*check)(TDB_DATA, TDB_DATA, void *), - void *data) -{ - tdb_off_t *fr = NULL, *used = NULL, ft, recovery; - size_t num_free = 0, num_used = 0, num_found = 0, num_ftables = 0, - num_capabilities = 0; - uint64_t features; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_CANT_CHECK) { - return tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING, - "tdb_check: database has unknown capability," - " cannot check."); - } - - if (tdb->flags & TDB_VERSION1) { - if (tdb1_check(tdb, check, data) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - - ecode = tdb_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_WAIT, false); - if (ecode != TDB_SUCCESS) { - return tdb->last_error = ecode; - } - - ecode = tdb_lock_expand(tdb, F_RDLCK); - if (ecode != TDB_SUCCESS) { - tdb_allrecord_unlock(tdb, F_RDLCK); - return tdb->last_error = ecode; - } - - ecode = check_header(tdb, &recovery, &features, &num_capabilities); - if (ecode != TDB_SUCCESS) - goto out; - - /* First we do a linear scan, checking all records. */ - ecode = check_linear(tdb, &used, &num_used, &fr, &num_free, features, - recovery); - if (ecode != TDB_SUCCESS) - goto out; - - for (ft = first_ftable(tdb); ft; ft = next_ftable(tdb, ft)) { - if (TDB_OFF_IS_ERR(ft)) { - ecode = TDB_OFF_TO_ERR(ft); - goto out; - } - ecode = check_free_table(tdb, ft, num_ftables, fr, num_free, - &num_found); - if (ecode != TDB_SUCCESS) - goto out; - num_ftables++; - } - - /* FIXME: Check key uniqueness? */ - ecode = check_hash(tdb, used, num_used, num_ftables + num_capabilities, - check, data); - if (ecode != TDB_SUCCESS) - goto out; - - if (num_found != num_free) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_check: Not all entries are in" - " free table"); - } - -out: - tdb_allrecord_unlock(tdb, F_RDLCK); - tdb_unlock_expand(tdb, F_RDLCK); - free(fr); - free(used); - return tdb->last_error = ecode; -} diff --git a/ccan/tdb2/doc/TDB1_porting.txt b/ccan/tdb2/doc/TDB1_porting.txt deleted file mode 100644 index ef305cab..00000000 --- a/ccan/tdb2/doc/TDB1_porting.txt +++ /dev/null @@ -1,72 +0,0 @@ -Interface differences between TDB1 and TDB2. - -- tdb2 uses 'struct tdb_data', tdb1 uses 'struct TDB_DATA'. Use the - TDB_DATA typedef if you want portability between the two. - -- tdb2 functions return 0 on success, and a negative error on failure, - whereas tdb1 functions returned 0 on success, and -1 on failure. - tdb1 then used tdb_error() to determine the error; this is also - supported in tdb2 to ease backwards compatibility, though the other - form is preferred. - -- tdb2's tdb_fetch() returns an error, tdb1's returned the data directly - (or tdb_null, and you were supposed to check tdb_error() to find out why). - -- tdb2's tdb_nextkey() frees the old key's dptr, in tdb2 you needed to do - this manually. - -- tdb1's tdb_open/tdb_open_ex took an explicit hash size. tdb2's hash table - resizes as required. - -- tdb2 uses a linked list of attribute structures to implement logging and - alternate hashes. tdb1 used tdb_open_ex, which was not extensible. - -- tdb2 does locking on read-only databases (ie. O_RDONLY passed to tdb_open). - tdb1 did not: use the TDB_NOLOCK flag if you want to suppress locking. - -- tdb2's log function is simpler than tdb1's log function. The string is - already formatted, and it takes an enum tdb_log_level not a tdb_debug_level, - and which has only three values: TDB_LOG_ERROR, TDB_LOG_USE_ERROR and - TDB_LOG_WARNING. - -- tdb2 provides tdb_deq() for comparing two struct tdb_data. - -- tdb2's tdb_name() returns a copy of the name even for TDB_INTERNAL dbs. - -- tdb2 does not need tdb_reopen() or tdb_reopen_all(). If you call - fork() after during certain operations the child should close the - tdb, or complete the operations before continuing to use the tdb: - - tdb_transaction_start(): child must tdb_transaction_cancel() - tdb_lockall(): child must call tdb_unlockall() - tdb_lockall_read(): child must call tdb_unlockall_read() - tdb_chainlock(): child must call tdb_chainunlock() - tdb_parse() callback: child must return from tdb_parse() - -- tdb2 will not open a non-tdb file, even if O_CREAT is specified. - -- There is no tdb_traverse_read. For operating on TDB1 files, you can - simulate it by tdb_add_flag(tdb, TDB_RDONLY); tdb_traverse(); - tdb_remove_flag(tdb, TDB_RDONLY). This may be desirable because - traverse on TDB1 files use a write lock on the entire database - unless it's read-only. - -- Failure inside a transaction (such as a lock function failing) does - not implicitly cancel the transaction; you still need to call - tdb_transaction_cancel(). - -TDB1 Compatibility: - -- tdb2's offers a tdb1_incompatible_hash function, which is the same - as the default hash with the TDB_INCOMPATIBLE_HASH flag. There is - no way of marking an old TDB incompatible with versions < 1.2.6 - while using any other hash. - -- The TDB_ATTRIBUTE_TDB1_HASHSIZE attribute can be used to control the - hash size, but only when creating (ie. O_CREAT) a TDB1 - (ie. TDB_VERSION1). - -- There is no TDB_CLEAR_IF_FIRST flag; it has severe scalability and - API problems. If necessary, you can emulate this by using the open - hook and placing a 1-byte lock at offset 4. If your program forks, - you will need to place this lock again in the child. diff --git a/ccan/tdb2/doc/design-1.3.txt b/ccan/tdb2/doc/design-1.3.txt deleted file mode 100644 index 651ada08..00000000 --- a/ccan/tdb2/doc/design-1.3.txt +++ /dev/null @@ -1,1050 +0,0 @@ -TDB2: A Redesigning The Trivial DataBase - -Rusty Russell, IBM Corporation - -27-April-2010 - -Abstract - -The Trivial DataBase on-disk format is 32 bits; with usage cases -heading towards the 4G limit, that must change. This required -breakage provides an opportunity to revisit TDB's other design -decisions and reassess them. - -1 Introduction - -The Trivial DataBase was originally written by Andrew Tridgell as -a simple key/data pair storage system with the same API as dbm, -but allowing multiple readers and writers while being small -enough (< 1000 lines of C) to include in SAMBA. The simple design -created in 1999 has proven surprisingly robust and performant, -used in Samba versions 3 and 4 as well as numerous other -projects. Its useful life was greatly increased by the -(backwards-compatible!) addition of transaction support in 2005. - -The wider variety and greater demands of TDB-using code has lead -to some organic growth of the API, as well as some compromises on -the implementation. None of these, by themselves, are seen as -show-stoppers, but the cumulative effect is to a loss of elegance -over the initial, simple TDB implementation. Here is a table of -the approximate number of lines of implementation code and number -of API functions at the end of each year: - - -+-----------+----------------+--------------------------------+ -| Year End | API Functions | Lines of C Code Implementation | -+-----------+----------------+--------------------------------+ -+-----------+----------------+--------------------------------+ -| 1999 | 13 | 1195 | -+-----------+----------------+--------------------------------+ -| 2000 | 24 | 1725 | -+-----------+----------------+--------------------------------+ -| 2001 | 32 | 2228 | -+-----------+----------------+--------------------------------+ -| 2002 | 35 | 2481 | -+-----------+----------------+--------------------------------+ -| 2003 | 35 | 2552 | -+-----------+----------------+--------------------------------+ -| 2004 | 40 | 2584 | -+-----------+----------------+--------------------------------+ -| 2005 | 38 | 2647 | -+-----------+----------------+--------------------------------+ -| 2006 | 52 | 3754 | -+-----------+----------------+--------------------------------+ -| 2007 | 66 | 4398 | -+-----------+----------------+--------------------------------+ -| 2008 | 71 | 4768 | -+-----------+----------------+--------------------------------+ -| 2009 | 73 | 5715 | -+-----------+----------------+--------------------------------+ - - -This review is an attempt to catalog and address all the known -issues with TDB and create solutions which address the problems -without significantly increasing complexity; all involved are far -too aware of the dangers of second system syndrome in rewriting a -successful project like this. - -2 API Issues - -2.1 tdb_open_ex Is Not Expandable - -The tdb_open() call was expanded to tdb_open_ex(), which added an -optional hashing function and an optional logging function -argument. Additional arguments to open would require the -introduction of a tdb_open_ex2 call etc. - -2.1.1 Proposed Solution - -tdb_open() will take a linked-list of attributes: - -enum tdb_attribute { - - TDB_ATTRIBUTE_LOG = 0, - - TDB_ATTRIBUTE_HASH = 1 - -}; - -struct tdb_attribute_base { - - enum tdb_attribute attr; - - union tdb_attribute *next; - -}; - -struct tdb_attribute_log { - - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_LOG -*/ - - tdb_log_func log_fn; - - void *log_private; - -}; - -struct tdb_attribute_hash { - - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_HASH -*/ - - tdb_hash_func hash_fn; - - void *hash_private; - -}; - -union tdb_attribute { - - struct tdb_attribute_base base; - - struct tdb_attribute_log log; - - struct tdb_attribute_hash hash; - -}; - -This allows future attributes to be added, even if this expands -the size of the union. - -2.2 tdb_traverse Makes Impossible Guarantees - -tdb_traverse (and tdb_firstkey/tdb_nextkey) predate transactions, -and it was thought that it was important to guarantee that all -records which exist at the start and end of the traversal would -be included, and no record would be included twice. - -This adds complexity (see[Reliable-Traversal-Adds]) and does not -work anyway for records which are altered (in particular, those -which are expanded may be effectively deleted and re-added behind -the traversal). - -2.2.1 Proposed Solution - -Abandon the guarantee. You will see every record if no changes -occur during your traversal, otherwise you will see some subset. -You can prevent changes by using a transaction or the locking -API. - -2.3 Nesting of Transactions Is Fraught - -TDB has alternated between allowing nested transactions and not -allowing them. Various paths in the Samba codebase assume that -transactions will nest, and in a sense they can: the operation is -only committed to disk when the outer transaction is committed. -There are two problems, however: - -1. Canceling the inner transaction will cause the outer - transaction commit to fail, and will not undo any operations - since the inner transaction began. This problem is soluble with - some additional internal code. - -2. An inner transaction commit can be cancelled by the outer - transaction. This is desirable in the way which Samba's - database initialization code uses transactions, but could be a - surprise to any users expecting a successful transaction commit - to expose changes to others. - -The current solution is to specify the behavior at tdb_open(), -with the default currently that nested transactions are allowed. -This flag can also be changed at runtime. - -2.3.1 Proposed Solution - -Given the usage patterns, it seems that the “least-surprise” -behavior of disallowing nested transactions should become the -default. Additionally, it seems the outer transaction is the only -code which knows whether inner transactions should be allowed, so -a flag to indicate this could be added to tdb_transaction_start. -However, this behavior can be simulated with a wrapper which uses -tdb_add_flags() and tdb_remove_flags(), so the API should not be -expanded for this relatively-obscure case. - -2.4 Incorrect Hash Function is Not Detected - -tdb_open_ex() allows the calling code to specify a different hash -function to use, but does not check that all other processes -accessing this tdb are using the same hash function. The result -is that records are missing from tdb_fetch(). - -2.4.1 Proposed Solution - -The header should contain an example hash result (eg. the hash of -0xdeadbeef), and tdb_open_ex() should check that the given hash -function produces the same answer, or fail the tdb_open call. - -2.5 tdb_set_max_dead/TDB_VOLATILE Expose Implementation - -In response to scalability issues with the free list ([TDB-Freelist-Is] -) two API workarounds have been incorporated in TDB: -tdb_set_max_dead() and the TDB_VOLATILE flag to tdb_open. The -latter actually calls the former with an argument of “5”. - -This code allows deleted records to accumulate without putting -them in the free list. On delete we iterate through each chain -and free them in a batch if there are more than max_dead entries. -These are never otherwise recycled except as a side-effect of a -tdb_repack. - -2.5.1 Proposed Solution - -With the scalability problems of the freelist solved, this API -can be removed. The TDB_VOLATILE flag may still be useful as a -hint that store and delete of records will be at least as common -as fetch in order to allow some internal tuning, but initially -will become a no-op. - -2.6 TDB Files Cannot Be Opened Multiple Times - In The Same Process - -No process can open the same TDB twice; we check and disallow it. -This is an unfortunate side-effect of fcntl locks, which operate -on a per-file rather than per-file-descriptor basis, and do not -nest. Thus, closing any file descriptor on a file clears all the -locks obtained by this process, even if they were placed using a -different file descriptor! - -Note that even if this were solved, deadlock could occur if -operations were nested: this is a more manageable programming -error in most cases. - -2.6.1 Proposed Solution - -We could lobby POSIX to fix the perverse rules, or at least lobby -Linux to violate them so that the most common implementation does -not have this restriction. This would be a generally good idea -for other fcntl lock users. - -Samba uses a wrapper which hands out the same tdb_context to -multiple callers if this happens, and does simple reference -counting. We should do this inside the tdb library, which already -emulates lock nesting internally; it would need to recognize when -deadlock occurs within a single process. This would create a new -failure mode for tdb operations (while we currently handle -locking failures, they are impossible in normal use and a process -encountering them can do little but give up). - -I do not see benefit in an additional tdb_open flag to indicate -whether re-opening is allowed, as though there may be some -benefit to adding a call to detect when a tdb_context is shared, -to allow other to create such an API. - -2.7 TDB API Is Not POSIX Thread-safe - -The TDB API uses an error code which can be queried after an -operation to determine what went wrong. This programming model -does not work with threads, unless specific additional guarantees -are given by the implementation. In addition, even -otherwise-independent threads cannot open the same TDB (as in [TDB-Files-Cannot] -). - -2.7.1 Proposed Solution - -Reachitecting the API to include a tdb_errcode pointer would be a -great deal of churn; we are better to guarantee that the -tdb_errcode is per-thread so the current programming model can be -maintained. - -This requires dynamic per-thread allocations, which is awkward -with POSIX threads (pthread_key_create space is limited and we -cannot simply allocate a key for every TDB). - -Internal locking is required to make sure that fcntl locks do not -overlap between threads, and also that the global list of tdbs is -maintained. - -The aim is that building tdb with -DTDB_PTHREAD will result in a -pthread-safe version of the library, and otherwise no overhead -will exist. - -2.8 *_nonblock Functions And *_mark Functions Expose - Implementation - -CTDB[footnote: -Clustered TDB, see http://ctdb.samba.org -] wishes to operate on TDB in a non-blocking manner. This is -currently done as follows: - -1. Call the _nonblock variant of an API function (eg. - tdb_lockall_nonblock). If this fails: - -2. Fork a child process, and wait for it to call the normal - variant (eg. tdb_lockall). - -3. If the child succeeds, call the _mark variant to indicate we - already have the locks (eg. tdb_lockall_mark). - -4. Upon completion, tell the child to release the locks (eg. - tdb_unlockall). - -5. Indicate to tdb that it should consider the locks removed (eg. - tdb_unlockall_mark). - -There are several issues with this approach. Firstly, adding two -new variants of each function clutters the API for an obscure -use, and so not all functions have three variants. Secondly, it -assumes that all paths of the functions ask for the same locks, -otherwise the parent process will have to get a lock which the -child doesn't have under some circumstances. I don't believe this -is currently the case, but it constrains the implementation. - -2.8.1 Proposed Solution - -Implement a hook for locking methods, so that the caller can -control the calls to create and remove fcntl locks. In this -scenario, ctdbd would operate as follows: - -1. Call the normal API function, eg tdb_lockall(). - -2. When the lock callback comes in, check if the child has the - lock. Initially, this is always false. If so, return 0. - Otherwise, try to obtain it in non-blocking mode. If that - fails, return EWOULDBLOCK. - -3. Release locks in the unlock callback as normal. - -4. If tdb_lockall() fails, see if we recorded a lock failure; if - so, call the child to repeat the operation. - -5. The child records what locks it obtains, and returns that - information to the parent. - -6. When the child has succeeded, goto 1. - -This is flexible enough to handle any potential locking scenario, -even when lock requirements change. It can be optimized so that -the parent does not release locks, just tells the child which -locks it doesn't need to obtain. - -It also keeps the complexity out of the API, and in ctdbd where -it is needed. - -2.9 tdb_chainlock Functions Expose Implementation - -tdb_chainlock locks some number of records, including the record -indicated by the given key. This gave atomicity guarantees; -no-one can start a transaction, alter, read or delete that key -while the lock is held. - -It also makes the same guarantee for any other key in the chain, -which is an internal implementation detail and potentially a -cause for deadlock. - -2.9.1 Proposed Solution - -None. It would be nice to have an explicit single entry lock -which effected no other keys. Unfortunately, this won't work for -an entry which doesn't exist. Thus while chainlock may be -implemented more efficiently for the existing case, it will still -have overlap issues with the non-existing case. So it is best to -keep the current (lack of) guarantee about which records will be -effected to avoid constraining our implementation. - -2.10 Signal Handling is Not Race-Free - -The tdb_setalarm_sigptr() call allows the caller's signal handler -to indicate that the tdb locking code should return with a -failure, rather than trying again when a signal is received (and -errno == EAGAIN). This is usually used to implement timeouts. - -Unfortunately, this does not work in the case where the signal is -received before the tdb code enters the fcntl() call to place the -lock: the code will sleep within the fcntl() code, unaware that -the signal wants it to exit. In the case of long timeouts, this -does not happen in practice. - -2.10.1 Proposed Solution - -The locking hooks proposed in[Proposed-Solution-locking-hook] -would allow the user to decide on whether to fail the lock -acquisition on a signal. This allows the caller to choose their -own compromise: they could narrow the race by checking -immediately before the fcntl call.[footnote: -It may be possible to make this race-free in some implementations -by having the signal handler alter the struct flock to make it -invalid. This will cause the fcntl() lock call to fail with -EINVAL if the signal occurs before the kernel is entered, -otherwise EAGAIN. -] - -2.11 The API Uses Gratuitous Typedefs, Capitals - -typedefs are useful for providing source compatibility when types -can differ across implementations, or arguably in the case of -function pointer definitions which are hard for humans to parse. -Otherwise it is simply obfuscation and pollutes the namespace. - -Capitalization is usually reserved for compile-time constants and -macros. - - TDB_CONTEXT There is no reason to use this over 'struct - tdb_context'; the definition isn't visible to the API user - anyway. - - TDB_DATA There is no reason to use this over struct TDB_DATA; - the struct needs to be understood by the API user. - - struct TDB_DATA This would normally be called 'struct - tdb_data'. - - enum TDB_ERROR Similarly, this would normally be enum - tdb_error. - -2.11.1 Proposed Solution - -None. Introducing lower case variants would please pedants like -myself, but if it were done the existing ones should be kept. -There is little point forcing a purely cosmetic change upon tdb -users. - -2.12 tdb_log_func Doesn't Take The - Private Pointer - -For API compatibility reasons, the logging function needs to call -tdb_get_logging_private() to retrieve the pointer registered by -the tdb_open_ex for logging. - -2.12.1 Proposed Solution - -It should simply take an extra argument, since we are prepared to -break the API/ABI. - -2.13 Various Callback Functions Are Not Typesafe - -The callback functions in tdb_set_logging_function (after [tdb_log_func-Doesnt-Take] - is resolved), tdb_parse_record, tdb_traverse, tdb_traverse_read -and tdb_check all take void * and must internally convert it to -the argument type they were expecting. - -If this type changes, the compiler will not produce warnings on -the callers, since it only sees void *. - -2.13.1 Proposed Solution - -With careful use of macros, we can create callback functions -which give a warning when used on gcc and the types of the -callback and its private argument differ. Unsupported compilers -will not give a warning, which is no worse than now. In addition, -the callbacks become clearer, as they need not use void * for -their parameter. - -See CCAN's typesafe_cb module at -http://ccan.ozlabs.org/info/typesafe_cb.html - -2.14 TDB_CLEAR_IF_FIRST Must Be Specified On All Opens, - tdb_reopen_all Problematic - -The TDB_CLEAR_IF_FIRST flag to tdb_open indicates that the TDB -file should be cleared if the caller discovers it is the only -process with the TDB open. However, if any caller does not -specify TDB_CLEAR_IF_FIRST it will not be detected, so will have -the TDB erased underneath them (usually resulting in a crash). - -There is a similar issue on fork(); if the parent exits (or -otherwise closes the tdb) before the child calls tdb_reopen_all() -to establish the lock used to indicate the TDB is opened by -someone, a TDB_CLEAR_IF_FIRST opener at that moment will believe -it alone has opened the TDB and will erase it. - -2.14.1 Proposed Solution - -Remove TDB_CLEAR_IF_FIRST. Other workarounds are possible, but -see [TDB_CLEAR_IF_FIRST-Imposes-Performance]. - -3 Performance And Scalability Issues - -3.1 TDB_CLEAR_IF_FIRST - Imposes Performance Penalty - -When TDB_CLEAR_IF_FIRST is specified, a 1-byte read lock is -placed at offset 4 (aka. the ACTIVE_LOCK). While these locks -never conflict in normal tdb usage, they do add substantial -overhead for most fcntl lock implementations when the kernel -scans to detect if a lock conflict exists. This is often a single -linked list, making the time to acquire and release a fcntl lock -O(N) where N is the number of processes with the TDB open, not -the number actually doing work. - -In a Samba server it is common to have huge numbers of clients -sitting idle, and thus they have weaned themselves off the -TDB_CLEAR_IF_FIRST flag.[footnote: -There is a flag to tdb_reopen_all() which is used for this -optimization: if the parent process will outlive the child, the -child does not need the ACTIVE_LOCK. This is a workaround for -this very performance issue. -] - -3.1.1 Proposed Solution - -Remove the flag. It was a neat idea, but even trivial servers -tend to know when they are initializing for the first time and -can simply unlink the old tdb at that point. - -3.2 TDB Files Have a 4G Limit - -This seems to be becoming an issue (so much for “trivial”!), -particularly for ldb. - -3.2.1 Proposed Solution - -A new, incompatible TDB format which uses 64 bit offsets -internally rather than 32 bit as now. For simplicity of endian -conversion (which TDB does on the fly if required), all values -will be 64 bit on disk. In practice, some upper bits may be used -for other purposes, but at least 56 bits will be available for -file offsets. - -tdb_open() will automatically detect the old version, and even -create them if TDB_VERSION6 is specified to tdb_open. - -32 bit processes will still be able to access TDBs larger than 4G -(assuming that their off_t allows them to seek to 64 bits), they -will gracefully fall back as they fail to mmap. This can happen -already with large TDBs. - -Old versions of tdb will fail to open the new TDB files (since 28 -August 2009, commit 398d0c29290: prior to that any unrecognized -file format would be erased and initialized as a fresh tdb!) - -3.3 TDB Records Have a 4G Limit - -This has not been a reported problem, and the API uses size_t -which can be 64 bit on 64 bit platforms. However, other limits -may have made such an issue moot. - -3.3.1 Proposed Solution - -Record sizes will be 64 bit, with an error returned on 32 bit -platforms which try to access such records (the current -implementation would return TDB_ERR_OOM in a similar case). It -seems unlikely that 32 bit keys will be a limitation, so the -implementation may not support this (see [sub:Records-Incur-A]). - -3.4 Hash Size Is Determined At TDB Creation Time - -TDB contains a number of hash chains in the header; the number is -specified at creation time, and defaults to 131. This is such a -bottleneck on large databases (as each hash chain gets quite -long), that LDB uses 10,000 for this hash. In general it is -impossible to know what the 'right' answer is at database -creation time. - -3.4.1 Proposed Solution - -After comprehensive performance testing on various scalable hash -variants[footnote: -http://rusty.ozlabs.org/?p=89 and http://rusty.ozlabs.org/?p=94 -This was annoying because I was previously convinced that an -expanding tree of hashes would be very close to optimal. -], it became clear that it is hard to beat a straight linear hash -table which doubles in size when it reaches saturation. There are -three details which become important: - -1. On encountering a full bucket, we use the next bucket. - -2. Extra hash bits are stored with the offset, to reduce - comparisons. - -3. A marker entry is used on deleting an entry. - -The doubling of the table must be done under a transaction; we -will not reduce it on deletion, so it will be an unusual case. It -will either be placed at the head (other entries will be moved -out the way so we can expand). We could have a pointer in the -header to the current hashtable location, but that pointer would -have to be read frequently to check for hashtable moves. - -The locking for this is slightly more complex than the chained -case; we currently have one lock per bucket, and that means we -would need to expand the lock if we overflow to the next bucket. -The frequency of such collisions will effect our locking -heuristics: we can always lock more buckets than we need. - -One possible optimization is to only re-check the hash size on an -insert or a lookup miss. - -3.5 TDB Freelist Is Highly Contended - -TDB uses a single linked list for the free list. Allocation -occurs as follows, using heuristics which have evolved over time: - -1. Get the free list lock for this whole operation. - -2. Multiply length by 1.25, so we always over-allocate by 25%. - -3. Set the slack multiplier to 1. - -4. Examine the current freelist entry: if it is > length but < - the current best case, remember it as the best case. - -5. Multiply the slack multiplier by 1.05. - -6. If our best fit so far is less than length * slack multiplier, - return it. The slack will be turned into a new free record if - it's large enough. - -7. Otherwise, go onto the next freelist entry. - -Deleting a record occurs as follows: - -1. Lock the hash chain for this whole operation. - -2. Walk the chain to find the record, keeping the prev pointer - offset. - -3. If max_dead is non-zero: - - (a) Walk the hash chain again and count the dead records. - - (b) If it's more than max_dead, bulk free all the dead ones - (similar to steps 4 and below, but the lock is only obtained - once). - - (c) Simply mark this record as dead and return. - -4. Get the free list lock for the remainder of this operation. - -5. Examine the following block to see if it is - free; if so, enlarge the current block and remove that block - from the free list. This was disabled, as removal from the free - list was O(entries-in-free-list). - -6. Examine the preceeding block to see if it is free: for this - reason, each block has a 32-bit tailer which indicates its - length. If it is free, expand it to cover our new block and - return. - -7. Otherwise, prepend ourselves to the free list. - -Disabling right-merging (step [right-merging]) causes -fragmentation; the other heuristics proved insufficient to -address this, so the final answer to this was that when we expand -the TDB file inside a transaction commit, we repack the entire -tdb. - -The single list lock limits our allocation rate; due to the other -issues this is not currently seen as a bottleneck. - -3.5.1 Proposed Solution - -The first step is to remove all the current heuristics, as they -obviously interact, then examine them once the lock contention is -addressed. - -The free list must be split to reduce contention. Assuming -perfect free merging, we can at most have 1 free list entry for -each entry. This implies that the number of free lists is related -to the size of the hash table, but as it is rare to walk a large -number of free list entries we can use far fewer, say 1/32 of the -number of hash buckets. - -There are various benefits in using per-size free lists (see [sub:TDB-Becomes-Fragmented] -) but it's not clear this would reduce contention in the common -case where all processes are allocating/freeing the same size. -Thus we almost certainly need to divide in other ways: the most -obvious is to divide the file into zones, and using a free list -(or set of free lists) for each. This approximates address -ordering. - -Note that this means we need to split the free lists when we -expand the file; this is probably acceptable when we double the -hash table size, since that is such an expensive operation -already. In the case of increasing the file size, there is an -optimization we can use: if we use M in the formula above as the -file size rounded up to the next power of 2, we only need -reshuffle free lists when the file size crosses a power of 2 -boundary, and reshuffling the free lists is trivial: we simply -merge every consecutive pair of free lists. - -The basic algorithm is as follows. Freeing is simple: - -1. Identify the correct zone. - -2. Lock the corresponding list. - -3. Re-check the zone (we didn't have a lock, sizes could have - changed): relock if necessary. - -4. Place the freed entry in the list for that zone. - -Allocation is a little more complicated, as we perform delayed -coalescing at this point: - -1. Pick a zone either the zone we last freed into, or based on a “ - random” number. - -2. Lock the corresponding list. - -3. Re-check the zone: relock if necessary. - -4. If the top entry is -large enough, remove it from the list and - return it. - -5. Otherwise, coalesce entries in the list. - - (a) - - (b) - - (c) - - (d) - -6. If there was no entry large enough, unlock the list and try - the next zone. - -7. - -8. - -9. If no zone satisfies, expand the file. - -This optimizes rapid insert/delete of free list entries by not -coalescing them all the time.. First-fit address ordering -ordering seems to be fairly good for keeping fragmentation low -(see [sub:TDB-Becomes-Fragmented]). Note that address ordering -does not need a tailer to coalesce, though if we needed one we -could have one cheaply: see [sub:Records-Incur-A]. - - - -I anticipate that the number of entries in each free zone would -be small, but it might be worth using one free entry to hold -pointers to the others for cache efficiency. - -3.6 TDB Becomes Fragmented - -Much of this is a result of allocation strategy[footnote: -The Memory Fragmentation Problem: Solved? Johnstone & Wilson 1995 -ftp://ftp.cs.utexas.edu/pub/garbage/malloc/ismm98.ps -] and deliberate hobbling of coalescing; internal fragmentation -(aka overallocation) is deliberately set at 25%, and external -fragmentation is only cured by the decision to repack the entire -db when a transaction commit needs to enlarge the file. - -3.6.1 Proposed Solution - -The 25% overhead on allocation works in practice for ldb because -indexes tend to expand by one record at a time. This internal -fragmentation can be resolved by having an “expanded” bit in the -header to note entries that have previously expanded, and -allocating more space for them. - -There are is a spectrum of possible solutions for external -fragmentation: one is to use a fragmentation-avoiding allocation -strategy such as best-fit address-order allocator. The other end -of the spectrum would be to use a bump allocator (very fast and -simple) and simply repack the file when we reach the end. - -There are three problems with efficient fragmentation-avoiding -allocators: they are non-trivial, they tend to use a single free -list for each size, and there's no evidence that tdb allocation -patterns will match those recorded for general allocators (though -it seems likely). - -Thus we don't spend too much effort on external fragmentation; we -will be no worse than the current code if we need to repack on -occasion. More effort is spent on reducing freelist contention, -and reducing overhead. - -3.7 Records Incur A 28-Byte Overhead - -Each TDB record has a header as follows: - -struct tdb_record { - - tdb_off_t next; /* offset of the next record in the list -*/ - - tdb_len_t rec_len; /* total byte length of record */ - - tdb_len_t key_len; /* byte length of key */ - - tdb_len_t data_len; /* byte length of data */ - - uint32_t full_hash; /* the full 32 bit hash of the key */ - - uint32_t magic; /* try to catch errors */ - - /* the following union is implied: - - union { - - char record[rec_len]; - - struct { - - char key[key_len]; - - char data[data_len]; - - } - - uint32_t totalsize; (tailer) - - } - - */ - -}; - -Naively, this would double to a 56-byte overhead on a 64 bit -implementation. - -3.7.1 Proposed Solution - -We can use various techniques to reduce this for an allocated -block: - -1. The 'next' pointer is not required, as we are using a flat - hash table. - -2. 'rec_len' can instead be expressed as an addition to key_len - and data_len (it accounts for wasted or overallocated length in - the record). Since the record length is always a multiple of 8, - we can conveniently fit it in 32 bits (representing up to 35 - bits). - -3. 'key_len' and 'data_len' can be reduced. I'm unwilling to - restrict 'data_len' to 32 bits, but instead we can combine the - two into one 64-bit field and using a 5 bit value which - indicates at what bit to divide the two. Keys are unlikely to - scale as fast as data, so I'm assuming a maximum key size of 32 - bits. - -4. 'full_hash' is used to avoid a memcmp on the “miss” case, but - this is diminishing returns after a handful of bits (at 10 - bits, it reduces 99.9% of false memcmp). As an aside, as the - lower bits are already incorporated in the hash table - resolution, the upper bits should be used here. - -5. 'magic' does not need to be enlarged: it currently reflects - one of 5 values (used, free, dead, recovery, and - unused_recovery). It is useful for quick sanity checking - however, and should not be eliminated. - -6. 'tailer' is only used to coalesce free blocks (so a block to - the right can find the header to check if this block is free). - This can be replaced by a single 'free' bit in the header of - the following block (and the tailer only exists in free - blocks).[footnote: -This technique from Thomas Standish. Data Structure Techniques. -Addison-Wesley, Reading, Massachusetts, 1980. -] The current proposed coalescing algorithm doesn't need this, - however. - -This produces a 16 byte used header like this: - -struct tdb_used_record { - - uint32_t magic : 16, - - prev_is_free: 1, - - key_data_divide: 5, - - top_hash: 10; - - uint32_t extra_octets; - - uint64_t key_and_data_len; - -}; - -And a free record like this: - -struct tdb_free_record { - - uint32_t free_magic; - - uint64_t total_length; - - ... - - uint64_t tailer; - -}; - - - -3.8 Transaction Commit Requires 4 fdatasync - -The current transaction algorithm is: - -1. write_recovery_data(); - -2. sync(); - -3. write_recovery_header(); - -4. sync(); - -5. overwrite_with_new_data(); - -6. sync(); - -7. remove_recovery_header(); - -8. sync(); - -On current ext3, each sync flushes all data to disk, so the next -3 syncs are relatively expensive. But this could become a -performance bottleneck on other filesystems such as ext4. - -3.8.1 Proposed Solution - - - - - - - - - -Neil Brown points out that this is overzealous, and only one sync -is needed: - -1. Bundle the recovery data, a transaction counter and a strong - checksum of the new data. - -2. Strong checksum that whole bundle. - -3. Store the bundle in the database. - -4. Overwrite the oldest of the two recovery pointers in the - header (identified using the transaction counter) with the - offset of this bundle. - -5. sync. - -6. Write the new data to the file. - -Checking for recovery means identifying the latest bundle with a -valid checksum and using the new data checksum to ensure that it -has been applied. This is more expensive than the current check, -but need only be done at open. For running databases, a separate -header field can be used to indicate a transaction in progress; -we need only check for recovery if this is set. - -3.9 TDB Does Not Have Snapshot Support - -3.9.1 Proposed Solution - -None. At some point you say “use a real database”. - -But as a thought experiment, if we implemented transactions to -only overwrite free entries (this is tricky: there must not be a -header in each entry which indicates whether it is free, but use -of presence in metadata elsewhere), and a pointer to the hash -table, we could create an entirely new commit without destroying -existing data. Then it would be easy to implement snapshots in a -similar way. - -This would not allow arbitrary changes to the database, such as -tdb_repack does, and would require more space (since we have to -preserve the current and future entries at once). If we used hash -trees rather than one big hash table, we might only have to -rewrite some sections of the hash, too. - -We could then implement snapshots using a similar method, using -multiple different hash tables/free tables. - -3.10 Transactions Cannot Operate in Parallel - -This would be useless for ldb, as it hits the index records with -just about every update. It would add significant complexity in -resolving clashes, and cause the all transaction callers to write -their code to loop in the case where the transactions spuriously -failed. - -3.10.1 Proposed Solution - -We could solve a small part of the problem by providing read-only -transactions. These would allow one write transaction to begin, -but it could not commit until all r/o transactions are done. This -would require a new RO_TRANSACTION_LOCK, which would be upgraded -on commit. - -3.11 Default Hash Function Is Suboptimal - -The Knuth-inspired multiplicative hash used by tdb is fairly slow -(especially if we expand it to 64 bits), and works best when the -hash bucket size is a prime number (which also means a slow -modulus). In addition, it is highly predictable which could -potentially lead to a Denial of Service attack in some TDB uses. - -3.11.1 Proposed Solution - -The Jenkins lookup3 hash[footnote: -http://burtleburtle.net/bob/c/lookup3.c -] is a fast and superbly-mixing hash. It's used by the Linux -kernel and almost everything else. This has the particular -properties that it takes an initial seed, and produces two 32 bit -hash numbers, which we can combine into a 64-bit hash. - -The seed should be created at tdb-creation time from some random -source, and placed in the header. This is far from foolproof, but -adds a little bit of protection against hash bombing. - -3.12 Reliable Traversal Adds Complexity - -We lock a record during traversal iteration, and try to grab that -lock in the delete code. If that grab on delete fails, we simply -mark it deleted and continue onwards; traversal checks for this -condition and does the delete when it moves off the record. - -If traversal terminates, the dead record may be left -indefinitely. - -3.12.1 Proposed Solution - -Remove reliability guarantees; see [traverse-Proposed-Solution]. - -3.13 Fcntl Locking Adds Overhead - -Placing a fcntl lock means a system call, as does removing one. -This is actually one reason why transactions can be faster -(everything is locked once at transaction start). In the -uncontended case, this overhead can theoretically be eliminated. - -3.13.1 Proposed Solution - -None. - -We tried this before with spinlock support, in the early days of -TDB, and it didn't make much difference except in manufactured -benchmarks. - -We could use spinlocks (with futex kernel support under Linux), -but it means that we lose automatic cleanup when a process dies -with a lock. There is a method of auto-cleanup under Linux, but -it's not supported by other operating systems. We could -reintroduce a clear-if-first-style lock and sweep for dead -futexes on open, but that wouldn't help the normal case of one -concurrent opener dying. Increasingly elaborate repair schemes -could be considered, but they require an ABI change (everyone -must use them) anyway, so there's no need to do this at the same -time as everything else. - diff --git a/ccan/tdb2/doc/design.lyx b/ccan/tdb2/doc/design.lyx deleted file mode 100644 index ba3f9cc6..00000000 --- a/ccan/tdb2/doc/design.lyx +++ /dev/null @@ -1,2689 +0,0 @@ -#LyX 1.6.7 created this file. For more info see http://www.lyx.org/ -\lyxformat 345 -\begin_document -\begin_header -\textclass article -\use_default_options true -\language english -\inputencoding auto -\font_roman default -\font_sans default -\font_typewriter default -\font_default_family default -\font_sc false -\font_osf false -\font_sf_scale 100 -\font_tt_scale 100 - -\graphics default -\paperfontsize default -\use_hyperref false -\papersize default -\use_geometry false -\use_amsmath 1 -\use_esint 1 -\cite_engine basic -\use_bibtopic false -\paperorientation portrait -\secnumdepth 3 -\tocdepth 3 -\paragraph_separation indent -\defskip medskip -\quotes_language english -\papercolumns 1 -\papersides 1 -\paperpagestyle default -\tracking_changes true -\output_changes true -\author "" -\author "" -\end_header - -\begin_body - -\begin_layout Title -TDB2: A Redesigning The Trivial DataBase -\end_layout - -\begin_layout Author -Rusty Russell, IBM Corporation -\end_layout - -\begin_layout Date -17-March-2011 -\end_layout - -\begin_layout Abstract -The Trivial DataBase on-disk format is 32 bits; with usage cases heading - towards the 4G limit, that must change. - This required breakage provides an opportunity to revisit TDB's other design - decisions and reassess them. -\end_layout - -\begin_layout Section -Introduction -\end_layout - -\begin_layout Standard -The Trivial DataBase was originally written by Andrew Tridgell as a simple - key/data pair storage system with the same API as dbm, but allowing multiple - readers and writers while being small enough (< 1000 lines of C) to include - in SAMBA. - The simple design created in 1999 has proven surprisingly robust and performant -, used in Samba versions 3 and 4 as well as numerous other projects. - Its useful life was greatly increased by the (backwards-compatible!) addition - of transaction support in 2005. -\end_layout - -\begin_layout Standard -The wider variety and greater demands of TDB-using code has lead to some - organic growth of the API, as well as some compromises on the implementation. - None of these, by themselves, are seen as show-stoppers, but the cumulative - effect is to a loss of elegance over the initial, simple TDB implementation. - Here is a table of the approximate number of lines of implementation code - and number of API functions at the end of each year: -\end_layout - -\begin_layout Standard -\begin_inset Tabular - - - - - - - -\begin_inset Text - -\begin_layout Plain Layout -Year End -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -API Functions -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -Lines of C Code Implementation -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -1999 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -13 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -1195 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2000 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -24 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -1725 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2001 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -32 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2228 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2002 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -35 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2481 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2003 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -35 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2552 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2004 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -40 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2584 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2005 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -38 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2647 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2006 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -52 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -3754 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2007 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -66 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -4398 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2008 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -71 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -4768 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2009 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -73 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -5715 -\end_layout - -\end_inset - - - - -\end_inset - - -\end_layout - -\begin_layout Standard -This review is an attempt to catalog and address all the known issues with - TDB and create solutions which address the problems without significantly - increasing complexity; all involved are far too aware of the dangers of - second system syndrome in rewriting a successful project like this. -\end_layout - -\begin_layout Section -API Issues -\end_layout - -\begin_layout Subsection -tdb_open_ex Is Not Expandable -\end_layout - -\begin_layout Standard -The tdb_open() call was expanded to tdb_open_ex(), which added an optional - hashing function and an optional logging function argument. - Additional arguments to open would require the introduction of a tdb_open_ex2 - call etc. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\begin_inset CommandInset label -LatexCommand label -name "attributes" - -\end_inset - - -\end_layout - -\begin_layout Standard -tdb_open() will take a linked-list of attributes: -\end_layout - -\begin_layout LyX-Code -enum tdb_attribute { -\end_layout - -\begin_layout LyX-Code - TDB_ATTRIBUTE_LOG = 0, -\end_layout - -\begin_layout LyX-Code - TDB_ATTRIBUTE_HASH = 1 -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout LyX-Code -struct tdb_attribute_base { -\end_layout - -\begin_layout LyX-Code - enum tdb_attribute attr; -\end_layout - -\begin_layout LyX-Code - union tdb_attribute *next; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout LyX-Code -struct tdb_attribute_log { -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_LOG */ -\end_layout - -\begin_layout LyX-Code - tdb_log_func log_fn; -\end_layout - -\begin_layout LyX-Code - void *log_private; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout LyX-Code -struct tdb_attribute_hash { -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_HASH */ -\end_layout - -\begin_layout LyX-Code - tdb_hash_func hash_fn; -\end_layout - -\begin_layout LyX-Code - void *hash_private; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout LyX-Code -union tdb_attribute { -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_base base; -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_log log; -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_hash hash; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout Standard -This allows future attributes to be added, even if this expands the size - of the union. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -tdb_traverse Makes Impossible Guarantees -\end_layout - -\begin_layout Standard -tdb_traverse (and tdb_firstkey/tdb_nextkey) predate transactions, and it - was thought that it was important to guarantee that all records which exist - at the start and end of the traversal would be included, and no record - would be included twice. -\end_layout - -\begin_layout Standard -This adds complexity (see -\begin_inset CommandInset ref -LatexCommand ref -reference "Reliable-Traversal-Adds" - -\end_inset - -) and does not work anyway for records which are altered (in particular, - those which are expanded may be effectively deleted and re-added behind - the traversal). -\end_layout - -\begin_layout Subsubsection -\begin_inset CommandInset label -LatexCommand label -name "traverse-Proposed-Solution" - -\end_inset - -Proposed Solution -\end_layout - -\begin_layout Standard -Abandon the guarantee. - You will see every record if no changes occur during your traversal, otherwise - you will see some subset. - You can prevent changes by using a transaction or the locking API. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. - Delete-during-traverse will still delete every record, too (assuming no - other changes). -\end_layout - -\begin_layout Subsection -Nesting of Transactions Is Fraught -\end_layout - -\begin_layout Standard -TDB has alternated between allowing nested transactions and not allowing - them. - Various paths in the Samba codebase assume that transactions will nest, - and in a sense they can: the operation is only committed to disk when the - outer transaction is committed. - There are two problems, however: -\end_layout - -\begin_layout Enumerate -Canceling the inner transaction will cause the outer transaction commit - to fail, and will not undo any operations since the inner transaction began. - This problem is soluble with some additional internal code. -\end_layout - -\begin_layout Enumerate -An inner transaction commit can be cancelled by the outer transaction. - This is desirable in the way which Samba's database initialization code - uses transactions, but could be a surprise to any users expecting a successful - transaction commit to expose changes to others. -\end_layout - -\begin_layout Standard -The current solution is to specify the behavior at tdb_open(), with the - default currently that nested transactions are allowed. - This flag can also be changed at runtime. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Given the usage patterns, it seems that the -\begin_inset Quotes eld -\end_inset - -least-surprise -\begin_inset Quotes erd -\end_inset - - behavior of disallowing nested transactions should become the default. - Additionally, it seems the outer transaction is the only code which knows - whether inner transactions should be allowed, so a flag to indicate this - could be added to tdb_transaction_start. - However, this behavior can be simulated with a wrapper which uses tdb_add_flags -() and tdb_remove_flags(), so the API should not be expanded for this relatively --obscure case. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete; the nesting flag has been removed. -\end_layout - -\begin_layout Subsection -Incorrect Hash Function is Not Detected -\end_layout - -\begin_layout Standard -tdb_open_ex() allows the calling code to specify a different hash function - to use, but does not check that all other processes accessing this tdb - are using the same hash function. - The result is that records are missing from tdb_fetch(). -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The header should contain an example hash result (eg. - the hash of 0xdeadbeef), and tdb_open_ex() should check that the given - hash function produces the same answer, or fail the tdb_open call. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -tdb_set_max_dead/TDB_VOLATILE Expose Implementation -\end_layout - -\begin_layout Standard -In response to scalability issues with the free list ( -\begin_inset CommandInset ref -LatexCommand ref -reference "TDB-Freelist-Is" - -\end_inset - -) two API workarounds have been incorporated in TDB: tdb_set_max_dead() - and the TDB_VOLATILE flag to tdb_open. - The latter actually calls the former with an argument of -\begin_inset Quotes eld -\end_inset - -5 -\begin_inset Quotes erd -\end_inset - -. -\end_layout - -\begin_layout Standard -This code allows deleted records to accumulate without putting them in the - free list. - On delete we iterate through each chain and free them in a batch if there - are more than max_dead entries. - These are never otherwise recycled except as a side-effect of a tdb_repack. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -With the scalability problems of the freelist solved, this API can be removed. - The TDB_VOLATILE flag may still be useful as a hint that store and delete - of records will be at least as common as fetch in order to allow some internal - tuning, but initially will become a no-op. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. - Unknown flags cause tdb_open() to fail as well, so they can be detected - at runtime. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "TDB-Files-Cannot" - -\end_inset - -TDB Files Cannot Be Opened Multiple Times In The Same Process -\end_layout - -\begin_layout Standard -No process can open the same TDB twice; we check and disallow it. - This is an unfortunate side-effect of fcntl locks, which operate on a per-file - rather than per-file-descriptor basis, and do not nest. - Thus, closing any file descriptor on a file clears all the locks obtained - by this process, even if they were placed using a different file descriptor! -\end_layout - -\begin_layout Standard -Note that even if this were solved, deadlock could occur if operations were - nested: this is a more manageable programming error in most cases. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -We could lobby POSIX to fix the perverse rules, or at least lobby Linux - to violate them so that the most common implementation does not have this - restriction. - This would be a generally good idea for other fcntl lock users. -\end_layout - -\begin_layout Standard -Samba uses a wrapper which hands out the same tdb_context to multiple callers - if this happens, and does simple reference counting. - We should do this inside the tdb library, which already emulates lock nesting - internally; it would need to recognize when deadlock occurs within a single - process. - This would create a new failure mode for tdb operations (while we currently - handle locking failures, they are impossible in normal use and a process - encountering them can do little but give up). -\end_layout - -\begin_layout Standard -I do not see benefit in an additional tdb_open flag to indicate whether - re-opening is allowed, as though there may be some benefit to adding a - call to detect when a tdb_context is shared, to allow other to create such - an API. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -TDB API Is Not POSIX Thread-safe -\end_layout - -\begin_layout Standard -The TDB API uses an error code which can be queried after an operation to - determine what went wrong. - This programming model does not work with threads, unless specific additional - guarantees are given by the implementation. - In addition, even otherwise-independent threads cannot open the same TDB - (as in -\begin_inset CommandInset ref -LatexCommand ref -reference "TDB-Files-Cannot" - -\end_inset - -). -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Reachitecting the API to include a tdb_errcode pointer would be a great - deal of churn, but fortunately most functions return 0 on success and -1 - on error: we can change these to return 0 on success and a negative error - code on error, and the API remains similar to previous. - The tdb_fetch, tdb_firstkey and tdb_nextkey functions need to take a TDB_DATA - pointer and return an error code. - It is also simpler to have tdb_nextkey replace its key argument in place, - freeing up any old .dptr. -\end_layout - -\begin_layout Standard -Internal locking is required to make sure that fcntl locks do not overlap - between threads, and also that the global list of tdbs is maintained. -\end_layout - -\begin_layout Standard -The aim is that building tdb with -DTDB_PTHREAD will result in a pthread-safe - version of the library, and otherwise no overhead will exist. - Alternatively, a hooking mechanism similar to that proposed for -\begin_inset CommandInset ref -LatexCommand ref -reference "Proposed-Solution-locking-hook" - -\end_inset - - could be used to enable pthread locking at runtime. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete; API has been changed but thread safety has not been implemented. -\end_layout - -\begin_layout Subsection -*_nonblock Functions And *_mark Functions Expose Implementation -\end_layout - -\begin_layout Standard -CTDB -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -Clustered TDB, see http://ctdb.samba.org -\end_layout - -\end_inset - - wishes to operate on TDB in a non-blocking manner. - This is currently done as follows: -\end_layout - -\begin_layout Enumerate -Call the _nonblock variant of an API function (eg. - tdb_lockall_nonblock). - If this fails: -\end_layout - -\begin_layout Enumerate -Fork a child process, and wait for it to call the normal variant (eg. - tdb_lockall). -\end_layout - -\begin_layout Enumerate -If the child succeeds, call the _mark variant to indicate we already have - the locks (eg. - tdb_lockall_mark). -\end_layout - -\begin_layout Enumerate -Upon completion, tell the child to release the locks (eg. - tdb_unlockall). -\end_layout - -\begin_layout Enumerate -Indicate to tdb that it should consider the locks removed (eg. - tdb_unlockall_mark). -\end_layout - -\begin_layout Standard -There are several issues with this approach. - Firstly, adding two new variants of each function clutters the API for - an obscure use, and so not all functions have three variants. - Secondly, it assumes that all paths of the functions ask for the same locks, - otherwise the parent process will have to get a lock which the child doesn't - have under some circumstances. - I don't believe this is currently the case, but it constrains the implementatio -n. - -\end_layout - -\begin_layout Subsubsection -\begin_inset CommandInset label -LatexCommand label -name "Proposed-Solution-locking-hook" - -\end_inset - -Proposed Solution -\end_layout - -\begin_layout Standard -Implement a hook for locking methods, so that the caller can control the - calls to create and remove fcntl locks. - In this scenario, ctdbd would operate as follows: -\end_layout - -\begin_layout Enumerate -Call the normal API function, eg tdb_lockall(). -\end_layout - -\begin_layout Enumerate -When the lock callback comes in, check if the child has the lock. - Initially, this is always false. - If so, return 0. - Otherwise, try to obtain it in non-blocking mode. - If that fails, return EWOULDBLOCK. -\end_layout - -\begin_layout Enumerate -Release locks in the unlock callback as normal. -\end_layout - -\begin_layout Enumerate -If tdb_lockall() fails, see if we recorded a lock failure; if so, call the - child to repeat the operation. -\end_layout - -\begin_layout Enumerate -The child records what locks it obtains, and returns that information to - the parent. -\end_layout - -\begin_layout Enumerate -When the child has succeeded, goto 1. -\end_layout - -\begin_layout Standard -This is flexible enough to handle any potential locking scenario, even when - lock requirements change. - It can be optimized so that the parent does not release locks, just tells - the child which locks it doesn't need to obtain. -\end_layout - -\begin_layout Standard -It also keeps the complexity out of the API, and in ctdbd where it is needed. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. -\end_layout - -\begin_layout Subsection -tdb_chainlock Functions Expose Implementation -\end_layout - -\begin_layout Standard -tdb_chainlock locks some number of records, including the record indicated - by the given key. - This gave atomicity guarantees; no-one can start a transaction, alter, - read or delete that key while the lock is held. -\end_layout - -\begin_layout Standard -It also makes the same guarantee for any other key in the chain, which is - an internal implementation detail and potentially a cause for deadlock. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None. - It would be nice to have an explicit single entry lock which effected no - other keys. - Unfortunately, this won't work for an entry which doesn't exist. - Thus while chainlock may be implemented more efficiently for the existing - case, it will still have overlap issues with the non-existing case. - So it is best to keep the current (lack of) guarantee about which records - will be effected to avoid constraining our implementation. -\end_layout - -\begin_layout Subsection -Signal Handling is Not Race-Free -\end_layout - -\begin_layout Standard -The tdb_setalarm_sigptr() call allows the caller's signal handler to indicate - that the tdb locking code should return with a failure, rather than trying - again when a signal is received (and errno == EAGAIN). - This is usually used to implement timeouts. -\end_layout - -\begin_layout Standard -Unfortunately, this does not work in the case where the signal is received - before the tdb code enters the fcntl() call to place the lock: the code - will sleep within the fcntl() code, unaware that the signal wants it to - exit. - In the case of long timeouts, this does not happen in practice. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The locking hooks proposed in -\begin_inset CommandInset ref -LatexCommand ref -reference "Proposed-Solution-locking-hook" - -\end_inset - - would allow the user to decide on whether to fail the lock acquisition - on a signal. - This allows the caller to choose their own compromise: they could narrow - the race by checking immediately before the fcntl call. -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -It may be possible to make this race-free in some implementations by having - the signal handler alter the struct flock to make it invalid. - This will cause the fcntl() lock call to fail with EINVAL if the signal - occurs before the kernel is entered, otherwise EAGAIN. -\end_layout - -\end_inset - - -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. -\end_layout - -\begin_layout Subsection -The API Uses Gratuitous Typedefs, Capitals -\end_layout - -\begin_layout Standard -typedefs are useful for providing source compatibility when types can differ - across implementations, or arguably in the case of function pointer definitions - which are hard for humans to parse. - Otherwise it is simply obfuscation and pollutes the namespace. -\end_layout - -\begin_layout Standard -Capitalization is usually reserved for compile-time constants and macros. -\end_layout - -\begin_layout Description -TDB_CONTEXT There is no reason to use this over 'struct tdb_context'; the - definition isn't visible to the API user anyway. -\end_layout - -\begin_layout Description -TDB_DATA There is no reason to use this over struct TDB_DATA; the struct - needs to be understood by the API user. -\end_layout - -\begin_layout Description -struct -\begin_inset space ~ -\end_inset - -TDB_DATA This would normally be called 'struct tdb_data'. -\end_layout - -\begin_layout Description -enum -\begin_inset space ~ -\end_inset - -TDB_ERROR Similarly, this would normally be enum tdb_error. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None. - Introducing lower case variants would please pedants like myself, but if - it were done the existing ones should be kept. - There is little point forcing a purely cosmetic change upon tdb users. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "tdb_log_func-Doesnt-Take" - -\end_inset - -tdb_log_func Doesn't Take The Private Pointer -\end_layout - -\begin_layout Standard -For API compatibility reasons, the logging function needs to call tdb_get_loggin -g_private() to retrieve the pointer registered by the tdb_open_ex for logging. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -It should simply take an extra argument, since we are prepared to break - the API/ABI. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Various Callback Functions Are Not Typesafe -\end_layout - -\begin_layout Standard -The callback functions in tdb_set_logging_function (after -\begin_inset CommandInset ref -LatexCommand ref -reference "tdb_log_func-Doesnt-Take" - -\end_inset - - is resolved), tdb_parse_record, tdb_traverse, tdb_traverse_read and tdb_check - all take void * and must internally convert it to the argument type they - were expecting. -\end_layout - -\begin_layout Standard -If this type changes, the compiler will not produce warnings on the callers, - since it only sees void *. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -With careful use of macros, we can create callback functions which give - a warning when used on gcc and the types of the callback and its private - argument differ. - Unsupported compilers will not give a warning, which is no worse than now. - In addition, the callbacks become clearer, as they need not use void * - for their parameter. -\end_layout - -\begin_layout Standard -See CCAN's typesafe_cb module at http://ccan.ozlabs.org/info/typesafe_cb.html -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -TDB_CLEAR_IF_FIRST Must Be Specified On All Opens, tdb_reopen_all Problematic -\end_layout - -\begin_layout Standard -The TDB_CLEAR_IF_FIRST flag to tdb_open indicates that the TDB file should - be cleared if the caller discovers it is the only process with the TDB - open. - However, if any caller does not specify TDB_CLEAR_IF_FIRST it will not - be detected, so will have the TDB erased underneath them (usually resulting - in a crash). -\end_layout - -\begin_layout Standard -There is a similar issue on fork(); if the parent exits (or otherwise closes - the tdb) before the child calls tdb_reopen_all() to establish the lock - used to indicate the TDB is opened by someone, a TDB_CLEAR_IF_FIRST opener - at that moment will believe it alone has opened the TDB and will erase - it. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Remove TDB_CLEAR_IF_FIRST. - Other workarounds are possible, but see -\begin_inset CommandInset ref -LatexCommand ref -reference "TDB_CLEAR_IF_FIRST-Imposes-Performance" - -\end_inset - -. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Extending The Header Is Difficult -\end_layout - -\begin_layout Standard -We have reserved (zeroed) words in the TDB header, which can be used for - future features. - If the future features are compulsory, the version number must be updated - to prevent old code from accessing the database. - But if the future feature is optional, we have no way of telling if older - code is accessing the database or not. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The header should contain a -\begin_inset Quotes eld -\end_inset - -format variant -\begin_inset Quotes erd -\end_inset - - value (64-bit). - This is divided into two 32-bit parts: -\end_layout - -\begin_layout Enumerate -The lower part reflects the format variant understood by code accessing - the database. -\end_layout - -\begin_layout Enumerate -The upper part reflects the format variant you must understand to write - to the database (otherwise you can only open for reading). -\end_layout - -\begin_layout Standard -The latter field can only be written at creation time, the former should - be written under the OPEN_LOCK when opening the database for writing, if - the variant of the code is lower than the current lowest variant. -\end_layout - -\begin_layout Standard -This should allow backwards-compatible features to be added, and detection - if older code (which doesn't understand the feature) writes to the database. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Record Headers Are Not Expandible -\end_layout - -\begin_layout Standard -If we later want to add (say) checksums on keys and data, it would require - another format change, which we'd like to avoid. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -We often have extra padding at the tail of a record. - If we ensure that the first byte (if any) of this padding is zero, we will - have a way for future changes to detect code which doesn't understand a - new format: the new code would write (say) a 1 at the tail, and thus if - there is no tail or the first byte is 0, we would know the extension is - not present on that record. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -TDB Does Not Use Talloc -\end_layout - -\begin_layout Standard -Many users of TDB (particularly Samba) use the talloc allocator, and thus - have to wrap TDB in a talloc context to use it conveniently. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The allocation within TDB is not complicated enough to justify the use of - talloc, and I am reluctant to force another (excellent) library on TDB - users. - Nonetheless a compromise is possible. - An attribute (see -\begin_inset CommandInset ref -LatexCommand ref -reference "attributes" - -\end_inset - -) can be added later to tdb_open() to provide an alternate allocation mechanism, - specifically for talloc but usable by any other allocator (which would - ignore the -\begin_inset Quotes eld -\end_inset - -context -\begin_inset Quotes erd -\end_inset - - argument). -\end_layout - -\begin_layout Standard -This would form a talloc heirarchy as expected, but the caller would still - have to attach a destructor to the tdb context returned from tdb_open to - close it. - All TDB_DATA fields would be children of the tdb_context, and the caller - would still have to manage them (using talloc_free() or talloc_steal()). -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\begin_layout Section -Performance And Scalability Issues -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "TDB_CLEAR_IF_FIRST-Imposes-Performance" - -\end_inset - -TDB_CLEAR_IF_FIRST Imposes Performance Penalty -\end_layout - -\begin_layout Standard -When TDB_CLEAR_IF_FIRST is specified, a 1-byte read lock is placed at offset - 4 (aka. - the ACTIVE_LOCK). - While these locks never conflict in normal tdb usage, they do add substantial - overhead for most fcntl lock implementations when the kernel scans to detect - if a lock conflict exists. - This is often a single linked list, making the time to acquire and release - a fcntl lock O(N) where N is the number of processes with the TDB open, - not the number actually doing work. -\end_layout - -\begin_layout Standard -In a Samba server it is common to have huge numbers of clients sitting idle, - and thus they have weaned themselves off the TDB_CLEAR_IF_FIRST flag. -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -There is a flag to tdb_reopen_all() which is used for this optimization: - if the parent process will outlive the child, the child does not need the - ACTIVE_LOCK. - This is a workaround for this very performance issue. -\end_layout - -\end_inset - - -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Remove the flag. - It was a neat idea, but even trivial servers tend to know when they are - initializing for the first time and can simply unlink the old tdb at that - point. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -TDB Files Have a 4G Limit -\end_layout - -\begin_layout Standard -This seems to be becoming an issue (so much for -\begin_inset Quotes eld -\end_inset - -trivial -\begin_inset Quotes erd -\end_inset - -!), particularly for ldb. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -A new, incompatible TDB format which uses 64 bit offsets internally rather - than 32 bit as now. - For simplicity of endian conversion (which TDB does on the fly if required), - all values will be 64 bit on disk. - In practice, some upper bits may be used for other purposes, but at least - 56 bits will be available for file offsets. -\end_layout - -\begin_layout Standard -tdb_open() will automatically detect the old version, and even create them - if TDB_VERSION6 is specified to tdb_open. -\end_layout - -\begin_layout Standard -32 bit processes will still be able to access TDBs larger than 4G (assuming - that their off_t allows them to seek to 64 bits), they will gracefully - fall back as they fail to mmap. - This can happen already with large TDBs. -\end_layout - -\begin_layout Standard -Old versions of tdb will fail to open the new TDB files (since 28 August - 2009, commit 398d0c29290: prior to that any unrecognized file format would - be erased and initialized as a fresh tdb!) -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -TDB Records Have a 4G Limit -\end_layout - -\begin_layout Standard -This has not been a reported problem, and the API uses size_t which can - be 64 bit on 64 bit platforms. - However, other limits may have made such an issue moot. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Record sizes will be 64 bit, with an error returned on 32 bit platforms - which try to access such records (the current implementation would return - TDB_ERR_OOM in a similar case). - It seems unlikely that 32 bit keys will be a limitation, so the implementation - may not support this (see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:Records-Incur-A" - -\end_inset - -). -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Hash Size Is Determined At TDB Creation Time -\end_layout - -\begin_layout Standard -TDB contains a number of hash chains in the header; the number is specified - at creation time, and defaults to 131. - This is such a bottleneck on large databases (as each hash chain gets quite - long), that LDB uses 10,000 for this hash. - In general it is impossible to know what the 'right' answer is at database - creation time. -\end_layout - -\begin_layout Subsubsection -\begin_inset CommandInset label -LatexCommand label -name "sub:Hash-Size-Solution" - -\end_inset - -Proposed Solution -\end_layout - -\begin_layout Standard -After comprehensive performance testing on various scalable hash variants -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -http://rusty.ozlabs.org/?p=89 and http://rusty.ozlabs.org/?p=94 This was annoying - because I was previously convinced that an expanding tree of hashes would - be very close to optimal. -\end_layout - -\end_inset - -, it became clear that it is hard to beat a straight linear hash table which - doubles in size when it reaches saturation. - Unfortunately, altering the hash table introduces serious locking complications -: the entire hash table needs to be locked to enlarge the hash table, and - others might be holding locks. - Particularly insidious are insertions done under tdb_chainlock. -\end_layout - -\begin_layout Standard -Thus an expanding layered hash will be used: an array of hash groups, with - each hash group exploding into pointers to lower hash groups once it fills, - turning into a hash tree. - This has implications for locking: we must lock the entire group in case - we need to expand it, yet we don't know how deep the tree is at that point. -\end_layout - -\begin_layout Standard -Note that bits from the hash table entries should be stolen to hold more - hash bits to reduce the penalty of collisions. - We can use the otherwise-unused lower 3 bits. - If we limit the size of the database to 64 exabytes, we can use the top - 8 bits of the hash entry as well. - These 11 bits would reduce false positives down to 1 in 2000 which is more - than we need: we can use one of the bits to indicate that the extra hash - bits are valid. - This means we can choose not to re-hash all entries when we expand a hash - group; simply use the next bits we need and mark them invalid. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "TDB-Freelist-Is" - -\end_inset - -TDB Freelist Is Highly Contended -\end_layout - -\begin_layout Standard -TDB uses a single linked list for the free list. - Allocation occurs as follows, using heuristics which have evolved over - time: -\end_layout - -\begin_layout Enumerate -Get the free list lock for this whole operation. -\end_layout - -\begin_layout Enumerate -Multiply length by 1.25, so we always over-allocate by 25%. -\end_layout - -\begin_layout Enumerate -Set the slack multiplier to 1. -\end_layout - -\begin_layout Enumerate -Examine the current freelist entry: if it is > length but < the current - best case, remember it as the best case. -\end_layout - -\begin_layout Enumerate -Multiply the slack multiplier by 1.05. -\end_layout - -\begin_layout Enumerate -If our best fit so far is less than length * slack multiplier, return it. - The slack will be turned into a new free record if it's large enough. -\end_layout - -\begin_layout Enumerate -Otherwise, go onto the next freelist entry. -\end_layout - -\begin_layout Standard -Deleting a record occurs as follows: -\end_layout - -\begin_layout Enumerate -Lock the hash chain for this whole operation. -\end_layout - -\begin_layout Enumerate -Walk the chain to find the record, keeping the prev pointer offset. -\end_layout - -\begin_layout Enumerate -If max_dead is non-zero: -\end_layout - -\begin_deeper -\begin_layout Enumerate -Walk the hash chain again and count the dead records. -\end_layout - -\begin_layout Enumerate -If it's more than max_dead, bulk free all the dead ones (similar to steps - 4 and below, but the lock is only obtained once). -\end_layout - -\begin_layout Enumerate -Simply mark this record as dead and return. - -\end_layout - -\end_deeper -\begin_layout Enumerate -Get the free list lock for the remainder of this operation. -\end_layout - -\begin_layout Enumerate -\begin_inset CommandInset label -LatexCommand label -name "right-merging" - -\end_inset - -Examine the following block to see if it is free; if so, enlarge the current - block and remove that block from the free list. - This was disabled, as removal from the free list was O(entries-in-free-list). -\end_layout - -\begin_layout Enumerate -Examine the preceeding block to see if it is free: for this reason, each - block has a 32-bit tailer which indicates its length. - If it is free, expand it to cover our new block and return. -\end_layout - -\begin_layout Enumerate -Otherwise, prepend ourselves to the free list. -\end_layout - -\begin_layout Standard -Disabling right-merging (step -\begin_inset CommandInset ref -LatexCommand ref -reference "right-merging" - -\end_inset - -) causes fragmentation; the other heuristics proved insufficient to address - this, so the final answer to this was that when we expand the TDB file - inside a transaction commit, we repack the entire tdb. -\end_layout - -\begin_layout Standard -The single list lock limits our allocation rate; due to the other issues - this is not currently seen as a bottleneck. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The first step is to remove all the current heuristics, as they obviously - interact, then examine them once the lock contention is addressed. -\end_layout - -\begin_layout Standard -The free list must be split to reduce contention. - Assuming perfect free merging, we can at most have 1 free list entry for - each entry. - This implies that the number of free lists is related to the size of the - hash table, but as it is rare to walk a large number of free list entries - we can use far fewer, say 1/32 of the number of hash buckets. -\end_layout - -\begin_layout Standard -It seems tempting to try to reuse the hash implementation which we use for - records here, but we have two ways of searching for free entries: for allocatio -n we search by size (and possibly zone) which produces too many clashes - for our hash table to handle well, and for coalescing we search by address. - Thus an array of doubly-linked free lists seems preferable. -\end_layout - -\begin_layout Standard -There are various benefits in using per-size free lists (see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:TDB-Becomes-Fragmented" - -\end_inset - -) but it's not clear this would reduce contention in the common case where - all processes are allocating/freeing the same size. - Thus we almost certainly need to divide in other ways: the most obvious - is to divide the file into zones, and using a free list (or table of free - lists) for each. - This approximates address ordering. -\end_layout - -\begin_layout Standard -Unfortunately it is difficult to know what heuristics should be used to - determine zone sizes, and our transaction code relies on being able to - create a -\begin_inset Quotes eld -\end_inset - -recovery area -\begin_inset Quotes erd -\end_inset - - by simply appending to the file (difficult if it would need to create a - new zone header). - Thus we use a linked-list of free tables; currently we only ever create - one, but if there is more than one we choose one at random to use. - In future we may use heuristics to add new free tables on contention. - We only expand the file when all free tables are exhausted. -\end_layout - -\begin_layout Standard -The basic algorithm is as follows. - Freeing is simple: -\end_layout - -\begin_layout Enumerate -Identify the correct free list. -\end_layout - -\begin_layout Enumerate -Lock the corresponding list. -\end_layout - -\begin_layout Enumerate -Re-check the list (we didn't have a lock, sizes could have changed): relock - if necessary. -\end_layout - -\begin_layout Enumerate -Place the freed entry in the list. -\end_layout - -\begin_layout Standard -Allocation is a little more complicated, as we perform delayed coalescing - at this point: -\end_layout - -\begin_layout Enumerate -Pick a free table; usually the previous one. -\end_layout - -\begin_layout Enumerate -Lock the corresponding list. -\end_layout - -\begin_layout Enumerate -If the top entry is -large enough, remove it from the list and return it. -\end_layout - -\begin_layout Enumerate -Otherwise, coalesce entries in the list.If there was no entry large enough, - unlock the list and try the next largest list -\end_layout - -\begin_layout Enumerate -If no list has an entry which meets our needs, try the next free table. -\end_layout - -\begin_layout Enumerate -If no zone satisfies, expand the file. -\end_layout - -\begin_layout Standard -This optimizes rapid insert/delete of free list entries by not coalescing - them all the time.. - First-fit address ordering ordering seems to be fairly good for keeping - fragmentation low (see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:TDB-Becomes-Fragmented" - -\end_inset - -). - Note that address ordering does not need a tailer to coalesce, though if - we needed one we could have one cheaply: see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:Records-Incur-A" - -\end_inset - -. - -\end_layout - -\begin_layout Standard -Each free entry has the free table number in the header: less than 255. - It also contains a doubly-linked list for easy deletion. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "sub:TDB-Becomes-Fragmented" - -\end_inset - -TDB Becomes Fragmented -\end_layout - -\begin_layout Standard -Much of this is a result of allocation strategy -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -The Memory Fragmentation Problem: Solved? Johnstone & Wilson 1995 ftp://ftp.cs.ute -xas.edu/pub/garbage/malloc/ismm98.ps -\end_layout - -\end_inset - - and deliberate hobbling of coalescing; internal fragmentation (aka overallocati -on) is deliberately set at 25%, and external fragmentation is only cured - by the decision to repack the entire db when a transaction commit needs - to enlarge the file. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The 25% overhead on allocation works in practice for ldb because indexes - tend to expand by one record at a time. - This internal fragmentation can be resolved by having an -\begin_inset Quotes eld -\end_inset - -expanded -\begin_inset Quotes erd -\end_inset - - bit in the header to note entries that have previously expanded, and allocating - more space for them. -\end_layout - -\begin_layout Standard -There are is a spectrum of possible solutions for external fragmentation: - one is to use a fragmentation-avoiding allocation strategy such as best-fit - address-order allocator. - The other end of the spectrum would be to use a bump allocator (very fast - and simple) and simply repack the file when we reach the end. -\end_layout - -\begin_layout Standard -There are three problems with efficient fragmentation-avoiding allocators: - they are non-trivial, they tend to use a single free list for each size, - and there's no evidence that tdb allocation patterns will match those recorded - for general allocators (though it seems likely). -\end_layout - -\begin_layout Standard -Thus we don't spend too much effort on external fragmentation; we will be - no worse than the current code if we need to repack on occasion. - More effort is spent on reducing freelist contention, and reducing overhead. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "sub:Records-Incur-A" - -\end_inset - -Records Incur A 28-Byte Overhead -\end_layout - -\begin_layout Standard -Each TDB record has a header as follows: -\end_layout - -\begin_layout LyX-Code -struct tdb_record { -\end_layout - -\begin_layout LyX-Code - tdb_off_t next; /* offset of the next record in the list */ -\end_layout - -\begin_layout LyX-Code - tdb_len_t rec_len; /* total byte length of record */ -\end_layout - -\begin_layout LyX-Code - tdb_len_t key_len; /* byte length of key */ -\end_layout - -\begin_layout LyX-Code - tdb_len_t data_len; /* byte length of data */ -\end_layout - -\begin_layout LyX-Code - uint32_t full_hash; /* the full 32 bit hash of the key */ -\end_layout - -\begin_layout LyX-Code - uint32_t magic; /* try to catch errors */ -\end_layout - -\begin_layout LyX-Code - /* the following union is implied: -\end_layout - -\begin_layout LyX-Code - union { -\end_layout - -\begin_layout LyX-Code - char record[rec_len]; -\end_layout - -\begin_layout LyX-Code - struct { -\end_layout - -\begin_layout LyX-Code - char key[key_len]; -\end_layout - -\begin_layout LyX-Code - char data[data_len]; -\end_layout - -\begin_layout LyX-Code - } -\end_layout - -\begin_layout LyX-Code - uint32_t totalsize; (tailer) -\end_layout - -\begin_layout LyX-Code - } -\end_layout - -\begin_layout LyX-Code - */ -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout Standard -Naively, this would double to a 56-byte overhead on a 64 bit implementation. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -We can use various techniques to reduce this for an allocated block: -\end_layout - -\begin_layout Enumerate -The 'next' pointer is not required, as we are using a flat hash table. -\end_layout - -\begin_layout Enumerate -'rec_len' can instead be expressed as an addition to key_len and data_len - (it accounts for wasted or overallocated length in the record). - Since the record length is always a multiple of 8, we can conveniently - fit it in 32 bits (representing up to 35 bits). -\end_layout - -\begin_layout Enumerate -'key_len' and 'data_len' can be reduced. - I'm unwilling to restrict 'data_len' to 32 bits, but instead we can combine - the two into one 64-bit field and using a 5 bit value which indicates at - what bit to divide the two. - Keys are unlikely to scale as fast as data, so I'm assuming a maximum key - size of 32 bits. -\end_layout - -\begin_layout Enumerate -'full_hash' is used to avoid a memcmp on the -\begin_inset Quotes eld -\end_inset - -miss -\begin_inset Quotes erd -\end_inset - - case, but this is diminishing returns after a handful of bits (at 10 bits, - it reduces 99.9% of false memcmp). - As an aside, as the lower bits are already incorporated in the hash table - resolution, the upper bits should be used here. - Note that it's not clear that these bits will be a win, given the extra - bits in the hash table itself (see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:Hash-Size-Solution" - -\end_inset - -). -\end_layout - -\begin_layout Enumerate -'magic' does not need to be enlarged: it currently reflects one of 5 values - (used, free, dead, recovery, and unused_recovery). - It is useful for quick sanity checking however, and should not be eliminated. -\end_layout - -\begin_layout Enumerate -'tailer' is only used to coalesce free blocks (so a block to the right can - find the header to check if this block is free). - This can be replaced by a single 'free' bit in the header of the following - block (and the tailer only exists in free blocks). -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -This technique from Thomas Standish. - Data Structure Techniques. - Addison-Wesley, Reading, Massachusetts, 1980. -\end_layout - -\end_inset - - The current proposed coalescing algorithm doesn't need this, however. -\end_layout - -\begin_layout Standard -This produces a 16 byte used header like this: -\end_layout - -\begin_layout LyX-Code -struct tdb_used_record { -\end_layout - -\begin_layout LyX-Code - uint32_t used_magic : 16, -\end_layout - -\begin_layout LyX-Code - -\end_layout - -\begin_layout LyX-Code - key_data_divide: 5, -\end_layout - -\begin_layout LyX-Code - top_hash: 11; -\end_layout - -\begin_layout LyX-Code - uint32_t extra_octets; -\end_layout - -\begin_layout LyX-Code - uint64_t key_and_data_len; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout Standard -And a free record like this: -\end_layout - -\begin_layout LyX-Code -struct tdb_free_record { -\end_layout - -\begin_layout LyX-Code - uint64_t free_magic: 8, -\end_layout - -\begin_layout LyX-Code - prev : 56; -\end_layout - -\begin_layout LyX-Code - -\end_layout - -\begin_layout LyX-Code - uint64_t free_table: 8, -\end_layout - -\begin_layout LyX-Code - total_length : 56 -\end_layout - -\begin_layout LyX-Code - uint64_t next;; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout Standard -Note that by limiting valid offsets to 56 bits, we can pack everything we - need into 3 64-byte words, meaning our minimum record size is 8 bytes. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Transaction Commit Requires 4 fdatasync -\end_layout - -\begin_layout Standard -The current transaction algorithm is: -\end_layout - -\begin_layout Enumerate -write_recovery_data(); -\end_layout - -\begin_layout Enumerate -sync(); -\end_layout - -\begin_layout Enumerate -write_recovery_header(); -\end_layout - -\begin_layout Enumerate -sync(); -\end_layout - -\begin_layout Enumerate -overwrite_with_new_data(); -\end_layout - -\begin_layout Enumerate -sync(); -\end_layout - -\begin_layout Enumerate -remove_recovery_header(); -\end_layout - -\begin_layout Enumerate -sync(); -\end_layout - -\begin_layout Standard -On current ext3, each sync flushes all data to disk, so the next 3 syncs - are relatively expensive. - But this could become a performance bottleneck on other filesystems such - as ext4. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Neil Brown points out that this is overzealous, and only one sync is needed: -\end_layout - -\begin_layout Enumerate -Bundle the recovery data, a transaction counter and a strong checksum of - the new data. -\end_layout - -\begin_layout Enumerate -Strong checksum that whole bundle. -\end_layout - -\begin_layout Enumerate -Store the bundle in the database. -\end_layout - -\begin_layout Enumerate -Overwrite the oldest of the two recovery pointers in the header (identified - using the transaction counter) with the offset of this bundle. -\end_layout - -\begin_layout Enumerate -sync. -\end_layout - -\begin_layout Enumerate -Write the new data to the file. -\end_layout - -\begin_layout Standard -Checking for recovery means identifying the latest bundle with a valid checksum - and using the new data checksum to ensure that it has been applied. - This is more expensive than the current check, but need only be done at - open. - For running databases, a separate header field can be used to indicate - a transaction in progress; we need only check for recovery if this is set. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "sub:TDB-Does-Not" - -\end_inset - -TDB Does Not Have Snapshot Support -\end_layout - -\begin_layout Subsubsection -Proposed SolutionNone. - At some point you say -\begin_inset Quotes eld -\end_inset - -use a real database -\begin_inset Quotes erd -\end_inset - - (but see -\begin_inset CommandInset ref -LatexCommand ref -reference "replay-attribute" - -\end_inset - -). -\end_layout - -\begin_layout Standard -But as a thought experiment, if we implemented transactions to only overwrite - free entries (this is tricky: there must not be a header in each entry - which indicates whether it is free, but use of presence in metadata elsewhere), - and a pointer to the hash table, we could create an entirely new commit - without destroying existing data. - Then it would be easy to implement snapshots in a similar way. -\end_layout - -\begin_layout Standard -This would not allow arbitrary changes to the database, such as tdb_repack - does, and would require more space (since we have to preserve the current - and future entries at once). - If we used hash trees rather than one big hash table, we might only have - to rewrite some sections of the hash, too. -\end_layout - -\begin_layout Standard -We could then implement snapshots using a similar method, using multiple - different hash tables/free tables. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\begin_layout Subsection -Transactions Cannot Operate in Parallel -\end_layout - -\begin_layout Standard -This would be useless for ldb, as it hits the index records with just about - every update. - It would add significant complexity in resolving clashes, and cause the - all transaction callers to write their code to loop in the case where the - transactions spuriously failed. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None (but see -\begin_inset CommandInset ref -LatexCommand ref -reference "replay-attribute" - -\end_inset - -). - We could solve a small part of the problem by providing read-only transactions. - These would allow one write transaction to begin, but it could not commit - until all r/o transactions are done. - This would require a new RO_TRANSACTION_LOCK, which would be upgraded on - commit. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\begin_layout Subsection -Default Hash Function Is Suboptimal -\end_layout - -\begin_layout Standard -The Knuth-inspired multiplicative hash used by tdb is fairly slow (especially - if we expand it to 64 bits), and works best when the hash bucket size is - a prime number (which also means a slow modulus). - In addition, it is highly predictable which could potentially lead to a - Denial of Service attack in some TDB uses. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The Jenkins lookup3 hash -\begin_inset Foot -status open - -\begin_layout Plain Layout -http://burtleburtle.net/bob/c/lookup3.c -\end_layout - -\end_inset - - is a fast and superbly-mixing hash. - It's used by the Linux kernel and almost everything else. - This has the particular properties that it takes an initial seed, and produces - two 32 bit hash numbers, which we can combine into a 64-bit hash. -\end_layout - -\begin_layout Standard -The seed should be created at tdb-creation time from some random source, - and placed in the header. - This is far from foolproof, but adds a little bit of protection against - hash bombing. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "Reliable-Traversal-Adds" - -\end_inset - -Reliable Traversal Adds Complexity -\end_layout - -\begin_layout Standard -We lock a record during traversal iteration, and try to grab that lock in - the delete code. - If that grab on delete fails, we simply mark it deleted and continue onwards; - traversal checks for this condition and does the delete when it moves off - the record. -\end_layout - -\begin_layout Standard -If traversal terminates, the dead record may be left indefinitely. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Remove reliability guarantees; see -\begin_inset CommandInset ref -LatexCommand ref -reference "traverse-Proposed-Solution" - -\end_inset - -. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Fcntl Locking Adds Overhead -\end_layout - -\begin_layout Standard -Placing a fcntl lock means a system call, as does removing one. - This is actually one reason why transactions can be faster (everything - is locked once at transaction start). - In the uncontended case, this overhead can theoretically be eliminated. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None. -\end_layout - -\begin_layout Standard -We tried this before with spinlock support, in the early days of TDB, and - it didn't make much difference except in manufactured benchmarks. -\end_layout - -\begin_layout Standard -We could use spinlocks (with futex kernel support under Linux), but it means - that we lose automatic cleanup when a process dies with a lock. - There is a method of auto-cleanup under Linux, but it's not supported by - other operating systems. - We could reintroduce a clear-if-first-style lock and sweep for dead futexes - on open, but that wouldn't help the normal case of one concurrent opener - dying. - Increasingly elaborate repair schemes could be considered, but they require - an ABI change (everyone must use them) anyway, so there's no need to do - this at the same time as everything else. -\end_layout - -\begin_layout Subsection -Some Transactions Don't Require Durability -\end_layout - -\begin_layout Standard -Volker points out that gencache uses a CLEAR_IF_FIRST tdb for normal (fast) - usage, and occasionally empties the results into a transactional TDB. - This kind of usage prioritizes performance over durability: as long as - we are consistent, data can be lost. -\end_layout - -\begin_layout Standard -This would be more neatly implemented inside tdb: a -\begin_inset Quotes eld -\end_inset - -soft -\begin_inset Quotes erd -\end_inset - - transaction commit (ie. - syncless) which meant that data may be reverted on a crash. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None. -\end_layout - -\begin_layout Standard -Unfortunately any transaction scheme which overwrites old data requires - a sync before that overwrite to avoid the possibility of corruption. -\end_layout - -\begin_layout Standard -It seems possible to use a scheme similar to that described in -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:TDB-Does-Not" - -\end_inset - -,where transactions are committed without overwriting existing data, and - an array of top-level pointers were available in the header. - If the transaction is -\begin_inset Quotes eld -\end_inset - -soft -\begin_inset Quotes erd -\end_inset - - then we would not need a sync at all: existing processes would pick up - the new hash table and free list and work with that. -\end_layout - -\begin_layout Standard -At some later point, a sync would allow recovery of the old data into the - free lists (perhaps when the array of top-level pointers filled). - On crash, tdb_open() would examine the array of top levels, and apply the - transactions until it encountered an invalid checksum. -\end_layout - -\begin_layout Subsection -Tracing Is Fragile, Replay Is External -\end_layout - -\begin_layout Standard -The current TDB has compile-time-enabled tracing code, but it often breaks - as it is not enabled by default. - In a similar way, the ctdb code has an external wrapper which does replay - tracing so it can coordinate cluster-wide transactions. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\begin_inset CommandInset label -LatexCommand label -name "replay-attribute" - -\end_inset - - -\end_layout - -\begin_layout Standard -Tridge points out that an attribute can be later added to tdb_open (see - -\begin_inset CommandInset ref -LatexCommand ref -reference "attributes" - -\end_inset - -) to provide replay/trace hooks, which could become the basis for this and - future parallel transactions and snapshot support. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\end_body -\end_document diff --git a/ccan/tdb2/doc/design.lyx,v b/ccan/tdb2/doc/design.lyx,v deleted file mode 100644 index 68e5ed27..00000000 --- a/ccan/tdb2/doc/design.lyx,v +++ /dev/null @@ -1,4679 +0,0 @@ -head 1.13; -access; -symbols; -locks; strict; -comment @# @; - - -1.13 -date 2011.03.01.11.46.54; author rusty; state Exp; -branches; -next 1.12; - -1.12 -date 2010.12.01.12.20.49; author rusty; state Exp; -branches; -next 1.11; - -1.11 -date 2010.12.01.11.55.20; author rusty; state Exp; -branches; -next 1.10; - -1.10 -date 2010.09.14.00.33.57; author rusty; state Exp; -branches; -next 1.9; - -1.9 -date 2010.09.09.07.25.12; author rusty; state Exp; -branches; -next 1.8; - -1.8 -date 2010.09.02.02.29.05; author rusty; state Exp; -branches; -next 1.7; - -1.7 -date 2010.09.01.10.58.12; author rusty; state Exp; -branches; -next 1.6; - -1.6 -date 2010.08.02.00.21.43; author rusty; state Exp; -branches; -next 1.5; - -1.5 -date 2010.08.02.00.21.16; author rusty; state Exp; -branches; -next 1.4; - -1.4 -date 2010.05.10.13.09.11; author rusty; state Exp; -branches; -next 1.3; - -1.3 -date 2010.05.10.11.58.37; author rusty; state Exp; -branches; -next 1.2; - -1.2 -date 2010.05.10.05.35.13; author rusty; state Exp; -branches; -next 1.1; - -1.1 -date 2010.05.04.02.29.16; author rusty; state Exp; -branches; -next ; - - -desc -@First draft -@ - - -1.13 -log -@Thread-safe API -@ -text -@#LyX 1.6.7 created this file. For more info see http://www.lyx.org/ -\lyxformat 345 -\begin_document -\begin_header -\textclass article -\use_default_options true -\language english -\inputencoding auto -\font_roman default -\font_sans default -\font_typewriter default -\font_default_family default -\font_sc false -\font_osf false -\font_sf_scale 100 -\font_tt_scale 100 - -\graphics default -\paperfontsize default -\use_hyperref false -\papersize default -\use_geometry false -\use_amsmath 1 -\use_esint 1 -\cite_engine basic -\use_bibtopic false -\paperorientation portrait -\secnumdepth 3 -\tocdepth 3 -\paragraph_separation indent -\defskip medskip -\quotes_language english -\papercolumns 1 -\papersides 1 -\paperpagestyle default -\tracking_changes true -\output_changes true -\author "Rusty Russell,,," -\author "" -\end_header - -\begin_body - -\begin_layout Title -TDB2: A Redesigning The Trivial DataBase -\end_layout - -\begin_layout Author -Rusty Russell, IBM Corporation -\end_layout - -\begin_layout Date -1-December-2010 -\end_layout - -\begin_layout Abstract -The Trivial DataBase on-disk format is 32 bits; with usage cases heading - towards the 4G limit, that must change. - This required breakage provides an opportunity to revisit TDB's other design - decisions and reassess them. -\end_layout - -\begin_layout Section -Introduction -\end_layout - -\begin_layout Standard -The Trivial DataBase was originally written by Andrew Tridgell as a simple - key/data pair storage system with the same API as dbm, but allowing multiple - readers and writers while being small enough (< 1000 lines of C) to include - in SAMBA. - The simple design created in 1999 has proven surprisingly robust and performant -, used in Samba versions 3 and 4 as well as numerous other projects. - Its useful life was greatly increased by the (backwards-compatible!) addition - of transaction support in 2005. -\end_layout - -\begin_layout Standard -The wider variety and greater demands of TDB-using code has lead to some - organic growth of the API, as well as some compromises on the implementation. - None of these, by themselves, are seen as show-stoppers, but the cumulative - effect is to a loss of elegance over the initial, simple TDB implementation. - Here is a table of the approximate number of lines of implementation code - and number of API functions at the end of each year: -\end_layout - -\begin_layout Standard -\begin_inset Tabular - - - - - - - -\begin_inset Text - -\begin_layout Plain Layout -Year End -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -API Functions -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -Lines of C Code Implementation -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -1999 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -13 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -1195 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2000 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -24 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -1725 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2001 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -32 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2228 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2002 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -35 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2481 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2003 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -35 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2552 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2004 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -40 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2584 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2005 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -38 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -2647 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2006 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -52 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -3754 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2007 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -66 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -4398 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2008 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -71 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -4768 -\end_layout - -\end_inset - - - - -\begin_inset Text - -\begin_layout Plain Layout -2009 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -73 -\end_layout - -\end_inset - - -\begin_inset Text - -\begin_layout Plain Layout -5715 -\end_layout - -\end_inset - - - - -\end_inset - - -\end_layout - -\begin_layout Standard -This review is an attempt to catalog and address all the known issues with - TDB and create solutions which address the problems without significantly - increasing complexity; all involved are far too aware of the dangers of - second system syndrome in rewriting a successful project like this. -\end_layout - -\begin_layout Section -API Issues -\end_layout - -\begin_layout Subsection -tdb_open_ex Is Not Expandable -\end_layout - -\begin_layout Standard -The tdb_open() call was expanded to tdb_open_ex(), which added an optional - hashing function and an optional logging function argument. - Additional arguments to open would require the introduction of a tdb_open_ex2 - call etc. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\begin_inset CommandInset label -LatexCommand label -name "attributes" - -\end_inset - - -\end_layout - -\begin_layout Standard -tdb_open() will take a linked-list of attributes: -\end_layout - -\begin_layout LyX-Code -enum tdb_attribute { -\end_layout - -\begin_layout LyX-Code - TDB_ATTRIBUTE_LOG = 0, -\end_layout - -\begin_layout LyX-Code - TDB_ATTRIBUTE_HASH = 1 -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout LyX-Code -struct tdb_attribute_base { -\end_layout - -\begin_layout LyX-Code - enum tdb_attribute attr; -\end_layout - -\begin_layout LyX-Code - union tdb_attribute *next; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout LyX-Code -struct tdb_attribute_log { -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_LOG */ -\end_layout - -\begin_layout LyX-Code - tdb_log_func log_fn; -\end_layout - -\begin_layout LyX-Code - void *log_private; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout LyX-Code -struct tdb_attribute_hash { -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_HASH */ -\end_layout - -\begin_layout LyX-Code - tdb_hash_func hash_fn; -\end_layout - -\begin_layout LyX-Code - void *hash_private; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout LyX-Code -union tdb_attribute { -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_base base; -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_log log; -\end_layout - -\begin_layout LyX-Code - struct tdb_attribute_hash hash; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout Standard -This allows future attributes to be added, even if this expands the size - of the union. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -tdb_traverse Makes Impossible Guarantees -\end_layout - -\begin_layout Standard -tdb_traverse (and tdb_firstkey/tdb_nextkey) predate transactions, and it - was thought that it was important to guarantee that all records which exist - at the start and end of the traversal would be included, and no record - would be included twice. -\end_layout - -\begin_layout Standard -This adds complexity (see -\begin_inset CommandInset ref -LatexCommand ref -reference "Reliable-Traversal-Adds" - -\end_inset - -) and does not work anyway for records which are altered (in particular, - those which are expanded may be effectively deleted and re-added behind - the traversal). -\end_layout - -\begin_layout Subsubsection -\begin_inset CommandInset label -LatexCommand label -name "traverse-Proposed-Solution" - -\end_inset - -Proposed Solution -\end_layout - -\begin_layout Standard -Abandon the guarantee. - You will see every record if no changes occur during your traversal, otherwise - you will see some subset. - You can prevent changes by using a transaction or the locking API. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. - Delete-during-traverse will still delete every record, too (assuming no - other changes). -\end_layout - -\begin_layout Subsection -Nesting of Transactions Is Fraught -\end_layout - -\begin_layout Standard -TDB has alternated between allowing nested transactions and not allowing - them. - Various paths in the Samba codebase assume that transactions will nest, - and in a sense they can: the operation is only committed to disk when the - outer transaction is committed. - There are two problems, however: -\end_layout - -\begin_layout Enumerate -Canceling the inner transaction will cause the outer transaction commit - to fail, and will not undo any operations since the inner transaction began. - This problem is soluble with some additional internal code. -\end_layout - -\begin_layout Enumerate -An inner transaction commit can be cancelled by the outer transaction. - This is desirable in the way which Samba's database initialization code - uses transactions, but could be a surprise to any users expecting a successful - transaction commit to expose changes to others. -\end_layout - -\begin_layout Standard -The current solution is to specify the behavior at tdb_open(), with the - default currently that nested transactions are allowed. - This flag can also be changed at runtime. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Given the usage patterns, it seems that the -\begin_inset Quotes eld -\end_inset - -least-surprise -\begin_inset Quotes erd -\end_inset - - behavior of disallowing nested transactions should become the default. - Additionally, it seems the outer transaction is the only code which knows - whether inner transactions should be allowed, so a flag to indicate this - could be added to tdb_transaction_start. - However, this behavior can be simulated with a wrapper which uses tdb_add_flags -() and tdb_remove_flags(), so the API should not be expanded for this relatively --obscure case. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard - -\change_deleted 0 1298979572 -Incomplete; nesting flag is still defined as per tdb1. -\change_inserted 0 1298979584 -Complete; the nesting flag has been removed. -\change_unchanged - -\end_layout - -\begin_layout Subsection -Incorrect Hash Function is Not Detected -\end_layout - -\begin_layout Standard -tdb_open_ex() allows the calling code to specify a different hash function - to use, but does not check that all other processes accessing this tdb - are using the same hash function. - The result is that records are missing from tdb_fetch(). -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The header should contain an example hash result (eg. - the hash of 0xdeadbeef), and tdb_open_ex() should check that the given - hash function produces the same answer, or fail the tdb_open call. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -tdb_set_max_dead/TDB_VOLATILE Expose Implementation -\end_layout - -\begin_layout Standard -In response to scalability issues with the free list ( -\begin_inset CommandInset ref -LatexCommand ref -reference "TDB-Freelist-Is" - -\end_inset - -) two API workarounds have been incorporated in TDB: tdb_set_max_dead() - and the TDB_VOLATILE flag to tdb_open. - The latter actually calls the former with an argument of -\begin_inset Quotes eld -\end_inset - -5 -\begin_inset Quotes erd -\end_inset - -. -\end_layout - -\begin_layout Standard -This code allows deleted records to accumulate without putting them in the - free list. - On delete we iterate through each chain and free them in a batch if there - are more than max_dead entries. - These are never otherwise recycled except as a side-effect of a tdb_repack. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -With the scalability problems of the freelist solved, this API can be removed. - The TDB_VOLATILE flag may still be useful as a hint that store and delete - of records will be at least as common as fetch in order to allow some internal - tuning, but initially will become a no-op. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. - TDB_VOLATILE still defined, but implementation should fail on unknown flags - to be future-proof. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "TDB-Files-Cannot" - -\end_inset - -TDB Files Cannot Be Opened Multiple Times In The Same Process -\end_layout - -\begin_layout Standard -No process can open the same TDB twice; we check and disallow it. - This is an unfortunate side-effect of fcntl locks, which operate on a per-file - rather than per-file-descriptor basis, and do not nest. - Thus, closing any file descriptor on a file clears all the locks obtained - by this process, even if they were placed using a different file descriptor! -\end_layout - -\begin_layout Standard -Note that even if this were solved, deadlock could occur if operations were - nested: this is a more manageable programming error in most cases. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -We could lobby POSIX to fix the perverse rules, or at least lobby Linux - to violate them so that the most common implementation does not have this - restriction. - This would be a generally good idea for other fcntl lock users. -\end_layout - -\begin_layout Standard -Samba uses a wrapper which hands out the same tdb_context to multiple callers - if this happens, and does simple reference counting. - We should do this inside the tdb library, which already emulates lock nesting - internally; it would need to recognize when deadlock occurs within a single - process. - This would create a new failure mode for tdb operations (while we currently - handle locking failures, they are impossible in normal use and a process - encountering them can do little but give up). -\end_layout - -\begin_layout Standard -I do not see benefit in an additional tdb_open flag to indicate whether - re-opening is allowed, as though there may be some benefit to adding a - call to detect when a tdb_context is shared, to allow other to create such - an API. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. -\end_layout - -\begin_layout Subsection -TDB API Is Not POSIX Thread-safe -\end_layout - -\begin_layout Standard -The TDB API uses an error code which can be queried after an operation to - determine what went wrong. - This programming model does not work with threads, unless specific additional - guarantees are given by the implementation. - In addition, even otherwise-independent threads cannot open the same TDB - (as in -\begin_inset CommandInset ref -LatexCommand ref -reference "TDB-Files-Cannot" - -\end_inset - -). -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Reachitecting the API to include a tdb_errcode pointer would be a great - deal of churn -\change_inserted 0 1298979557 -, but fortunately most functions return 0 on success and -1 on error: we - can change these to return 0 on success and a negative error code on error, - and the API remains similar to previous. - The tdb_fetch, tdb_firstkey and tdb_nextkey functions need to take a TDB_DATA - pointer and return an error code. - It is also simpler to have tdb_nextkey replace its key argument in place, - freeing up any old .dptr. -\end_layout - -\begin_layout Standard - -\change_deleted 0 1298979438 -; we are better to guarantee that the tdb_errcode is per-thread so the current - programming model can be maintained. -\end_layout - -\begin_layout Standard - -\change_deleted 0 1298979438 -This requires dynamic per-thread allocations, which is awkward with POSIX - threads (pthread_key_create space is limited and we cannot simply allocate - a key for every TDB). -\change_unchanged - -\end_layout - -\begin_layout Standard -Internal locking is required to make sure that fcntl locks do not overlap - between threads, and also that the global list of tdbs is maintained. -\end_layout - -\begin_layout Standard -The aim is that building tdb with -DTDB_PTHREAD will result in a pthread-safe - version of the library, and otherwise no overhead will exist. - Alternatively, a hooking mechanism similar to that proposed for -\begin_inset CommandInset ref -LatexCommand ref -reference "Proposed-Solution-locking-hook" - -\end_inset - - could be used to enable pthread locking at runtime. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete -\change_inserted 0 1298979681 -; API has been changed but thread safety has not been implemented. -\change_deleted 0 1298979669 -. -\change_unchanged - -\end_layout - -\begin_layout Subsection -*_nonblock Functions And *_mark Functions Expose Implementation -\end_layout - -\begin_layout Standard -CTDB -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -Clustered TDB, see http://ctdb.samba.org -\end_layout - -\end_inset - - wishes to operate on TDB in a non-blocking manner. - This is currently done as follows: -\end_layout - -\begin_layout Enumerate -Call the _nonblock variant of an API function (eg. - tdb_lockall_nonblock). - If this fails: -\end_layout - -\begin_layout Enumerate -Fork a child process, and wait for it to call the normal variant (eg. - tdb_lockall). -\end_layout - -\begin_layout Enumerate -If the child succeeds, call the _mark variant to indicate we already have - the locks (eg. - tdb_lockall_mark). -\end_layout - -\begin_layout Enumerate -Upon completion, tell the child to release the locks (eg. - tdb_unlockall). -\end_layout - -\begin_layout Enumerate -Indicate to tdb that it should consider the locks removed (eg. - tdb_unlockall_mark). -\end_layout - -\begin_layout Standard -There are several issues with this approach. - Firstly, adding two new variants of each function clutters the API for - an obscure use, and so not all functions have three variants. - Secondly, it assumes that all paths of the functions ask for the same locks, - otherwise the parent process will have to get a lock which the child doesn't - have under some circumstances. - I don't believe this is currently the case, but it constrains the implementatio -n. - -\end_layout - -\begin_layout Subsubsection -\begin_inset CommandInset label -LatexCommand label -name "Proposed-Solution-locking-hook" - -\end_inset - -Proposed Solution -\end_layout - -\begin_layout Standard -Implement a hook for locking methods, so that the caller can control the - calls to create and remove fcntl locks. - In this scenario, ctdbd would operate as follows: -\end_layout - -\begin_layout Enumerate -Call the normal API function, eg tdb_lockall(). -\end_layout - -\begin_layout Enumerate -When the lock callback comes in, check if the child has the lock. - Initially, this is always false. - If so, return 0. - Otherwise, try to obtain it in non-blocking mode. - If that fails, return EWOULDBLOCK. -\end_layout - -\begin_layout Enumerate -Release locks in the unlock callback as normal. -\end_layout - -\begin_layout Enumerate -If tdb_lockall() fails, see if we recorded a lock failure; if so, call the - child to repeat the operation. -\end_layout - -\begin_layout Enumerate -The child records what locks it obtains, and returns that information to - the parent. -\end_layout - -\begin_layout Enumerate -When the child has succeeded, goto 1. -\end_layout - -\begin_layout Standard -This is flexible enough to handle any potential locking scenario, even when - lock requirements change. - It can be optimized so that the parent does not release locks, just tells - the child which locks it doesn't need to obtain. -\end_layout - -\begin_layout Standard -It also keeps the complexity out of the API, and in ctdbd where it is needed. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. -\end_layout - -\begin_layout Subsection -tdb_chainlock Functions Expose Implementation -\end_layout - -\begin_layout Standard -tdb_chainlock locks some number of records, including the record indicated - by the given key. - This gave atomicity guarantees; no-one can start a transaction, alter, - read or delete that key while the lock is held. -\end_layout - -\begin_layout Standard -It also makes the same guarantee for any other key in the chain, which is - an internal implementation detail and potentially a cause for deadlock. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None. - It would be nice to have an explicit single entry lock which effected no - other keys. - Unfortunately, this won't work for an entry which doesn't exist. - Thus while chainlock may be implemented more efficiently for the existing - case, it will still have overlap issues with the non-existing case. - So it is best to keep the current (lack of) guarantee about which records - will be effected to avoid constraining our implementation. -\end_layout - -\begin_layout Subsection -Signal Handling is Not Race-Free -\end_layout - -\begin_layout Standard -The tdb_setalarm_sigptr() call allows the caller's signal handler to indicate - that the tdb locking code should return with a failure, rather than trying - again when a signal is received (and errno == EAGAIN). - This is usually used to implement timeouts. -\end_layout - -\begin_layout Standard -Unfortunately, this does not work in the case where the signal is received - before the tdb code enters the fcntl() call to place the lock: the code - will sleep within the fcntl() code, unaware that the signal wants it to - exit. - In the case of long timeouts, this does not happen in practice. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The locking hooks proposed in -\begin_inset CommandInset ref -LatexCommand ref -reference "Proposed-Solution-locking-hook" - -\end_inset - - would allow the user to decide on whether to fail the lock acquisition - on a signal. - This allows the caller to choose their own compromise: they could narrow - the race by checking immediately before the fcntl call. -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -It may be possible to make this race-free in some implementations by having - the signal handler alter the struct flock to make it invalid. - This will cause the fcntl() lock call to fail with EINVAL if the signal - occurs before the kernel is entered, otherwise EAGAIN. -\end_layout - -\end_inset - - -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. -\end_layout - -\begin_layout Subsection -The API Uses Gratuitous Typedefs, Capitals -\end_layout - -\begin_layout Standard -typedefs are useful for providing source compatibility when types can differ - across implementations, or arguably in the case of function pointer definitions - which are hard for humans to parse. - Otherwise it is simply obfuscation and pollutes the namespace. -\end_layout - -\begin_layout Standard -Capitalization is usually reserved for compile-time constants and macros. -\end_layout - -\begin_layout Description -TDB_CONTEXT There is no reason to use this over 'struct tdb_context'; the - definition isn't visible to the API user anyway. -\end_layout - -\begin_layout Description -TDB_DATA There is no reason to use this over struct TDB_DATA; the struct - needs to be understood by the API user. -\end_layout - -\begin_layout Description -struct -\begin_inset space ~ -\end_inset - -TDB_DATA This would normally be called 'struct tdb_data'. -\end_layout - -\begin_layout Description -enum -\begin_inset space ~ -\end_inset - -TDB_ERROR Similarly, this would normally be enum tdb_error. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None. - Introducing lower case variants would please pedants like myself, but if - it were done the existing ones should be kept. - There is little point forcing a purely cosmetic change upon tdb users. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "tdb_log_func-Doesnt-Take" - -\end_inset - -tdb_log_func Doesn't Take The Private Pointer -\end_layout - -\begin_layout Standard -For API compatibility reasons, the logging function needs to call tdb_get_loggin -g_private() to retrieve the pointer registered by the tdb_open_ex for logging. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -It should simply take an extra argument, since we are prepared to break - the API/ABI. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Various Callback Functions Are Not Typesafe -\end_layout - -\begin_layout Standard -The callback functions in tdb_set_logging_function (after -\begin_inset CommandInset ref -LatexCommand ref -reference "tdb_log_func-Doesnt-Take" - -\end_inset - - is resolved), tdb_parse_record, tdb_traverse, tdb_traverse_read and tdb_check - all take void * and must internally convert it to the argument type they - were expecting. -\end_layout - -\begin_layout Standard -If this type changes, the compiler will not produce warnings on the callers, - since it only sees void *. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -With careful use of macros, we can create callback functions which give - a warning when used on gcc and the types of the callback and its private - argument differ. - Unsupported compilers will not give a warning, which is no worse than now. - In addition, the callbacks become clearer, as they need not use void * - for their parameter. -\end_layout - -\begin_layout Standard -See CCAN's typesafe_cb module at http://ccan.ozlabs.org/info/typesafe_cb.html -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. -\end_layout - -\begin_layout Subsection -TDB_CLEAR_IF_FIRST Must Be Specified On All Opens, tdb_reopen_all Problematic -\end_layout - -\begin_layout Standard -The TDB_CLEAR_IF_FIRST flag to tdb_open indicates that the TDB file should - be cleared if the caller discovers it is the only process with the TDB - open. - However, if any caller does not specify TDB_CLEAR_IF_FIRST it will not - be detected, so will have the TDB erased underneath them (usually resulting - in a crash). -\end_layout - -\begin_layout Standard -There is a similar issue on fork(); if the parent exits (or otherwise closes - the tdb) before the child calls tdb_reopen_all() to establish the lock - used to indicate the TDB is opened by someone, a TDB_CLEAR_IF_FIRST opener - at that moment will believe it alone has opened the TDB and will erase - it. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Remove TDB_CLEAR_IF_FIRST. - Other workarounds are possible, but see -\begin_inset CommandInset ref -LatexCommand ref -reference "TDB_CLEAR_IF_FIRST-Imposes-Performance" - -\end_inset - -. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard - -\change_deleted 0 1298979699 -Incomplete, TDB_CLEAR_IF_FIRST still defined, but not implemented. -\change_inserted 0 1298979700 -Complete. -\change_unchanged - -\end_layout - -\begin_layout Subsection -Extending The Header Is Difficult -\end_layout - -\begin_layout Standard -We have reserved (zeroed) words in the TDB header, which can be used for - future features. - If the future features are compulsory, the version number must be updated - to prevent old code from accessing the database. - But if the future feature is optional, we have no way of telling if older - code is accessing the database or not. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The header should contain a -\begin_inset Quotes eld -\end_inset - -format variant -\begin_inset Quotes erd -\end_inset - - value (64-bit). - This is divided into two 32-bit parts: -\end_layout - -\begin_layout Enumerate -The lower part reflects the format variant understood by code accessing - the database. -\end_layout - -\begin_layout Enumerate -The upper part reflects the format variant you must understand to write - to the database (otherwise you can only open for reading). -\end_layout - -\begin_layout Standard -The latter field can only be written at creation time, the former should - be written under the OPEN_LOCK when opening the database for writing, if - the variant of the code is lower than the current lowest variant. -\end_layout - -\begin_layout Standard -This should allow backwards-compatible features to be added, and detection - if older code (which doesn't understand the feature) writes to the database. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. -\end_layout - -\begin_layout Subsection -Record Headers Are Not Expandible -\end_layout - -\begin_layout Standard -If we later want to add (say) checksums on keys and data, it would require - another format change, which we'd like to avoid. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -We often have extra padding at the tail of a record. - If we ensure that the first byte (if any) of this padding is zero, we will - have a way for future changes to detect code which doesn't understand a - new format: the new code would write (say) a 1 at the tail, and thus if - there is no tail or the first byte is 0, we would know the extension is - not present on that record. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Incomplete. -\end_layout - -\begin_layout Subsection -TDB Does Not Use Talloc -\end_layout - -\begin_layout Standard -Many users of TDB (particularly Samba) use the talloc allocator, and thus - have to wrap TDB in a talloc context to use it conveniently. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The allocation within TDB is not complicated enough to justify the use of - talloc, and I am reluctant to force another (excellent) library on TDB - users. - Nonetheless a compromise is possible. - An attribute (see -\begin_inset CommandInset ref -LatexCommand ref -reference "attributes" - -\end_inset - -) can be added later to tdb_open() to provide an alternate allocation mechanism, - specifically for talloc but usable by any other allocator (which would - ignore the -\begin_inset Quotes eld -\end_inset - -context -\begin_inset Quotes erd -\end_inset - - argument). -\end_layout - -\begin_layout Standard -This would form a talloc heirarchy as expected, but the caller would still - have to attach a destructor to the tdb context returned from tdb_open to - close it. - All TDB_DATA fields would be children of the tdb_context, and the caller - would still have to manage them (using talloc_free() or talloc_steal()). -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\begin_layout Section -Performance And Scalability Issues -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "TDB_CLEAR_IF_FIRST-Imposes-Performance" - -\end_inset - -TDB_CLEAR_IF_FIRST Imposes Performance Penalty -\end_layout - -\begin_layout Standard -When TDB_CLEAR_IF_FIRST is specified, a 1-byte read lock is placed at offset - 4 (aka. - the ACTIVE_LOCK). - While these locks never conflict in normal tdb usage, they do add substantial - overhead for most fcntl lock implementations when the kernel scans to detect - if a lock conflict exists. - This is often a single linked list, making the time to acquire and release - a fcntl lock O(N) where N is the number of processes with the TDB open, - not the number actually doing work. -\end_layout - -\begin_layout Standard -In a Samba server it is common to have huge numbers of clients sitting idle, - and thus they have weaned themselves off the TDB_CLEAR_IF_FIRST flag. -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -There is a flag to tdb_reopen_all() which is used for this optimization: - if the parent process will outlive the child, the child does not need the - ACTIVE_LOCK. - This is a workaround for this very performance issue. -\end_layout - -\end_inset - - -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Remove the flag. - It was a neat idea, but even trivial servers tend to know when they are - initializing for the first time and can simply unlink the old tdb at that - point. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard - -\change_deleted 0 1298979837 -Incomplete; TDB_CLEAR_IF_FIRST still defined, but does nothing. -\change_inserted 0 1298979837 -Complete. -\change_unchanged - -\end_layout - -\begin_layout Subsection -TDB Files Have a 4G Limit -\end_layout - -\begin_layout Standard -This seems to be becoming an issue (so much for -\begin_inset Quotes eld -\end_inset - -trivial -\begin_inset Quotes erd -\end_inset - -!), particularly for ldb. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -A new, incompatible TDB format which uses 64 bit offsets internally rather - than 32 bit as now. - For simplicity of endian conversion (which TDB does on the fly if required), - all values will be 64 bit on disk. - In practice, some upper bits may be used for other purposes, but at least - 56 bits will be available for file offsets. -\end_layout - -\begin_layout Standard -tdb_open() will automatically detect the old version, and even create them - if TDB_VERSION6 is specified to tdb_open. -\end_layout - -\begin_layout Standard -32 bit processes will still be able to access TDBs larger than 4G (assuming - that their off_t allows them to seek to 64 bits), they will gracefully - fall back as they fail to mmap. - This can happen already with large TDBs. -\end_layout - -\begin_layout Standard -Old versions of tdb will fail to open the new TDB files (since 28 August - 2009, commit 398d0c29290: prior to that any unrecognized file format would - be erased and initialized as a fresh tdb!) -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -TDB Records Have a 4G Limit -\end_layout - -\begin_layout Standard -This has not been a reported problem, and the API uses size_t which can - be 64 bit on 64 bit platforms. - However, other limits may have made such an issue moot. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Record sizes will be 64 bit, with an error returned on 32 bit platforms - which try to access such records (the current implementation would return - TDB_ERR_OOM in a similar case). - It seems unlikely that 32 bit keys will be a limitation, so the implementation - may not support this (see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:Records-Incur-A" - -\end_inset - -). -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Hash Size Is Determined At TDB Creation Time -\end_layout - -\begin_layout Standard -TDB contains a number of hash chains in the header; the number is specified - at creation time, and defaults to 131. - This is such a bottleneck on large databases (as each hash chain gets quite - long), that LDB uses 10,000 for this hash. - In general it is impossible to know what the 'right' answer is at database - creation time. -\end_layout - -\begin_layout Subsubsection -\begin_inset CommandInset label -LatexCommand label -name "sub:Hash-Size-Solution" - -\end_inset - -Proposed Solution -\end_layout - -\begin_layout Standard -After comprehensive performance testing on various scalable hash variants -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -http://rusty.ozlabs.org/?p=89 and http://rusty.ozlabs.org/?p=94 This was annoying - because I was previously convinced that an expanding tree of hashes would - be very close to optimal. -\end_layout - -\end_inset - -, it became clear that it is hard to beat a straight linear hash table which - doubles in size when it reaches saturation. - Unfortunately, altering the hash table introduces serious locking complications -: the entire hash table needs to be locked to enlarge the hash table, and - others might be holding locks. - Particularly insidious are insertions done under tdb_chainlock. -\end_layout - -\begin_layout Standard -Thus an expanding layered hash will be used: an array of hash groups, with - each hash group exploding into pointers to lower hash groups once it fills, - turning into a hash tree. - This has implications for locking: we must lock the entire group in case - we need to expand it, yet we don't know how deep the tree is at that point. -\end_layout - -\begin_layout Standard -Note that bits from the hash table entries should be stolen to hold more - hash bits to reduce the penalty of collisions. - We can use the otherwise-unused lower 3 bits. - If we limit the size of the database to 64 exabytes, we can use the top - 8 bits of the hash entry as well. - These 11 bits would reduce false positives down to 1 in 2000 which is more - than we need: we can use one of the bits to indicate that the extra hash - bits are valid. - This means we can choose not to re-hash all entries when we expand a hash - group; simply use the next bits we need and mark them invalid. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "TDB-Freelist-Is" - -\end_inset - -TDB Freelist Is Highly Contended -\end_layout - -\begin_layout Standard -TDB uses a single linked list for the free list. - Allocation occurs as follows, using heuristics which have evolved over - time: -\end_layout - -\begin_layout Enumerate -Get the free list lock for this whole operation. -\end_layout - -\begin_layout Enumerate -Multiply length by 1.25, so we always over-allocate by 25%. -\end_layout - -\begin_layout Enumerate -Set the slack multiplier to 1. -\end_layout - -\begin_layout Enumerate -Examine the current freelist entry: if it is > length but < the current - best case, remember it as the best case. -\end_layout - -\begin_layout Enumerate -Multiply the slack multiplier by 1.05. -\end_layout - -\begin_layout Enumerate -If our best fit so far is less than length * slack multiplier, return it. - The slack will be turned into a new free record if it's large enough. -\end_layout - -\begin_layout Enumerate -Otherwise, go onto the next freelist entry. -\end_layout - -\begin_layout Standard -Deleting a record occurs as follows: -\end_layout - -\begin_layout Enumerate -Lock the hash chain for this whole operation. -\end_layout - -\begin_layout Enumerate -Walk the chain to find the record, keeping the prev pointer offset. -\end_layout - -\begin_layout Enumerate -If max_dead is non-zero: -\end_layout - -\begin_deeper -\begin_layout Enumerate -Walk the hash chain again and count the dead records. -\end_layout - -\begin_layout Enumerate -If it's more than max_dead, bulk free all the dead ones (similar to steps - 4 and below, but the lock is only obtained once). -\end_layout - -\begin_layout Enumerate -Simply mark this record as dead and return. - -\end_layout - -\end_deeper -\begin_layout Enumerate -Get the free list lock for the remainder of this operation. -\end_layout - -\begin_layout Enumerate -\begin_inset CommandInset label -LatexCommand label -name "right-merging" - -\end_inset - -Examine the following block to see if it is free; if so, enlarge the current - block and remove that block from the free list. - This was disabled, as removal from the free list was O(entries-in-free-list). -\end_layout - -\begin_layout Enumerate -Examine the preceeding block to see if it is free: for this reason, each - block has a 32-bit tailer which indicates its length. - If it is free, expand it to cover our new block and return. -\end_layout - -\begin_layout Enumerate -Otherwise, prepend ourselves to the free list. -\end_layout - -\begin_layout Standard -Disabling right-merging (step -\begin_inset CommandInset ref -LatexCommand ref -reference "right-merging" - -\end_inset - -) causes fragmentation; the other heuristics proved insufficient to address - this, so the final answer to this was that when we expand the TDB file - inside a transaction commit, we repack the entire tdb. -\end_layout - -\begin_layout Standard -The single list lock limits our allocation rate; due to the other issues - this is not currently seen as a bottleneck. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The first step is to remove all the current heuristics, as they obviously - interact, then examine them once the lock contention is addressed. -\end_layout - -\begin_layout Standard -The free list must be split to reduce contention. - Assuming perfect free merging, we can at most have 1 free list entry for - each entry. - This implies that the number of free lists is related to the size of the - hash table, but as it is rare to walk a large number of free list entries - we can use far fewer, say 1/32 of the number of hash buckets. -\end_layout - -\begin_layout Standard -It seems tempting to try to reuse the hash implementation which we use for - records here, but we have two ways of searching for free entries: for allocatio -n we search by size (and possibly zone) which produces too many clashes - for our hash table to handle well, and for coalescing we search by address. - Thus an array of doubly-linked free lists seems preferable. -\end_layout - -\begin_layout Standard -There are various benefits in using per-size free lists (see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:TDB-Becomes-Fragmented" - -\end_inset - -) but it's not clear this would reduce contention in the common case where - all processes are allocating/freeing the same size. - Thus we almost certainly need to divide in other ways: the most obvious - is to divide the file into zones, and using a free list (or table of free - lists) for each. - This approximates address ordering. -\end_layout - -\begin_layout Standard -Unfortunately it is difficult to know what heuristics should be used to - determine zone sizes, and our transaction code relies on being able to - create a -\begin_inset Quotes eld -\end_inset - -recovery area -\begin_inset Quotes erd -\end_inset - - by simply appending to the file (difficult if it would need to create a - new zone header). - Thus we use a linked-list of free tables; currently we only ever create - one, but if there is more than one we choose one at random to use. - In future we may use heuristics to add new free tables on contention. - We only expand the file when all free tables are exhausted. -\end_layout - -\begin_layout Standard -The basic algorithm is as follows. - Freeing is simple: -\end_layout - -\begin_layout Enumerate -Identify the correct free list. -\end_layout - -\begin_layout Enumerate -Lock the corresponding list. -\end_layout - -\begin_layout Enumerate -Re-check the list (we didn't have a lock, sizes could have changed): relock - if necessary. -\end_layout - -\begin_layout Enumerate -Place the freed entry in the list. -\end_layout - -\begin_layout Standard -Allocation is a little more complicated, as we perform delayed coalescing - at this point: -\end_layout - -\begin_layout Enumerate -Pick a free table; usually the previous one. -\end_layout - -\begin_layout Enumerate -Lock the corresponding list. -\end_layout - -\begin_layout Enumerate -If the top entry is -large enough, remove it from the list and return it. -\end_layout - -\begin_layout Enumerate -Otherwise, coalesce entries in the list.If there was no entry large enough, - unlock the list and try the next largest list -\end_layout - -\begin_layout Enumerate -If no list has an entry which meets our needs, try the next free table. -\end_layout - -\begin_layout Enumerate -If no zone satisfies, expand the file. -\end_layout - -\begin_layout Standard -This optimizes rapid insert/delete of free list entries by not coalescing - them all the time.. - First-fit address ordering ordering seems to be fairly good for keeping - fragmentation low (see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:TDB-Becomes-Fragmented" - -\end_inset - -). - Note that address ordering does not need a tailer to coalesce, though if - we needed one we could have one cheaply: see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:Records-Incur-A" - -\end_inset - -. - -\end_layout - -\begin_layout Standard -Each free entry has the free table number in the header: less than 255. - It also contains a doubly-linked list for easy deletion. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "sub:TDB-Becomes-Fragmented" - -\end_inset - -TDB Becomes Fragmented -\end_layout - -\begin_layout Standard -Much of this is a result of allocation strategy -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -The Memory Fragmentation Problem: Solved? Johnstone & Wilson 1995 ftp://ftp.cs.ute -xas.edu/pub/garbage/malloc/ismm98.ps -\end_layout - -\end_inset - - and deliberate hobbling of coalescing; internal fragmentation (aka overallocati -on) is deliberately set at 25%, and external fragmentation is only cured - by the decision to repack the entire db when a transaction commit needs - to enlarge the file. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The 25% overhead on allocation works in practice for ldb because indexes - tend to expand by one record at a time. - This internal fragmentation can be resolved by having an -\begin_inset Quotes eld -\end_inset - -expanded -\begin_inset Quotes erd -\end_inset - - bit in the header to note entries that have previously expanded, and allocating - more space for them. -\end_layout - -\begin_layout Standard -There are is a spectrum of possible solutions for external fragmentation: - one is to use a fragmentation-avoiding allocation strategy such as best-fit - address-order allocator. - The other end of the spectrum would be to use a bump allocator (very fast - and simple) and simply repack the file when we reach the end. -\end_layout - -\begin_layout Standard -There are three problems with efficient fragmentation-avoiding allocators: - they are non-trivial, they tend to use a single free list for each size, - and there's no evidence that tdb allocation patterns will match those recorded - for general allocators (though it seems likely). -\end_layout - -\begin_layout Standard -Thus we don't spend too much effort on external fragmentation; we will be - no worse than the current code if we need to repack on occasion. - More effort is spent on reducing freelist contention, and reducing overhead. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "sub:Records-Incur-A" - -\end_inset - -Records Incur A 28-Byte Overhead -\end_layout - -\begin_layout Standard -Each TDB record has a header as follows: -\end_layout - -\begin_layout LyX-Code -struct tdb_record { -\end_layout - -\begin_layout LyX-Code - tdb_off_t next; /* offset of the next record in the list */ -\end_layout - -\begin_layout LyX-Code - tdb_len_t rec_len; /* total byte length of record */ -\end_layout - -\begin_layout LyX-Code - tdb_len_t key_len; /* byte length of key */ -\end_layout - -\begin_layout LyX-Code - tdb_len_t data_len; /* byte length of data */ -\end_layout - -\begin_layout LyX-Code - uint32_t full_hash; /* the full 32 bit hash of the key */ -\end_layout - -\begin_layout LyX-Code - uint32_t magic; /* try to catch errors */ -\end_layout - -\begin_layout LyX-Code - /* the following union is implied: -\end_layout - -\begin_layout LyX-Code - union { -\end_layout - -\begin_layout LyX-Code - char record[rec_len]; -\end_layout - -\begin_layout LyX-Code - struct { -\end_layout - -\begin_layout LyX-Code - char key[key_len]; -\end_layout - -\begin_layout LyX-Code - char data[data_len]; -\end_layout - -\begin_layout LyX-Code - } -\end_layout - -\begin_layout LyX-Code - uint32_t totalsize; (tailer) -\end_layout - -\begin_layout LyX-Code - } -\end_layout - -\begin_layout LyX-Code - */ -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout Standard -Naively, this would double to a 56-byte overhead on a 64 bit implementation. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -We can use various techniques to reduce this for an allocated block: -\end_layout - -\begin_layout Enumerate -The 'next' pointer is not required, as we are using a flat hash table. -\end_layout - -\begin_layout Enumerate -'rec_len' can instead be expressed as an addition to key_len and data_len - (it accounts for wasted or overallocated length in the record). - Since the record length is always a multiple of 8, we can conveniently - fit it in 32 bits (representing up to 35 bits). -\end_layout - -\begin_layout Enumerate -'key_len' and 'data_len' can be reduced. - I'm unwilling to restrict 'data_len' to 32 bits, but instead we can combine - the two into one 64-bit field and using a 5 bit value which indicates at - what bit to divide the two. - Keys are unlikely to scale as fast as data, so I'm assuming a maximum key - size of 32 bits. -\end_layout - -\begin_layout Enumerate -'full_hash' is used to avoid a memcmp on the -\begin_inset Quotes eld -\end_inset - -miss -\begin_inset Quotes erd -\end_inset - - case, but this is diminishing returns after a handful of bits (at 10 bits, - it reduces 99.9% of false memcmp). - As an aside, as the lower bits are already incorporated in the hash table - resolution, the upper bits should be used here. - Note that it's not clear that these bits will be a win, given the extra - bits in the hash table itself (see -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:Hash-Size-Solution" - -\end_inset - -). -\end_layout - -\begin_layout Enumerate -'magic' does not need to be enlarged: it currently reflects one of 5 values - (used, free, dead, recovery, and unused_recovery). - It is useful for quick sanity checking however, and should not be eliminated. -\end_layout - -\begin_layout Enumerate -'tailer' is only used to coalesce free blocks (so a block to the right can - find the header to check if this block is free). - This can be replaced by a single 'free' bit in the header of the following - block (and the tailer only exists in free blocks). -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -This technique from Thomas Standish. - Data Structure Techniques. - Addison-Wesley, Reading, Massachusetts, 1980. -\end_layout - -\end_inset - - The current proposed coalescing algorithm doesn't need this, however. -\end_layout - -\begin_layout Standard -This produces a 16 byte used header like this: -\end_layout - -\begin_layout LyX-Code -struct tdb_used_record { -\end_layout - -\begin_layout LyX-Code - uint32_t used_magic : 16, -\end_layout - -\begin_layout LyX-Code - -\end_layout - -\begin_layout LyX-Code - key_data_divide: 5, -\end_layout - -\begin_layout LyX-Code - top_hash: 11; -\end_layout - -\begin_layout LyX-Code - uint32_t extra_octets; -\end_layout - -\begin_layout LyX-Code - uint64_t key_and_data_len; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout Standard -And a free record like this: -\end_layout - -\begin_layout LyX-Code -struct tdb_free_record { -\end_layout - -\begin_layout LyX-Code - uint64_t free_magic: 8, -\end_layout - -\begin_layout LyX-Code - prev : 56; -\end_layout - -\begin_layout LyX-Code - -\end_layout - -\begin_layout LyX-Code - uint64_t free_table: 8, -\end_layout - -\begin_layout LyX-Code - total_length : 56 -\end_layout - -\begin_layout LyX-Code - uint64_t next;; -\end_layout - -\begin_layout LyX-Code -}; -\end_layout - -\begin_layout Standard - -\change_deleted 0 1291206079 - -\change_unchanged -Note that by limiting valid offsets to 56 bits, we can pack everything we - need into 3 64-byte words, meaning our minimum record size is 8 bytes. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Transaction Commit Requires 4 fdatasync -\end_layout - -\begin_layout Standard -The current transaction algorithm is: -\end_layout - -\begin_layout Enumerate -write_recovery_data(); -\end_layout - -\begin_layout Enumerate -sync(); -\end_layout - -\begin_layout Enumerate -write_recovery_header(); -\end_layout - -\begin_layout Enumerate -sync(); -\end_layout - -\begin_layout Enumerate -overwrite_with_new_data(); -\end_layout - -\begin_layout Enumerate -sync(); -\end_layout - -\begin_layout Enumerate -remove_recovery_header(); -\end_layout - -\begin_layout Enumerate -sync(); -\end_layout - -\begin_layout Standard -On current ext3, each sync flushes all data to disk, so the next 3 syncs - are relatively expensive. - But this could become a performance bottleneck on other filesystems such - as ext4. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Neil Brown points out that this is overzealous, and only one sync is needed: -\end_layout - -\begin_layout Enumerate -Bundle the recovery data, a transaction counter and a strong checksum of - the new data. -\end_layout - -\begin_layout Enumerate -Strong checksum that whole bundle. -\end_layout - -\begin_layout Enumerate -Store the bundle in the database. -\end_layout - -\begin_layout Enumerate -Overwrite the oldest of the two recovery pointers in the header (identified - using the transaction counter) with the offset of this bundle. -\end_layout - -\begin_layout Enumerate -sync. -\end_layout - -\begin_layout Enumerate -Write the new data to the file. -\end_layout - -\begin_layout Standard -Checking for recovery means identifying the latest bundle with a valid checksum - and using the new data checksum to ensure that it has been applied. - This is more expensive than the current check, but need only be done at - open. - For running databases, a separate header field can be used to indicate - a transaction in progress; we need only check for recovery if this is set. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "sub:TDB-Does-Not" - -\end_inset - -TDB Does Not Have Snapshot Support -\end_layout - -\begin_layout Subsubsection -Proposed SolutionNone. - At some point you say -\begin_inset Quotes eld -\end_inset - -use a real database -\begin_inset Quotes erd -\end_inset - - (but see -\begin_inset CommandInset ref -LatexCommand ref -reference "replay-attribute" - -\end_inset - -). -\end_layout - -\begin_layout Standard -But as a thought experiment, if we implemented transactions to only overwrite - free entries (this is tricky: there must not be a header in each entry - which indicates whether it is free, but use of presence in metadata elsewhere), - and a pointer to the hash table, we could create an entirely new commit - without destroying existing data. - Then it would be easy to implement snapshots in a similar way. -\end_layout - -\begin_layout Standard -This would not allow arbitrary changes to the database, such as tdb_repack - does, and would require more space (since we have to preserve the current - and future entries at once). - If we used hash trees rather than one big hash table, we might only have - to rewrite some sections of the hash, too. -\end_layout - -\begin_layout Standard -We could then implement snapshots using a similar method, using multiple - different hash tables/free tables. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\begin_layout Subsection -Transactions Cannot Operate in Parallel -\end_layout - -\begin_layout Standard -This would be useless for ldb, as it hits the index records with just about - every update. - It would add significant complexity in resolving clashes, and cause the - all transaction callers to write their code to loop in the case where the - transactions spuriously failed. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None (but see -\begin_inset CommandInset ref -LatexCommand ref -reference "replay-attribute" - -\end_inset - -). - We could solve a small part of the problem by providing read-only transactions. - These would allow one write transaction to begin, but it could not commit - until all r/o transactions are done. - This would require a new RO_TRANSACTION_LOCK, which would be upgraded on - commit. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\begin_layout Subsection -Default Hash Function Is Suboptimal -\end_layout - -\begin_layout Standard -The Knuth-inspired multiplicative hash used by tdb is fairly slow (especially - if we expand it to 64 bits), and works best when the hash bucket size is - a prime number (which also means a slow modulus). - In addition, it is highly predictable which could potentially lead to a - Denial of Service attack in some TDB uses. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -The Jenkins lookup3 hash -\begin_inset Foot -status open - -\begin_layout Plain Layout -http://burtleburtle.net/bob/c/lookup3.c -\end_layout - -\end_inset - - is a fast and superbly-mixing hash. - It's used by the Linux kernel and almost everything else. - This has the particular properties that it takes an initial seed, and produces - two 32 bit hash numbers, which we can combine into a 64-bit hash. -\end_layout - -\begin_layout Standard -The seed should be created at tdb-creation time from some random source, - and placed in the header. - This is far from foolproof, but adds a little bit of protection against - hash bombing. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -\begin_inset CommandInset label -LatexCommand label -name "Reliable-Traversal-Adds" - -\end_inset - -Reliable Traversal Adds Complexity -\end_layout - -\begin_layout Standard -We lock a record during traversal iteration, and try to grab that lock in - the delete code. - If that grab on delete fails, we simply mark it deleted and continue onwards; - traversal checks for this condition and does the delete when it moves off - the record. -\end_layout - -\begin_layout Standard -If traversal terminates, the dead record may be left indefinitely. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -Remove reliability guarantees; see -\begin_inset CommandInset ref -LatexCommand ref -reference "traverse-Proposed-Solution" - -\end_inset - -. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Complete. -\end_layout - -\begin_layout Subsection -Fcntl Locking Adds Overhead -\end_layout - -\begin_layout Standard -Placing a fcntl lock means a system call, as does removing one. - This is actually one reason why transactions can be faster (everything - is locked once at transaction start). - In the uncontended case, this overhead can theoretically be eliminated. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None. -\end_layout - -\begin_layout Standard -We tried this before with spinlock support, in the early days of TDB, and - it didn't make much difference except in manufactured benchmarks. -\end_layout - -\begin_layout Standard -We could use spinlocks (with futex kernel support under Linux), but it means - that we lose automatic cleanup when a process dies with a lock. - There is a method of auto-cleanup under Linux, but it's not supported by - other operating systems. - We could reintroduce a clear-if-first-style lock and sweep for dead futexes - on open, but that wouldn't help the normal case of one concurrent opener - dying. - Increasingly elaborate repair schemes could be considered, but they require - an ABI change (everyone must use them) anyway, so there's no need to do - this at the same time as everything else. -\end_layout - -\begin_layout Subsection -Some Transactions Don't Require Durability -\end_layout - -\begin_layout Standard -Volker points out that gencache uses a CLEAR_IF_FIRST tdb for normal (fast) - usage, and occasionally empties the results into a transactional TDB. - This kind of usage prioritizes performance over durability: as long as - we are consistent, data can be lost. -\end_layout - -\begin_layout Standard -This would be more neatly implemented inside tdb: a -\begin_inset Quotes eld -\end_inset - -soft -\begin_inset Quotes erd -\end_inset - - transaction commit (ie. - syncless) which meant that data may be reverted on a crash. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\end_layout - -\begin_layout Standard -None. -\end_layout - -\begin_layout Standard -Unfortunately any transaction scheme which overwrites old data requires - a sync before that overwrite to avoid the possibility of corruption. -\end_layout - -\begin_layout Standard -It seems possible to use a scheme similar to that described in -\begin_inset CommandInset ref -LatexCommand ref -reference "sub:TDB-Does-Not" - -\end_inset - -,where transactions are committed without overwriting existing data, and - an array of top-level pointers were available in the header. - If the transaction is -\begin_inset Quotes eld -\end_inset - -soft -\begin_inset Quotes erd -\end_inset - - then we would not need a sync at all: existing processes would pick up - the new hash table and free list and work with that. -\end_layout - -\begin_layout Standard -At some later point, a sync would allow recovery of the old data into the - free lists (perhaps when the array of top-level pointers filled). - On crash, tdb_open() would examine the array of top levels, and apply the - transactions until it encountered an invalid checksum. -\end_layout - -\begin_layout Subsection -Tracing Is Fragile, Replay Is External -\end_layout - -\begin_layout Standard -The current TDB has compile-time-enabled tracing code, but it often breaks - as it is not enabled by default. - In a similar way, the ctdb code has an external wrapper which does replay - tracing so it can coordinate cluster-wide transactions. -\end_layout - -\begin_layout Subsubsection -Proposed Solution -\begin_inset CommandInset label -LatexCommand label -name "replay-attribute" - -\end_inset - - -\end_layout - -\begin_layout Standard -Tridge points out that an attribute can be later added to tdb_open (see - -\begin_inset CommandInset ref -LatexCommand ref -reference "attributes" - -\end_inset - -) to provide replay/trace hooks, which could become the basis for this and - future parallel transactions and snapshot support. -\end_layout - -\begin_layout Subsubsection -Status -\end_layout - -\begin_layout Standard -Deferred. -\end_layout - -\end_body -\end_document -@ - - -1.12 -log -@Add status, some fixes, linked freelists. -@ -text -@d53 1 -a53 7 - -\change_deleted 0 1291204535 -14-September -\change_inserted 0 1291204533 -1-December -\change_unchanged --2010 -a580 2 -\change_inserted 0 1291204563 - -a583 2 - -\change_inserted 0 1291204572 -a587 2 - -\change_inserted 0 1291204573 -a588 2 -\change_unchanged - -a629 2 -\change_inserted 0 1291204588 - -a632 2 - -\change_inserted 0 1291204588 -a636 2 - -\change_inserted 0 1291204631 -a639 2 -\change_unchanged - -a693 2 -\change_inserted 0 1291204639 - -a696 2 - -\change_inserted 0 1291204640 -d702 1 -a702 1 -\change_inserted 0 1291204665 -d704 2 -a728 2 -\change_inserted 0 1291204671 - -a731 2 - -\change_inserted 0 1291204671 -a735 2 - -\change_inserted 0 1291204673 -a736 2 -\change_unchanged - -a780 2 -\change_inserted 0 1291204731 - -a783 2 - -\change_inserted 0 1291204732 -a787 2 - -\change_inserted 0 1291204779 -a790 2 -\change_unchanged - -a842 2 -\change_inserted 0 1291204830 - -a845 2 - -\change_inserted 0 1291204831 -a849 2 - -\change_inserted 0 1291204834 -a850 2 -\change_unchanged - -d879 9 -a887 2 - deal of churn; we are better to guarantee that the tdb_errcode is per-thread - so the current programming model can be maintained. -d891 9 -d903 2 -a922 2 -\change_inserted 0 1291204847 - -a925 2 - -\change_inserted 0 1291204847 -d930 5 -a934 3 - -\change_inserted 0 1291204852 -Incomplete. -a1051 2 -\change_inserted 0 1291204881 - -a1054 2 - -\change_inserted 0 1291204881 -a1058 2 - -\change_inserted 0 1291204885 -a1059 2 -\change_unchanged - -a1140 2 -\change_inserted 0 1291204898 - -a1143 2 - -\change_inserted 0 1291204898 -a1147 2 - -\change_inserted 0 1291204901 -a1148 2 -\change_unchanged - -a1224 2 -\change_inserted 0 1291204908 - -a1227 2 - -\change_inserted 0 1291204908 -a1231 2 - -\change_inserted 0 1291204908 -a1232 2 -\change_unchanged - -a1271 2 -\change_inserted 0 1291204917 - -a1274 2 - -\change_inserted 0 1291204917 -a1278 2 - -\change_inserted 0 1291204920 -a1279 2 -\change_unchanged - -a1316 2 -\change_inserted 0 1291204927 - -a1319 2 - -\change_inserted 0 1291204928 -d1325 1 -a1325 1 -\change_inserted 0 1291204942 -d1327 2 -a1381 2 -\change_inserted 0 1291205003 - -a1384 2 - -\change_inserted 0 1291205004 -a1388 2 - -\change_inserted 0 1291205007 -a1411 2 -\change_inserted 0 1291205019 - -a1414 2 - -\change_inserted 0 1291205019 -a1418 2 - -\change_inserted 0 1291205023 -a1419 2 -\change_unchanged - -a1465 2 -\change_inserted 0 1291205029 - -a1468 2 - -\change_inserted 0 1291205029 -a1472 2 - -\change_inserted 0 1291206020 -a1473 2 -\change_unchanged - -a1528 2 -\change_inserted 0 1291205043 - -a1531 2 - -\change_inserted 0 1291205043 -d1537 1 -a1537 1 -\change_inserted 0 1291205057 -d1539 2 -a1589 2 -\change_inserted 0 1291205062 - -a1592 2 - -\change_inserted 0 1291205062 -a1596 2 - -\change_inserted 0 1291205062 -a1597 2 -\change_unchanged - -a1626 2 -\change_inserted 0 1291205072 - -a1629 2 - -\change_inserted 0 1291205073 -a1633 2 - -\change_inserted 0 1291205073 -a1634 2 -\change_unchanged - -a1674 4 - -\change_deleted 0 1291204504 - -\change_unchanged -a1699 2 -\change_inserted 0 1291205079 - -a1702 2 - -\change_inserted 0 1291205080 -a1706 2 - -\change_inserted 0 1291205080 -a1707 2 -\change_unchanged - -a1833 2 -\change_inserted 0 1291205090 - -d1869 2 -a1870 7 - is to divide the file into zones, and using a free list (or -\change_inserted 0 1291205498 -table -\change_deleted 0 1291205497 -set -\change_unchanged - of free lists) for each. -a1871 2 -\change_inserted 0 1291205203 - -a1874 2 - -\change_inserted 0 1291205358 -a1890 21 -\change_unchanged - -\end_layout - -\begin_layout Standard - -\change_deleted 0 1291205198 -Note that this means we need to split the free lists when we expand the - file; this is probably acceptable when we double the hash table size, since - that is such an expensive operation already. - In the case of increasing the file size, there is an optimization we can - use: if we use M in the formula above as the file size rounded up to the - next power of 2, we only need reshuffle free lists when the file size crosses - a power of 2 boundary, -\emph on -and -\emph default -reshuffling the free lists is trivial: we simply merge every consecutive - pair of free lists. -\change_unchanged - -d1899 1 -a1899 7 -Identify the correct -\change_inserted 0 1291205366 -free list -\change_deleted 0 1291205364 -zone -\change_unchanged -. -d1907 2 -a1908 7 -Re-check the -\change_inserted 0 1291205372 -list -\change_deleted 0 1291205371 -zone -\change_unchanged - (we didn't have a lock, sizes could have changed): relock if necessary. -d1912 1 -a1912 5 -Place the freed entry in the list -\change_deleted 0 1291205382 - for that zone -\change_unchanged -. -d1921 1 -a1921 15 -Pick a -\change_deleted 0 1291205403 -zone either the zone we last freed into, or based on a -\begin_inset Quotes eld -\end_inset - -random -\begin_inset Quotes erd -\end_inset - - number. -\change_inserted 0 1291205411 -free table; usually the previous one. -\change_unchanged - -a1925 10 -\change_deleted 0 1291205432 - -\end_layout - -\begin_layout Enumerate - -\change_deleted 0 1291205428 -Re-check the zone: relock if necessary. -\change_unchanged - -d1934 1 -a1934 7 - unlock the list and try the next -\change_inserted 0 1291205455 -largest list -\change_deleted 0 1291205452 -zone. -\change_inserted 0 1291205457 - -a1937 2 - -\change_inserted 0 1291205476 -a1938 2 -\change_unchanged - -a1966 2 -\change_inserted 0 1291205542 - -a1969 2 - -\change_inserted 0 1291205591 -a1971 70 -\change_unchanged - -\end_layout - -\begin_layout Standard - -\change_deleted 0 1291205539 -I anticipate that the number of entries in each free zone would be small, - but it might be worth using one free entry to hold pointers to the others - for cache efficiency. -\change_unchanged - -\end_layout - -\begin_layout Standard - -\change_deleted 0 1291205534 -\begin_inset CommandInset label -LatexCommand label -name "freelist-in-zone" - -\end_inset - -If we want to avoid locking complexity (enlarging the free lists when we - enlarge the file) we could place the array of free lists at the beginning - of each zone. - This means existing array lists never move, but means that a record cannot - be larger than a zone. - That in turn implies that zones should be variable sized (say, power of - 2), which makes the question -\begin_inset Quotes eld -\end_inset - -what zone is this record in? -\begin_inset Quotes erd -\end_inset - - much harder (and -\begin_inset Quotes eld -\end_inset - -pick a random zone -\begin_inset Quotes erd -\end_inset - -, but that's less common). - It could be done with as few as 4 bits from the record header. -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout -Using -\begin_inset Formula $2^{16+N*3}$ -\end_inset - -means 0 gives a minimal 65536-byte zone, 15 gives the maximal -\begin_inset Formula $2^{61}$ -\end_inset - - byte zone. - Zones range in factor of 8 steps. - Given the zone size for the zone the current record is in, we can determine - the start of the zone. -\end_layout - -\end_inset - - -\change_inserted 0 1291205139 - -d2218 1 -a2218 5 - uint32_t -\change_inserted 0 1291205758 -used_ -\change_unchanged -magic : 16, -a2222 4 -\change_deleted 0 1291205693 - prev_is_free: 1, -\change_unchanged - -d2230 1 -a2230 7 - top_hash: 1 -\change_inserted 0 1291205704 -1 -\change_deleted 0 1291205704 -0 -\change_unchanged -; -d2254 1 -a2254 9 - uint -\change_inserted 0 1291205725 -64 -\change_deleted 0 1291205723 -32 -\change_unchanged -_t -\change_inserted 0 1291205753 -free_magic: 8, -a2257 2 - -\change_inserted 0 1291205746 -a2262 24 -\change_deleted 0 1291205749 -free_magic; -\change_unchanged - -\end_layout - -\begin_layout LyX-Code - uint64_t -\change_inserted 0 1291205786 -free_table: 8, -\end_layout - -\begin_layout LyX-Code - -\change_inserted 0 1291205788 - -\change_unchanged -total_length -\change_inserted 0 1291205792 - : 56 -\change_deleted 0 1291205790 -; -\change_unchanged - -d2266 1 -a2266 7 - uint64_t -\change_deleted 0 1291205801 -prev, -\change_unchanged -next; -\change_deleted 0 1291205811 - -d2270 1 -a2270 3 - -\change_deleted 0 1291205811 - ... -d2274 1 -a2274 5 - -\change_deleted 0 1291205808 - uint64_t tailer -\change_unchanged -; -d2283 5 -a2287 16 -\change_deleted 0 1291205827 -We might want to take some bits from the used record's top_hash (and the - free record which has 32 bits of padding to spare anyway) if we use variable - sized zones. - See -\begin_inset CommandInset ref -LatexCommand ref -reference "freelist-in-zone" - -\end_inset - -. - -\change_inserted 0 1291205885 - Note that by limiting valid offsets to 56 bits, we can pack everything - we need into 3 64-byte words, meaning our minimum record size is 8 bytes. -a2290 2 - -\change_inserted 0 1291205886 -a2294 2 - -\change_inserted 0 1291205886 -a2295 2 -\change_unchanged - -a2385 2 -\change_inserted 0 1291205894 - -a2388 2 - -\change_inserted 0 1291205894 -a2392 2 - -\change_inserted 0 1291205902 -a2393 2 -\change_unchanged - -a2415 4 - -\change_deleted 0 1291204504 - -\change_unchanged -a2445 2 -\change_inserted 0 1291205910 - -a2448 2 - -\change_inserted 0 1291205910 -a2452 2 - -\change_inserted 0 1291205914 -a2453 2 -\change_unchanged - -a2485 2 -\change_inserted 0 1291205919 - -a2488 2 - -\change_inserted 0 1291205919 -a2492 2 - -\change_inserted 0 1291205922 -a2493 2 -\change_unchanged - -a2533 2 -\change_inserted 0 1291205929 - -a2536 2 - -\change_inserted 0 1291205929 -a2540 2 - -\change_inserted 0 1291205929 -a2541 2 -\change_unchanged - -a2578 2 -\change_inserted 0 1291205932 - -a2581 2 - -\change_inserted 0 1291205933 -a2585 2 - -\change_inserted 0 1291205933 -a2586 2 -\change_unchanged - -a2724 2 -\change_inserted 0 1291205944 - -a2727 2 - -\change_inserted 0 1291205945 -a2731 2 - -\change_inserted 0 1291205948 -a2732 2 -\change_unchanged - -@ - - -1.11 -log -@Merge changes -@ -text -@d53 7 -a59 1 -14-September-2010 -d587 16 -d644 18 -d716 16 -d753 16 -d813 18 -d883 16 -d953 16 -d1084 16 -d1181 16 -d1273 16 -d1328 16 -d1381 16 -d1447 19 -a1465 2 - if older code (which doesn't understand the feature) writes to the database.Reco -rd Headers Are Not Expandible -d1484 16 -d1546 16 -d1617 16 -d1680 16 -d1725 16 -d1810 16 -d1951 8 -a1958 3 -Proposed SolutionThe first step is to remove all the current heuristics, - as they obviously interact, then examine them once the lock contention - is addressed. -d1989 7 -a1995 2 - is to divide the file into zones, and using a free list (or set of free - lists) for each. -d1997 2 -d2002 25 -d2039 2 -d2049 7 -a2055 1 -Identify the correct zone. -d2063 7 -a2069 2 -Re-check the zone (we didn't have a lock, sizes could have changed): relock - if necessary. -d2073 5 -a2077 1 -Place the freed entry in the list for that zone. -d2086 3 -a2088 1 -Pick a zone either the zone we last freed into, or based on a -d2097 4 -d2105 2 -d2110 2 -d2113 2 -d2123 15 -a2137 1 - unlock the list and try the next zone. -d2166 11 -d2180 2 -d2185 2 -d2190 2 -d2223 1 -a2223 1 -status open -d2243 2 -d2491 5 -a2495 1 - uint32_t magic : 16, -d2499 2 -d2502 2 -d2511 7 -a2517 1 - top_hash: 10; -d2541 29 -a2569 1 - uint32_t free_magic; -d2573 11 -a2583 1 - uint64_t total_length; -d2587 7 -a2593 1 - uint64_t prev, next; -d2597 2 -d2603 5 -a2607 1 - uint64_t tailer; -d2615 2 -d2628 18 -d2736 16 -d2808 16 -d2856 16 -d2912 16 -d2965 16 -d3119 16 -@ - - -1.10 -log -@Tracing attribute, talloc support. -@ -text -@d1 1 -a1 1 -#LyX 1.6.5 created this file. For more info see http://www.lyx.org/ -d53 1 -a53 7 - -\change_deleted 0 1283307542 -26-July -\change_inserted 0 1284423485 -14-September -\change_unchanged --2010 -a472 2 -\change_inserted 0 1284422789 - -a479 2 -\change_unchanged - -a838 2 - -\change_inserted 0 1284016998 -a846 2 -\change_unchanged - -a1194 2 -\change_inserted 0 1284015637 - -a1197 2 - -\change_inserted 0 1284015716 -a1201 2 - -\change_inserted 0 1284015906 -a1210 2 - -\change_inserted 0 1284015637 -a1214 2 - -\change_inserted 0 1284016114 -a1227 2 - -\change_inserted 0 1284016149 -a1232 2 - -\change_inserted 0 1284016639 -a1237 2 - -\change_inserted 0 1284016821 -a1243 2 - -\change_inserted 0 1284016803 -d1245 2 -a1246 9 - if older code (which doesn't understand the feature) writes to the database. -\change_deleted 0 1284016101 - -\end_layout - -\begin_layout Subsection - -\change_inserted 0 1284015634 -Record Headers Are Not Expandible -a1249 2 - -\change_inserted 0 1284015634 -a1254 2 - -\change_inserted 0 1284015634 -a1258 2 - -\change_inserted 0 1284422552 -a1267 2 - -\change_inserted 0 1284422568 -a1271 2 - -\change_inserted 0 1284422646 -a1276 2 - -\change_inserted 0 1284422656 -a1280 2 - -\change_inserted 0 1284423065 -a1305 2 - -\change_inserted 0 1284423042 -a1310 2 -\change_unchanged - -a1457 2 - -\change_inserted 0 1283336713 -a1463 2 - -\change_unchanged -d1482 2 -d1485 1 -a1485 51 -\change_deleted 0 1283307675 -There are three details which become important: -\end_layout - -\begin_layout Enumerate - -\change_deleted 0 1283307675 -On encountering a full bucket, we use the next bucket. -\end_layout - -\begin_layout Enumerate - -\change_deleted 0 1283307675 -Extra hash bits are stored with the offset, to reduce comparisons. -\end_layout - -\begin_layout Enumerate - -\change_deleted 0 1283307675 -A marker entry is used on deleting an entry. -\end_layout - -\begin_layout Standard - -\change_deleted 0 1283307675 -The doubling of the table must be done under a transaction; we will not - reduce it on deletion, so it will be an unusual case. - It will either be placed at the head (other entries will be moved out the - way so we can expand). - We could have a pointer in the header to the current hashtable location, - but that pointer would have to be read frequently to check for hashtable - moves. -\end_layout - -\begin_layout Standard - -\change_deleted 0 1283307675 -The locking for this is slightly more complex than the chained case; we - currently have one lock per bucket, and that means we would need to expand - the lock if we overflow to the next bucket. - The frequency of such collisions will effect our locking heuristics: we - can always lock more buckets than we need. -\end_layout - -\begin_layout Standard - -\change_deleted 0 1283307675 -One possible optimization is to only re-check the hash size on an insert - or a lookup miss. - -\change_inserted 0 1283307770 -a1492 2 - -\change_inserted 0 1283336187 -a1500 2 - -\change_inserted 0 1283336586 -a1510 2 -\change_unchanged - -d1636 3 -a1638 8 -Proposed Solution -\change_deleted 0 1283336858 - -\end_layout - -\begin_layout Standard -The first step is to remove all the current heuristics, as they obviously - interact, then examine them once the lock contention is addressed. -a1647 2 -\change_inserted 0 1283336910 - -a1650 2 - -\change_inserted 0 1283337052 -a1655 2 -\change_unchanged - -a1776 2 -\change_inserted 0 1283309850 - -a1779 2 - -\change_inserted 0 1283337216 -a1813 2 - -\change_inserted 0 1284424151 -a1825 2 -\change_unchanged - -a1830 2 -\change_unchanged - -a2031 2 - -\change_inserted 0 1283336739 -a2040 2 -\change_unchanged - -a2117 2 -\change_inserted 0 1283337133 - -a2120 2 - -\change_inserted 0 1283337139 -a2121 2 -\change_unchanged - -a2136 2 - -\change_inserted 0 1283337235 -a2147 2 -\change_unchanged - -d2251 1 -a2251 7 -Proposed Solution -\change_deleted 0 1284423472 - -\end_layout - -\begin_layout Standard -None. -d2261 1 -a2261 1 -\change_inserted 0 1284423891 -d2263 1 -a2263 4 -\change_deleted 0 1284423891 -. - -\change_inserted 0 1284423901 -a2271 2 -\change_unchanged - -a2293 2 -\change_inserted 0 1284423495 - -a2312 2 - -\change_inserted 0 1284424201 -d2321 1 -a2321 3 - -\change_unchanged -We could solve a small part of the problem by providing read-only transactions. -a2505 2 -\change_inserted 0 1284423555 - -a2508 2 - -\change_inserted 0 1284423617 -a2512 2 - -\change_inserted 0 1284423719 -a2519 2 - -\change_inserted 0 1284423864 -a2530 2 - -\change_inserted 0 1284423850 -a2540 2 -\change_unchanged - -@ - - -1.9 -log -@Extension mechanism. -@ -text -@d56 2 -a57 2 -\change_inserted 0 1284016854 -9-September -d479 11 -d1303 1 -a1303 1 -\change_inserted 0 1284016847 -d1310 56 -d1945 1 -a1945 1 -\change_inserted 0 1283310945 -d1956 2 -d2402 2 -d2416 4 -d2421 12 -d2455 2 -d2476 12 -d2673 47 -@ - - -1.8 -log -@Remove bogus footnote -@ -text -@d56 2 -a57 2 -\change_inserted 0 1283307544 -1-September -d838 12 -d1198 103 -@ - - -1.7 -log -@Moving hash table does not work. -@ -text -@a1436 12 -\begin_inset Foot -status collapsed - -\begin_layout Plain Layout - -\change_inserted 0 1283336450 -If we make the hash offsets zone-relative, then this only restricts the - zone size, not the overall database size. -\end_layout - -\end_inset - -@ - - -1.6 -log -@Commit changes -@ -text -@d38 1 -a38 1 -\author "" -d53 7 -a59 1 -26-July-2010 -d1333 10 -d1361 3 -a1363 1 - There are three details which become important: -d1367 2 -d1373 2 -d1379 2 -d1385 2 -d1397 2 -d1407 2 -d1411 45 -d1582 2 -d1598 14 -d1733 62 -d1996 13 -d2086 10 -d2110 15 -a2124 1 -\begin_layout LyX-Code -@ - - -1.5 -log -@Soft transaction commit -@ -text -@d38 1 -a38 1 -\author "Rusty Russell,,," -a52 4 - -\change_deleted 0 1280141199 -10-May-2010 -\change_inserted 0 1280141202 -a53 2 -\change_unchanged - -a2028 2 - -\change_inserted 0 1280140902 -a2034 2 - -\change_unchanged -a2212 2 -\change_inserted 0 1280140661 - -a2215 2 - -\change_inserted 0 1280140703 -a2219 2 - -\change_inserted 0 1280708312 -a2226 2 - -\change_inserted 0 1280708400 -a2239 2 - -\change_inserted 0 1280140836 -a2243 2 - -\change_inserted 0 1280708255 -a2247 2 - -\change_inserted 0 1280708374 -a2252 2 - -\change_inserted 0 1280141181 -a2274 2 - -\change_inserted 0 1280141345 -@ - - -1.4 -log -@Merge changes -@ -text -@d38 1 -a38 1 -\author "" -d53 2 -d56 4 -d2035 10 -d2223 84 -@ - - -1.3 -log -@Transaction and freelist rethink. -@ -text -@d38 1 -a38 1 -\author "Rusty Russell,,," -d53 1 -a53 1 -27-April-2010 -d662 1 -a662 5 - behavior of disallowing -\change_inserted 0 1272940179 -nested -\change_unchanged -transactions should become the default. -a1210 2 -\change_inserted 0 1272944650 - -a1214 2 - -\change_inserted 0 1272944763 -a1218 2 -\change_unchanged - -a1223 2 -\change_unchanged - -a1301 2 - -\change_inserted 0 1273478114 -a1310 2 -\change_unchanged - -d1515 1 -a1515 11 -The free list -\change_deleted 0 1273469807 -should -\change_inserted 0 1273469810 -must -\change_unchanged - be split -\change_deleted 0 1273469815 -into multiple lists -\change_unchanged -to reduce contention. -a1520 2 -\change_inserted 0 1273470006 - -a1523 2 - -\change_inserted 0 1273492055 -a1539 2 - -\change_inserted 0 1273483888 -a1551 2 -\change_unchanged - -a1554 8 - -\change_deleted 0 1272942055 -There are various ways to organize these lisys, but because we want to be - able to quickly identify which free list an entry is in, and reduce the - number of locks required for merging, we will use zoning (eg. - each free list covers some fixed fraction of the file). - -\change_inserted 0 1273484187 -d1556 1 -a1556 7 - -\change_deleted 0 1273484194 -The algorithm for f -\change_inserted 0 1273484194 -F -\change_unchanged -reeing is simple: -d1560 1 -a1560 7 -Identify the correct -\change_deleted 0 1273482856 -free list -\change_inserted 0 1273482857 -zone -\change_unchanged -. -d1564 1 -a1564 7 -Lock the -\change_inserted 0 1273482895 -corresponding -\change_unchanged -list -\change_inserted 0 1273482863 -. -a1567 2 - -\change_inserted 0 1273482909 -d1573 1 -a1573 13 - -\change_deleted 0 1273482885 -, and p -\change_inserted 0 1273482888 -P -\change_unchanged -lace the freed entry -\change_deleted 0 1273492415 -at the head -\change_inserted 0 1273492415 -in the list for that zone -\change_unchanged -. -d1577 2 -a1578 7 -Allocation is a little more complicated, as we -\change_deleted 0 1273483240 -merge entries as we walk the list: -\change_inserted 0 1273484250 -perform delayed coalescing at this point: -\change_unchanged - -d1582 1 -a1582 19 -Pick a -\change_deleted 0 1273482955 -free list; -\change_inserted 0 1273482957 -zone -\change_unchanged - either the -\change_deleted 0 1273482962 -list -\change_inserted 0 1273482962 -zone -\change_unchanged - we last freed -\change_deleted 0 1273482966 -o -\change_inserted 0 1273482966 -i -\change_unchanged -nto, or based on a -d1594 1 -a1594 9 -Lock th -\change_inserted 0 1273482980 -e corresponding -\change_deleted 0 1273482973 -at -\change_unchanged - list. -\change_inserted 0 1273482982 - -a1597 2 - -\change_inserted 0 1273483084 -a1598 53 -\change_unchanged - -\end_layout - -\begin_layout Enumerate -If the top entry is -\change_deleted 0 1273492155 -well-sized, -\change_inserted 0 1273492159 --large enough, -\change_unchanged -remove it from the list and return it. -\end_layout - -\begin_layout Enumerate -Otherwise, -\change_inserted 0 1273492206 -coalesce entries in the list. -\change_deleted 0 1273492200 -examine the entry to the right of it in the file. - If it is free: -\end_layout - -\begin_deeper -\begin_layout Enumerate - -\change_deleted 0 1273492200 -If that entry is in a different list, lock that list too. -\end_layout - -\begin_layout Enumerate - -\change_deleted 0 1273492200 -If we had to place a new lock, re-check that the entry is free. -\end_layout - -\begin_layout Enumerate - -\change_deleted 0 1273492200 -Remove that entry from its free list and expand this entry to cover it. -\end_layout - -\begin_layout Enumerate - -\change_deleted 0 1273485554 -Goto step 3. -\end_layout - -\end_deeper -\begin_layout Enumerate - -\change_inserted 0 1273485311 -If there was no entry large enough, unlock the list and try the next zone. -d1602 1 -a1602 5 - -\change_deleted 0 1273483646 -Repeat step 3 with each entry in the list. -\change_unchanged - -d1606 2 -a1607 5 - -\change_deleted 0 1273483668 -Unlock the list and repeat step 2 with the next list. -\change_unchanged - -d1611 1 -a1611 7 -If no -\change_deleted 0 1273483671 -list -\change_inserted 0 1273483671 -zone -\change_unchanged - satisfies, expand the file. -d1615 2 -a1616 9 -This optimizes rapid insert/delete of free list entries -\change_inserted 0 1273485794 - by not coalescing them all the time. -\change_deleted 0 1273483685 -, and allows us to get rid of the tailer altogether -\change_unchanged -. - -\change_inserted 0 1273492299 -a1638 39 - -\change_deleted 0 1273476840 -The question of -\begin_inset Quotes eld -\end_inset - -well-sized -\begin_inset Quotes erd -\end_inset - - free entries is more difficult: the 25% overhead works in practice for - ldb because indexes tend to expand by one record at a time. - This can be resolved by having an -\begin_inset Quotes eld -\end_inset - -expanded -\begin_inset Quotes erd -\end_inset - - bit in the header to note entries that have previously expanded, and allocating - more space for them. - Whether the -\begin_inset Quotes eld -\end_inset - -increasing slack -\begin_inset Quotes erd -\end_inset - - algorithm should be implemented or first-fit used is still unknown: we - will determine this once these other ideas are implemented. -\change_inserted 0 1273483750 - -\end_layout - -\begin_layout Standard - -\change_inserted 0 1273492450 -a1644 2 - -\change_inserted 0 1273470441 -a1654 2 - -\change_inserted 0 1273476556 -a1659 2 - -\change_inserted 0 1273470423 -a1661 2 -\change_unchanged - -a1672 2 - -\change_inserted 0 1273476847 -a1676 2 - -\change_inserted 0 1273476886 -a1691 2 - -\change_inserted 0 1273477233 -a1699 2 - -\change_inserted 0 1273477534 -a1706 2 - -\change_inserted 0 1273482700 -a1712 2 - -\change_inserted 0 1273478079 -a1722 2 - -\change_inserted 0 1273477839 -a1726 2 - -\change_inserted 0 1273477925 -a1730 2 - -\change_inserted 0 1273477925 -a1734 2 - -\change_inserted 0 1273477925 -a1738 2 - -\change_inserted 0 1273477925 -a1742 2 - -\change_inserted 0 1273477925 -a1746 2 - -\change_inserted 0 1273477925 -a1750 2 - -\change_inserted 0 1273477925 -a1754 2 - -\change_inserted 0 1273477925 -a1758 2 - -\change_inserted 0 1273477925 -a1762 2 - -\change_inserted 0 1273477925 -a1766 2 - -\change_inserted 0 1273477925 -a1770 2 - -\change_inserted 0 1273477925 -a1774 2 - -\change_inserted 0 1273477925 -a1778 2 - -\change_inserted 0 1273477925 -a1782 2 - -\change_inserted 0 1273477925 -a1786 2 - -\change_inserted 0 1273477925 -a1790 2 - -\change_inserted 0 1273477925 -a1794 2 - -\change_inserted 0 1273477925 -a1798 2 - -\change_inserted 0 1273492522 -a1802 2 - -\change_inserted 0 1273492530 -a1806 2 - -\change_inserted 0 1273492546 -a1810 2 - -\change_inserted 0 1273478239 -a1814 2 - -\change_inserted 0 1273479960 -a1821 2 - -\change_inserted 0 1273480265 -a1830 2 - -\change_inserted 0 1273480354 -a1845 2 - -\change_inserted 0 1273478968 -a1851 2 - -\change_inserted 0 1273492604 -a1859 2 - -\change_inserted 0 1273479572 -a1862 2 -\change_unchanged - -a1870 2 - -\change_inserted 0 1273480282 -a1874 2 - -\change_inserted 0 1273478931 -a1878 2 - -\change_inserted 0 1273481549 -a1882 2 - -\change_inserted 0 1273481557 -a1886 2 - -\change_inserted 0 1273480307 -a1890 2 - -\change_inserted 0 1273480335 -a1894 2 - -\change_inserted 0 1273479897 -a1898 2 - -\change_inserted 0 1273479653 -a1902 2 - -\change_inserted 0 1273480371 -a1906 2 - -\change_inserted 0 1273480464 -a1910 2 - -\change_inserted 0 1273480399 -a1914 2 - -\change_inserted 0 1273480425 -a1918 2 - -\change_inserted 0 1273480453 -a1922 2 - -\change_inserted 0 1273480455 -a1926 2 - -\change_inserted 0 1273480450 -a1930 2 - -\change_inserted 0 1273480452 -a1935 2 -\change_inserted 0 1273478830 - -a1942 5 - -\change_deleted 0 1273481604 -In theory, we could get away with 2: one after we write the new data, and - one to somehow atomically change over to it. -\change_inserted 0 1273481632 -a1946 2 - -\change_inserted 0 1273481724 -a1950 2 - -\change_inserted 0 1273481713 -a1954 2 - -\change_inserted 0 1273481717 -a1958 2 - -\change_inserted 0 1273481730 -a1962 2 - -\change_inserted 0 1273481736 -a1966 2 - -\change_inserted 0 1273481744 -a1970 2 - -\change_inserted 0 1273481748 -a1974 2 - -\change_inserted 0 1273482185 -a1978 2 - -\change_inserted 0 1273482259 -a1989 50 - -\change_deleted 0 1273481848 -None. - Trying to rewrite the transaction code is a separate experiment, which - I encourage someone else to do. - At some point you say -\begin_inset Quotes eld -\end_inset - -use a real database -\begin_inset Quotes erd -\end_inset - -. -\end_layout - -\begin_layout Standard - -\change_deleted 0 1273481848 -But as a thought experiment: -\change_unchanged - -\end_layout - -\begin_layout Standard - -\change_deleted 0 1273481788 -Say there was a pointer in the header which said where the hash table and - free list tables were, and that no blocks were labeled with whether they - were free or not (it had to be derived from what list they were in). - We could create new hash table and free list in some free space, and populate - it as we want the post-committed state to look. - Then we sync, then we switch the offset in the header, then we sync again. -\end_layout - -\begin_layout Standard - -\change_deleted 0 1273481788 -This would not allow arbitrary changes to the database, such as tdb_repack - does, and would require more space (since we have to preserve the current - and future entries at once). - If we used hash trees rather than one big hash table, we might only have - to rewrite some sections of the hash, too. -\change_inserted 0 1273481854 - -\end_layout - -\begin_layout Standard - -\change_inserted 0 1273482102 -a1993 2 - -\change_inserted 0 1273482061 -a1998 2 - -\change_inserted 0 1273482063 -a2002 2 - -\change_inserted 0 1273482072 -a2006 2 - -\change_inserted 0 1273482139 -a2011 2 - -\change_inserted 0 1273482364 -a2015 2 - -\change_inserted 0 1273482163 -a2019 2 - -\change_inserted 0 1273482493 -a2037 2 - -\change_inserted 0 1273482536 -a2046 2 -\change_unchanged - -a2049 2 - -\change_inserted 0 1273482641 -a2058 2 - -\change_inserted 0 1273481827 -d2067 2 -a2068 11 -We could -\change_inserted 0 1273481829 -then -\change_unchanged -implement snapshots using a similar method -\change_deleted 0 1273481838 - to the above, only -\change_inserted 0 1273481840 -, -\change_unchanged - using multiple different hash tables/free tables. -@ - - -1.2 -log -@After first feedback (Ronnie & Volker) -@ -text -@d1314 13 -d1531 11 -a1541 1 -The free list should be split into multiple lists to reduce contention. -d1547 39 -d1596 7 -d1604 1 -a1604 1 -The algorithm for freeing is simple: -d1608 7 -a1614 1 -Identify the correct free list. -d1618 30 -a1647 1 -Lock the list, and place the freed entry at the head. -d1651 7 -a1657 2 -Allocation is a little more complicated, as we merge entries as we walk - the list: -d1661 19 -a1679 1 -Pick a free list; either the list we last freed onto, or based on a -d1691 17 -a1707 1 -Lock that list. -d1711 7 -a1717 1 -If the top entry is well-sized, remove it from the list and return it. -d1721 5 -a1725 1 -Otherwise, examine the entry to the right of it in the file. -d1731 2 -d1737 2 -d1743 2 -d1749 2 -d1756 8 -d1765 2 -d1770 2 -d1773 2 -d1778 7 -a1784 1 -If no list satisfies, expand the file. -d1788 28 -a1815 2 -This optimizes rapid insert/delete of free list entries, and allows us to - get rid of the tailer altogether. -d1819 2 -d1851 1 -a1851 1 -\change_inserted 0 1272941474 -d1857 303 -a2159 18 -\change_inserted 0 1272942759 -There are various ways to organize these lists, but because we want to be - able to quickly identify which free list an entry is in, and reduce the - number of locks required for merging, we will use zoning (eg. - each of the N free lists in a tdb file of size M covers a fixed fraction - M/N). - Note that this means we need to reshuffle the free lists when we expand - the file; this is probably acceptable when we double the hash table size, - since that is such an expensive operation already. - In the case of increasing the file size, there is an optimization we can - use: if we use M in the formula above as the file size rounded up to the - next power of 2, we only need reshuffle free lists when the file size crosses - a power of 2 boundary, -\emph on -and -\emph default -reshuffling the free lists is trivial: we simply merge every consecutive - pair of free lists. -d2164 107 -d2276 2 -d2280 59 -d2346 2 -d2363 2 -d2366 2 -d2371 2 -d2382 2 -d2389 57 -d2458 13 -d2474 32 -a2505 2 -We could implement snapshots using a similar method to the above, only using - multiple different hash tables/free tables. -@ - - -1.1 -log -@Initial revision -@ -text -@d1 1 -a1 1 -#LyX 1.6.4 created this file. For more info see http://www.lyx.org/ -d36 3 -a38 3 -\tracking_changes false -\output_changes false -\author "" -d662 5 -a666 1 - behavior of disallowing transactions should become the default. -d1215 21 -d1527 2 -d1533 3 -a1535 1 - The algorithm for freeing is simple: -d1642 26 -@ diff --git a/ccan/tdb2/doc/design.pdf b/ccan/tdb2/doc/design.pdf deleted file mode 100644 index 558dc1f8c2e273ea760ea97c663a3c145f8d937d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240440 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 NL8@mvua{F%xxc=W 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�o;QtuPtO!;IiH< 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 - -tdb_open() will take a linked-list of attributes: - -enum tdb_attribute { - - TDB_ATTRIBUTE_LOG = 0, - - TDB_ATTRIBUTE_HASH = 1 - -}; - -struct tdb_attribute_base { - - enum tdb_attribute attr; - - union tdb_attribute *next; - -}; - -struct tdb_attribute_log { - - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_LOG -*/ - - tdb_log_func log_fn; - - void *log_private; - -}; - -struct tdb_attribute_hash { - - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_HASH -*/ - - tdb_hash_func hash_fn; - - void *hash_private; - -}; - -union tdb_attribute { - - struct tdb_attribute_base base; - - struct tdb_attribute_log log; - - struct tdb_attribute_hash hash; - -}; - -This allows future attributes to be added, even if this expands -the size of the union. - -2.1.2 Status - -Complete. - -2.2 tdb_traverse Makes Impossible Guarantees - -tdb_traverse (and tdb_firstkey/tdb_nextkey) predate transactions, -and it was thought that it was important to guarantee that all -records which exist at the start and end of the traversal would -be included, and no record would be included twice. - -This adds complexity (see[Reliable-Traversal-Adds]) and does not -work anyway for records which are altered (in particular, those -which are expanded may be effectively deleted and re-added behind -the traversal). - -2.2.1 Proposed Solution - -Abandon the guarantee. You will see every record if no changes -occur during your traversal, otherwise you will see some subset. -You can prevent changes by using a transaction or the locking -API. - -2.2.2 Status - -Complete. Delete-during-traverse will still delete every record, -too (assuming no other changes). - -2.3 Nesting of Transactions Is Fraught - -TDB has alternated between allowing nested transactions and not -allowing them. Various paths in the Samba codebase assume that -transactions will nest, and in a sense they can: the operation is -only committed to disk when the outer transaction is committed. -There are two problems, however: - -1. Canceling the inner transaction will cause the outer - transaction commit to fail, and will not undo any operations - since the inner transaction began. This problem is soluble with - some additional internal code. - -2. An inner transaction commit can be cancelled by the outer - transaction. This is desirable in the way which Samba's - database initialization code uses transactions, but could be a - surprise to any users expecting a successful transaction commit - to expose changes to others. - -The current solution is to specify the behavior at tdb_open(), -with the default currently that nested transactions are allowed. -This flag can also be changed at runtime. - -2.3.1 Proposed Solution - -Given the usage patterns, it seems that the “least-surprise” -behavior of disallowing nested transactions should become the -default. Additionally, it seems the outer transaction is the only -code which knows whether inner transactions should be allowed, so -a flag to indicate this could be added to tdb_transaction_start. -However, this behavior can be simulated with a wrapper which uses -tdb_add_flags() and tdb_remove_flags(), so the API should not be -expanded for this relatively-obscure case. - -2.3.2 Status - -Incomplete; nesting flag is still defined as per tdb1. - -2.4 Incorrect Hash Function is Not Detected - -tdb_open_ex() allows the calling code to specify a different hash -function to use, but does not check that all other processes -accessing this tdb are using the same hash function. The result -is that records are missing from tdb_fetch(). - -2.4.1 Proposed Solution - -The header should contain an example hash result (eg. the hash of -0xdeadbeef), and tdb_open_ex() should check that the given hash -function produces the same answer, or fail the tdb_open call. - -2.4.2 Status - -Complete. - -2.5 tdb_set_max_dead/TDB_VOLATILE Expose Implementation - -In response to scalability issues with the free list ([TDB-Freelist-Is] -) two API workarounds have been incorporated in TDB: -tdb_set_max_dead() and the TDB_VOLATILE flag to tdb_open. The -latter actually calls the former with an argument of “5”. - -This code allows deleted records to accumulate without putting -them in the free list. On delete we iterate through each chain -and free them in a batch if there are more than max_dead entries. -These are never otherwise recycled except as a side-effect of a -tdb_repack. - -2.5.1 Proposed Solution - -With the scalability problems of the freelist solved, this API -can be removed. The TDB_VOLATILE flag may still be useful as a -hint that store and delete of records will be at least as common -as fetch in order to allow some internal tuning, but initially -will become a no-op. - -2.5.2 Status - -Incomplete. TDB_VOLATILE still defined, but implementation should -fail on unknown flags to be future-proof. - -2.6 TDB Files Cannot Be Opened Multiple Times - In The Same Process - -No process can open the same TDB twice; we check and disallow it. -This is an unfortunate side-effect of fcntl locks, which operate -on a per-file rather than per-file-descriptor basis, and do not -nest. Thus, closing any file descriptor on a file clears all the -locks obtained by this process, even if they were placed using a -different file descriptor! - -Note that even if this were solved, deadlock could occur if -operations were nested: this is a more manageable programming -error in most cases. - -2.6.1 Proposed Solution - -We could lobby POSIX to fix the perverse rules, or at least lobby -Linux to violate them so that the most common implementation does -not have this restriction. This would be a generally good idea -for other fcntl lock users. - -Samba uses a wrapper which hands out the same tdb_context to -multiple callers if this happens, and does simple reference -counting. We should do this inside the tdb library, which already -emulates lock nesting internally; it would need to recognize when -deadlock occurs within a single process. This would create a new -failure mode for tdb operations (while we currently handle -locking failures, they are impossible in normal use and a process -encountering them can do little but give up). - -I do not see benefit in an additional tdb_open flag to indicate -whether re-opening is allowed, as though there may be some -benefit to adding a call to detect when a tdb_context is shared, -to allow other to create such an API. - -2.6.2 Status - -Incomplete. - -2.7 TDB API Is Not POSIX Thread-safe - -The TDB API uses an error code which can be queried after an -operation to determine what went wrong. This programming model -does not work with threads, unless specific additional guarantees -are given by the implementation. In addition, even -otherwise-independent threads cannot open the same TDB (as in [TDB-Files-Cannot] -). - -2.7.1 Proposed Solution - -Reachitecting the API to include a tdb_errcode pointer would be a -great deal of churn; we are better to guarantee that the -tdb_errcode is per-thread so the current programming model can be -maintained. - -This requires dynamic per-thread allocations, which is awkward -with POSIX threads (pthread_key_create space is limited and we -cannot simply allocate a key for every TDB). - -Internal locking is required to make sure that fcntl locks do not -overlap between threads, and also that the global list of tdbs is -maintained. - -The aim is that building tdb with -DTDB_PTHREAD will result in a -pthread-safe version of the library, and otherwise no overhead -will exist. Alternatively, a hooking mechanism similar to that -proposed for [Proposed-Solution-locking-hook] could be used to -enable pthread locking at runtime. - -2.7.2 Status - -Incomplete. - -2.8 *_nonblock Functions And *_mark Functions Expose - Implementation - -CTDB[footnote: -Clustered TDB, see http://ctdb.samba.org -] wishes to operate on TDB in a non-blocking manner. This is -currently done as follows: - -1. Call the _nonblock variant of an API function (eg. - tdb_lockall_nonblock). If this fails: - -2. Fork a child process, and wait for it to call the normal - variant (eg. tdb_lockall). - -3. If the child succeeds, call the _mark variant to indicate we - already have the locks (eg. tdb_lockall_mark). - -4. Upon completion, tell the child to release the locks (eg. - tdb_unlockall). - -5. Indicate to tdb that it should consider the locks removed (eg. - tdb_unlockall_mark). - -There are several issues with this approach. Firstly, adding two -new variants of each function clutters the API for an obscure -use, and so not all functions have three variants. Secondly, it -assumes that all paths of the functions ask for the same locks, -otherwise the parent process will have to get a lock which the -child doesn't have under some circumstances. I don't believe this -is currently the case, but it constrains the implementation. - -2.8.1 Proposed Solution - -Implement a hook for locking methods, so that the caller can -control the calls to create and remove fcntl locks. In this -scenario, ctdbd would operate as follows: - -1. Call the normal API function, eg tdb_lockall(). - -2. When the lock callback comes in, check if the child has the - lock. Initially, this is always false. If so, return 0. - Otherwise, try to obtain it in non-blocking mode. If that - fails, return EWOULDBLOCK. - -3. Release locks in the unlock callback as normal. - -4. If tdb_lockall() fails, see if we recorded a lock failure; if - so, call the child to repeat the operation. - -5. The child records what locks it obtains, and returns that - information to the parent. - -6. When the child has succeeded, goto 1. - -This is flexible enough to handle any potential locking scenario, -even when lock requirements change. It can be optimized so that -the parent does not release locks, just tells the child which -locks it doesn't need to obtain. - -It also keeps the complexity out of the API, and in ctdbd where -it is needed. - -2.8.2 Status - -Incomplete. - -2.9 tdb_chainlock Functions Expose Implementation - -tdb_chainlock locks some number of records, including the record -indicated by the given key. This gave atomicity guarantees; -no-one can start a transaction, alter, read or delete that key -while the lock is held. - -It also makes the same guarantee for any other key in the chain, -which is an internal implementation detail and potentially a -cause for deadlock. - -2.9.1 Proposed Solution - -None. It would be nice to have an explicit single entry lock -which effected no other keys. Unfortunately, this won't work for -an entry which doesn't exist. Thus while chainlock may be -implemented more efficiently for the existing case, it will still -have overlap issues with the non-existing case. So it is best to -keep the current (lack of) guarantee about which records will be -effected to avoid constraining our implementation. - -2.10 Signal Handling is Not Race-Free - -The tdb_setalarm_sigptr() call allows the caller's signal handler -to indicate that the tdb locking code should return with a -failure, rather than trying again when a signal is received (and -errno == EAGAIN). This is usually used to implement timeouts. - -Unfortunately, this does not work in the case where the signal is -received before the tdb code enters the fcntl() call to place the -lock: the code will sleep within the fcntl() code, unaware that -the signal wants it to exit. In the case of long timeouts, this -does not happen in practice. - -2.10.1 Proposed Solution - -The locking hooks proposed in[Proposed-Solution-locking-hook] -would allow the user to decide on whether to fail the lock -acquisition on a signal. This allows the caller to choose their -own compromise: they could narrow the race by checking -immediately before the fcntl call.[footnote: -It may be possible to make this race-free in some implementations -by having the signal handler alter the struct flock to make it -invalid. This will cause the fcntl() lock call to fail with -EINVAL if the signal occurs before the kernel is entered, -otherwise EAGAIN. -] - -2.10.2 Status - -Incomplete. - -2.11 The API Uses Gratuitous Typedefs, Capitals - -typedefs are useful for providing source compatibility when types -can differ across implementations, or arguably in the case of -function pointer definitions which are hard for humans to parse. -Otherwise it is simply obfuscation and pollutes the namespace. - -Capitalization is usually reserved for compile-time constants and -macros. - - TDB_CONTEXT There is no reason to use this over 'struct - tdb_context'; the definition isn't visible to the API user - anyway. - - TDB_DATA There is no reason to use this over struct TDB_DATA; - the struct needs to be understood by the API user. - - struct TDB_DATA This would normally be called 'struct - tdb_data'. - - enum TDB_ERROR Similarly, this would normally be enum - tdb_error. - -2.11.1 Proposed Solution - -None. Introducing lower case variants would please pedants like -myself, but if it were done the existing ones should be kept. -There is little point forcing a purely cosmetic change upon tdb -users. - -2.12 tdb_log_func Doesn't Take The - Private Pointer - -For API compatibility reasons, the logging function needs to call -tdb_get_logging_private() to retrieve the pointer registered by -the tdb_open_ex for logging. - -2.12.1 Proposed Solution - -It should simply take an extra argument, since we are prepared to -break the API/ABI. - -2.12.2 Status - -Complete. - -2.13 Various Callback Functions Are Not Typesafe - -The callback functions in tdb_set_logging_function (after [tdb_log_func-Doesnt-Take] - is resolved), tdb_parse_record, tdb_traverse, tdb_traverse_read -and tdb_check all take void * and must internally convert it to -the argument type they were expecting. - -If this type changes, the compiler will not produce warnings on -the callers, since it only sees void *. - -2.13.1 Proposed Solution - -With careful use of macros, we can create callback functions -which give a warning when used on gcc and the types of the -callback and its private argument differ. Unsupported compilers -will not give a warning, which is no worse than now. In addition, -the callbacks become clearer, as they need not use void * for -their parameter. - -See CCAN's typesafe_cb module at -http://ccan.ozlabs.org/info/typesafe_cb.html - -2.13.2 Status - -Incomplete. - -2.14 TDB_CLEAR_IF_FIRST Must Be Specified On All Opens, - tdb_reopen_all Problematic - -The TDB_CLEAR_IF_FIRST flag to tdb_open indicates that the TDB -file should be cleared if the caller discovers it is the only -process with the TDB open. However, if any caller does not -specify TDB_CLEAR_IF_FIRST it will not be detected, so will have -the TDB erased underneath them (usually resulting in a crash). - -There is a similar issue on fork(); if the parent exits (or -otherwise closes the tdb) before the child calls tdb_reopen_all() -to establish the lock used to indicate the TDB is opened by -someone, a TDB_CLEAR_IF_FIRST opener at that moment will believe -it alone has opened the TDB and will erase it. - -2.14.1 Proposed Solution - -Remove TDB_CLEAR_IF_FIRST. Other workarounds are possible, but -see [TDB_CLEAR_IF_FIRST-Imposes-Performance]. - -2.14.2 Status - -Incomplete, TDB_CLEAR_IF_FIRST still defined, but not -implemented. - -2.15 Extending The Header Is Difficult - -We have reserved (zeroed) words in the TDB header, which can be -used for future features. If the future features are compulsory, -the version number must be updated to prevent old code from -accessing the database. But if the future feature is optional, we -have no way of telling if older code is accessing the database or -not. - -2.15.1 Proposed Solution - -The header should contain a “format variant” value (64-bit). This -is divided into two 32-bit parts: - -1. The lower part reflects the format variant understood by code - accessing the database. - -2. The upper part reflects the format variant you must understand - to write to the database (otherwise you can only open for - reading). - -The latter field can only be written at creation time, the former -should be written under the OPEN_LOCK when opening the database -for writing, if the variant of the code is lower than the current -lowest variant. - -This should allow backwards-compatible features to be added, and -detection if older code (which doesn't understand the feature) -writes to the database. - -2.15.2 Status - -Incomplete. - -2.16 Record Headers Are Not Expandible - -If we later want to add (say) checksums on keys and data, it -would require another format change, which we'd like to avoid. - -2.16.1 Proposed Solution - -We often have extra padding at the tail of a record. If we ensure -that the first byte (if any) of this padding is zero, we will -have a way for future changes to detect code which doesn't -understand a new format: the new code would write (say) a 1 at -the tail, and thus if there is no tail or the first byte is 0, we -would know the extension is not present on that record. - -2.16.2 Status - -Incomplete. - -2.17 TDB Does Not Use Talloc - -Many users of TDB (particularly Samba) use the talloc allocator, -and thus have to wrap TDB in a talloc context to use it -conveniently. - -2.17.1 Proposed Solution - -The allocation within TDB is not complicated enough to justify -the use of talloc, and I am reluctant to force another -(excellent) library on TDB users. Nonetheless a compromise is -possible. An attribute (see [attributes]) can be added later to -tdb_open() to provide an alternate allocation mechanism, -specifically for talloc but usable by any other allocator (which -would ignore the “context” argument). - -This would form a talloc heirarchy as expected, but the caller -would still have to attach a destructor to the tdb context -returned from tdb_open to close it. All TDB_DATA fields would be -children of the tdb_context, and the caller would still have to -manage them (using talloc_free() or talloc_steal()). - -2.17.2 Status - -Deferred. - -3 Performance And Scalability Issues - -3.1 TDB_CLEAR_IF_FIRST - Imposes Performance Penalty - -When TDB_CLEAR_IF_FIRST is specified, a 1-byte read lock is -placed at offset 4 (aka. the ACTIVE_LOCK). While these locks -never conflict in normal tdb usage, they do add substantial -overhead for most fcntl lock implementations when the kernel -scans to detect if a lock conflict exists. This is often a single -linked list, making the time to acquire and release a fcntl lock -O(N) where N is the number of processes with the TDB open, not -the number actually doing work. - -In a Samba server it is common to have huge numbers of clients -sitting idle, and thus they have weaned themselves off the -TDB_CLEAR_IF_FIRST flag.[footnote: -There is a flag to tdb_reopen_all() which is used for this -optimization: if the parent process will outlive the child, the -child does not need the ACTIVE_LOCK. This is a workaround for -this very performance issue. -] - -3.1.1 Proposed Solution - -Remove the flag. It was a neat idea, but even trivial servers -tend to know when they are initializing for the first time and -can simply unlink the old tdb at that point. - -3.1.2 Status - -Incomplete; TDB_CLEAR_IF_FIRST still defined, but does nothing. - -3.2 TDB Files Have a 4G Limit - -This seems to be becoming an issue (so much for “trivial”!), -particularly for ldb. - -3.2.1 Proposed Solution - -A new, incompatible TDB format which uses 64 bit offsets -internally rather than 32 bit as now. For simplicity of endian -conversion (which TDB does on the fly if required), all values -will be 64 bit on disk. In practice, some upper bits may be used -for other purposes, but at least 56 bits will be available for -file offsets. - -tdb_open() will automatically detect the old version, and even -create them if TDB_VERSION6 is specified to tdb_open. - -32 bit processes will still be able to access TDBs larger than 4G -(assuming that their off_t allows them to seek to 64 bits), they -will gracefully fall back as they fail to mmap. This can happen -already with large TDBs. - -Old versions of tdb will fail to open the new TDB files (since 28 -August 2009, commit 398d0c29290: prior to that any unrecognized -file format would be erased and initialized as a fresh tdb!) - -3.2.2 Status - -Complete. - -3.3 TDB Records Have a 4G Limit - -This has not been a reported problem, and the API uses size_t -which can be 64 bit on 64 bit platforms. However, other limits -may have made such an issue moot. - -3.3.1 Proposed Solution - -Record sizes will be 64 bit, with an error returned on 32 bit -platforms which try to access such records (the current -implementation would return TDB_ERR_OOM in a similar case). It -seems unlikely that 32 bit keys will be a limitation, so the -implementation may not support this (see [sub:Records-Incur-A]). - -3.3.2 Status - -Complete. - -3.4 Hash Size Is Determined At TDB Creation Time - -TDB contains a number of hash chains in the header; the number is -specified at creation time, and defaults to 131. This is such a -bottleneck on large databases (as each hash chain gets quite -long), that LDB uses 10,000 for this hash. In general it is -impossible to know what the 'right' answer is at database -creation time. - -3.4.1 Proposed Solution - -After comprehensive performance testing on various scalable hash -variants[footnote: -http://rusty.ozlabs.org/?p=89 and http://rusty.ozlabs.org/?p=94 -This was annoying because I was previously convinced that an -expanding tree of hashes would be very close to optimal. -], it became clear that it is hard to beat a straight linear hash -table which doubles in size when it reaches saturation. -Unfortunately, altering the hash table introduces serious locking -complications: the entire hash table needs to be locked to -enlarge the hash table, and others might be holding locks. -Particularly insidious are insertions done under tdb_chainlock. - -Thus an expanding layered hash will be used: an array of hash -groups, with each hash group exploding into pointers to lower -hash groups once it fills, turning into a hash tree. This has -implications for locking: we must lock the entire group in case -we need to expand it, yet we don't know how deep the tree is at -that point. - -Note that bits from the hash table entries should be stolen to -hold more hash bits to reduce the penalty of collisions. We can -use the otherwise-unused lower 3 bits. If we limit the size of -the database to 64 exabytes, we can use the top 8 bits of the -hash entry as well. These 11 bits would reduce false positives -down to 1 in 2000 which is more than we need: we can use one of -the bits to indicate that the extra hash bits are valid. This -means we can choose not to re-hash all entries when we expand a -hash group; simply use the next bits we need and mark them -invalid. - -3.4.2 Status - -Complete. - -3.5 TDB Freelist Is Highly Contended - -TDB uses a single linked list for the free list. Allocation -occurs as follows, using heuristics which have evolved over time: - -1. Get the free list lock for this whole operation. - -2. Multiply length by 1.25, so we always over-allocate by 25%. - -3. Set the slack multiplier to 1. - -4. Examine the current freelist entry: if it is > length but < - the current best case, remember it as the best case. - -5. Multiply the slack multiplier by 1.05. - -6. If our best fit so far is less than length * slack multiplier, - return it. The slack will be turned into a new free record if - it's large enough. - -7. Otherwise, go onto the next freelist entry. - -Deleting a record occurs as follows: - -1. Lock the hash chain for this whole operation. - -2. Walk the chain to find the record, keeping the prev pointer - offset. - -3. If max_dead is non-zero: - - (a) Walk the hash chain again and count the dead records. - - (b) If it's more than max_dead, bulk free all the dead ones - (similar to steps 4 and below, but the lock is only obtained - once). - - (c) Simply mark this record as dead and return. - -4. Get the free list lock for the remainder of this operation. - -5. Examine the following block to see if it is - free; if so, enlarge the current block and remove that block - from the free list. This was disabled, as removal from the free - list was O(entries-in-free-list). - -6. Examine the preceeding block to see if it is free: for this - reason, each block has a 32-bit tailer which indicates its - length. If it is free, expand it to cover our new block and - return. - -7. Otherwise, prepend ourselves to the free list. - -Disabling right-merging (step [right-merging]) causes -fragmentation; the other heuristics proved insufficient to -address this, so the final answer to this was that when we expand -the TDB file inside a transaction commit, we repack the entire -tdb. - -The single list lock limits our allocation rate; due to the other -issues this is not currently seen as a bottleneck. - -3.5.1 Proposed Solution - -The first step is to remove all the current heuristics, as they -obviously interact, then examine them once the lock contention is -addressed. - -The free list must be split to reduce contention. Assuming -perfect free merging, we can at most have 1 free list entry for -each entry. This implies that the number of free lists is related -to the size of the hash table, but as it is rare to walk a large -number of free list entries we can use far fewer, say 1/32 of the -number of hash buckets. - -It seems tempting to try to reuse the hash implementation which -we use for records here, but we have two ways of searching for -free entries: for allocation we search by size (and possibly -zone) which produces too many clashes for our hash table to -handle well, and for coalescing we search by address. Thus an -array of doubly-linked free lists seems preferable. - -There are various benefits in using per-size free lists (see [sub:TDB-Becomes-Fragmented] -) but it's not clear this would reduce contention in the common -case where all processes are allocating/freeing the same size. -Thus we almost certainly need to divide in other ways: the most -obvious is to divide the file into zones, and using a free list -(or table of free lists) for each. This approximates address -ordering. - -Unfortunately it is difficult to know what heuristics should be -used to determine zone sizes, and our transaction code relies on -being able to create a “recovery area” by simply appending to the -file (difficult if it would need to create a new zone header). -Thus we use a linked-list of free tables; currently we only ever -create one, but if there is more than one we choose one at random -to use. In future we may use heuristics to add new free tables on -contention. We only expand the file when all free tables are -exhausted. - -The basic algorithm is as follows. Freeing is simple: - -1. Identify the correct free list. - -2. Lock the corresponding list. - -3. Re-check the list (we didn't have a lock, sizes could have - changed): relock if necessary. - -4. Place the freed entry in the list. - -Allocation is a little more complicated, as we perform delayed -coalescing at this point: - -1. Pick a free table; usually the previous one. - -2. Lock the corresponding list. - -3. If the top entry is -large enough, remove it from the list and - return it. - -4. Otherwise, coalesce entries in the list.If there was no entry - large enough, unlock the list and try the next largest list - -5. If no list has an entry which meets our needs, try the next - free table. - -6. If no zone satisfies, expand the file. - -This optimizes rapid insert/delete of free list entries by not -coalescing them all the time.. First-fit address ordering -ordering seems to be fairly good for keeping fragmentation low -(see [sub:TDB-Becomes-Fragmented]). Note that address ordering -does not need a tailer to coalesce, though if we needed one we -could have one cheaply: see [sub:Records-Incur-A]. - -Each free entry has the free table number in the header: less -than 255. It also contains a doubly-linked list for easy -deletion. - -3.6 TDB Becomes Fragmented - -Much of this is a result of allocation strategy[footnote: -The Memory Fragmentation Problem: Solved? Johnstone & Wilson 1995 -ftp://ftp.cs.utexas.edu/pub/garbage/malloc/ismm98.ps -] and deliberate hobbling of coalescing; internal fragmentation -(aka overallocation) is deliberately set at 25%, and external -fragmentation is only cured by the decision to repack the entire -db when a transaction commit needs to enlarge the file. - -3.6.1 Proposed Solution - -The 25% overhead on allocation works in practice for ldb because -indexes tend to expand by one record at a time. This internal -fragmentation can be resolved by having an “expanded” bit in the -header to note entries that have previously expanded, and -allocating more space for them. - -There are is a spectrum of possible solutions for external -fragmentation: one is to use a fragmentation-avoiding allocation -strategy such as best-fit address-order allocator. The other end -of the spectrum would be to use a bump allocator (very fast and -simple) and simply repack the file when we reach the end. - -There are three problems with efficient fragmentation-avoiding -allocators: they are non-trivial, they tend to use a single free -list for each size, and there's no evidence that tdb allocation -patterns will match those recorded for general allocators (though -it seems likely). - -Thus we don't spend too much effort on external fragmentation; we -will be no worse than the current code if we need to repack on -occasion. More effort is spent on reducing freelist contention, -and reducing overhead. - -3.7 Records Incur A 28-Byte Overhead - -Each TDB record has a header as follows: - -struct tdb_record { - - tdb_off_t next; /* offset of the next record in the list -*/ - - tdb_len_t rec_len; /* total byte length of record */ - - tdb_len_t key_len; /* byte length of key */ - - tdb_len_t data_len; /* byte length of data */ - - uint32_t full_hash; /* the full 32 bit hash of the key */ - - uint32_t magic; /* try to catch errors */ - - /* the following union is implied: - - union { - - char record[rec_len]; - - struct { - - char key[key_len]; - - char data[data_len]; - - } - - uint32_t totalsize; (tailer) - - } - - */ - -}; - -Naively, this would double to a 56-byte overhead on a 64 bit -implementation. - -3.7.1 Proposed Solution - -We can use various techniques to reduce this for an allocated -block: - -1. The 'next' pointer is not required, as we are using a flat - hash table. - -2. 'rec_len' can instead be expressed as an addition to key_len - and data_len (it accounts for wasted or overallocated length in - the record). Since the record length is always a multiple of 8, - we can conveniently fit it in 32 bits (representing up to 35 - bits). - -3. 'key_len' and 'data_len' can be reduced. I'm unwilling to - restrict 'data_len' to 32 bits, but instead we can combine the - two into one 64-bit field and using a 5 bit value which - indicates at what bit to divide the two. Keys are unlikely to - scale as fast as data, so I'm assuming a maximum key size of 32 - bits. - -4. 'full_hash' is used to avoid a memcmp on the “miss” case, but - this is diminishing returns after a handful of bits (at 10 - bits, it reduces 99.9% of false memcmp). As an aside, as the - lower bits are already incorporated in the hash table - resolution, the upper bits should be used here. Note that it's - not clear that these bits will be a win, given the extra bits - in the hash table itself (see [sub:Hash-Size-Solution]). - -5. 'magic' does not need to be enlarged: it currently reflects - one of 5 values (used, free, dead, recovery, and - unused_recovery). It is useful for quick sanity checking - however, and should not be eliminated. - -6. 'tailer' is only used to coalesce free blocks (so a block to - the right can find the header to check if this block is free). - This can be replaced by a single 'free' bit in the header of - the following block (and the tailer only exists in free - blocks).[footnote: -This technique from Thomas Standish. Data Structure Techniques. -Addison-Wesley, Reading, Massachusetts, 1980. -] The current proposed coalescing algorithm doesn't need this, - however. - -This produces a 16 byte used header like this: - -struct tdb_used_record { - - uint32_t used_magic : 16, - - - - key_data_divide: 5, - - top_hash: 11; - - uint32_t extra_octets; - - uint64_t key_and_data_len; - -}; - -And a free record like this: - -struct tdb_free_record { - - uint64_t free_magic: 8, - - prev : 56; - - - - uint64_t free_table: 8, - - total_length : 56 - - uint64_t next;; - -}; - -Note that by limiting valid offsets to 56 bits, we can pack -everything we need into 3 64-byte words, meaning our minimum -record size is 8 bytes. - -3.7.2 Status - -Complete. - -3.8 Transaction Commit Requires 4 fdatasync - -The current transaction algorithm is: - -1. write_recovery_data(); - -2. sync(); - -3. write_recovery_header(); - -4. sync(); - -5. overwrite_with_new_data(); - -6. sync(); - -7. remove_recovery_header(); - -8. sync(); - -On current ext3, each sync flushes all data to disk, so the next -3 syncs are relatively expensive. But this could become a -performance bottleneck on other filesystems such as ext4. - -3.8.1 Proposed Solution - -Neil Brown points out that this is overzealous, and only one sync -is needed: - -1. Bundle the recovery data, a transaction counter and a strong - checksum of the new data. - -2. Strong checksum that whole bundle. - -3. Store the bundle in the database. - -4. Overwrite the oldest of the two recovery pointers in the - header (identified using the transaction counter) with the - offset of this bundle. - -5. sync. - -6. Write the new data to the file. - -Checking for recovery means identifying the latest bundle with a -valid checksum and using the new data checksum to ensure that it -has been applied. This is more expensive than the current check, -but need only be done at open. For running databases, a separate -header field can be used to indicate a transaction in progress; -we need only check for recovery if this is set. - -3.8.2 Status - -Deferred. - -3.9 TDB Does Not Have Snapshot Support - -3.9.1 Proposed SolutionNone. At some point you say “use a real - database” (but see [replay-attribute]). - -But as a thought experiment, if we implemented transactions to -only overwrite free entries (this is tricky: there must not be a -header in each entry which indicates whether it is free, but use -of presence in metadata elsewhere), and a pointer to the hash -table, we could create an entirely new commit without destroying -existing data. Then it would be easy to implement snapshots in a -similar way. - -This would not allow arbitrary changes to the database, such as -tdb_repack does, and would require more space (since we have to -preserve the current and future entries at once). If we used hash -trees rather than one big hash table, we might only have to -rewrite some sections of the hash, too. - -We could then implement snapshots using a similar method, using -multiple different hash tables/free tables. - -3.9.2 Status - -Deferred. - -3.10 Transactions Cannot Operate in Parallel - -This would be useless for ldb, as it hits the index records with -just about every update. It would add significant complexity in -resolving clashes, and cause the all transaction callers to write -their code to loop in the case where the transactions spuriously -failed. - -3.10.1 Proposed Solution - -None (but see [replay-attribute]). We could solve a small part of -the problem by providing read-only transactions. These would -allow one write transaction to begin, but it could not commit -until all r/o transactions are done. This would require a new -RO_TRANSACTION_LOCK, which would be upgraded on commit. - -3.10.2 Status - -Deferred. - -3.11 Default Hash Function Is Suboptimal - -The Knuth-inspired multiplicative hash used by tdb is fairly slow -(especially if we expand it to 64 bits), and works best when the -hash bucket size is a prime number (which also means a slow -modulus). In addition, it is highly predictable which could -potentially lead to a Denial of Service attack in some TDB uses. - -3.11.1 Proposed Solution - -The Jenkins lookup3 hash[footnote: -http://burtleburtle.net/bob/c/lookup3.c -] is a fast and superbly-mixing hash. It's used by the Linux -kernel and almost everything else. This has the particular -properties that it takes an initial seed, and produces two 32 bit -hash numbers, which we can combine into a 64-bit hash. - -The seed should be created at tdb-creation time from some random -source, and placed in the header. This is far from foolproof, but -adds a little bit of protection against hash bombing. - -3.11.2 Status - -Complete. - -3.12 Reliable Traversal Adds Complexity - -We lock a record during traversal iteration, and try to grab that -lock in the delete code. If that grab on delete fails, we simply -mark it deleted and continue onwards; traversal checks for this -condition and does the delete when it moves off the record. - -If traversal terminates, the dead record may be left -indefinitely. - -3.12.1 Proposed Solution - -Remove reliability guarantees; see [traverse-Proposed-Solution]. - -3.12.2 Status - -Complete. - -3.13 Fcntl Locking Adds Overhead - -Placing a fcntl lock means a system call, as does removing one. -This is actually one reason why transactions can be faster -(everything is locked once at transaction start). In the -uncontended case, this overhead can theoretically be eliminated. - -3.13.1 Proposed Solution - -None. - -We tried this before with spinlock support, in the early days of -TDB, and it didn't make much difference except in manufactured -benchmarks. - -We could use spinlocks (with futex kernel support under Linux), -but it means that we lose automatic cleanup when a process dies -with a lock. There is a method of auto-cleanup under Linux, but -it's not supported by other operating systems. We could -reintroduce a clear-if-first-style lock and sweep for dead -futexes on open, but that wouldn't help the normal case of one -concurrent opener dying. Increasingly elaborate repair schemes -could be considered, but they require an ABI change (everyone -must use them) anyway, so there's no need to do this at the same -time as everything else. - -3.14 Some Transactions Don't Require Durability - -Volker points out that gencache uses a CLEAR_IF_FIRST tdb for -normal (fast) usage, and occasionally empties the results into a -transactional TDB. This kind of usage prioritizes performance -over durability: as long as we are consistent, data can be lost. - -This would be more neatly implemented inside tdb: a “soft” -transaction commit (ie. syncless) which meant that data may be -reverted on a crash. - -3.14.1 Proposed Solution - -None. - -Unfortunately any transaction scheme which overwrites old data -requires a sync before that overwrite to avoid the possibility of -corruption. - -It seems possible to use a scheme similar to that described in [sub:TDB-Does-Not] -,where transactions are committed without overwriting existing -data, and an array of top-level pointers were available in the -header. If the transaction is “soft” then we would not need a -sync at all: existing processes would pick up the new hash table -and free list and work with that. - -At some later point, a sync would allow recovery of the old data -into the free lists (perhaps when the array of top-level pointers -filled). On crash, tdb_open() would examine the array of top -levels, and apply the transactions until it encountered an -invalid checksum. - -3.15 Tracing Is Fragile, Replay Is External - -The current TDB has compile-time-enabled tracing code, but it -often breaks as it is not enabled by default. In a similar way, -the ctdb code has an external wrapper which does replay tracing -so it can coordinate cluster-wide transactions. - -3.15.1 Proposed Solution - -Tridge points out that an attribute can be later added to -tdb_open (see [attributes]) to provide replay/trace hooks, which -could become the basis for this and future parallel transactions -and snapshot support. - -3.15.2 Status - -Deferred. - diff --git a/ccan/tdb2/free.c b/ccan/tdb2/free.c deleted file mode 100644 index e693fe82..00000000 --- a/ccan/tdb2/free.c +++ /dev/null @@ -1,975 +0,0 @@ - /* - Trivial Database 2: free list/block handling - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "private.h" -#include -#include -#include -#include -#include - -static unsigned fls64(uint64_t val) -{ - return ilog64(val); -} - -/* In which bucket would we find a particular record size? (ignoring header) */ -unsigned int size_to_bucket(tdb_len_t data_len) -{ - unsigned int bucket; - - /* We can't have records smaller than this. */ - assert(data_len >= TDB_MIN_DATA_LEN); - - /* Ignoring the header... */ - if (data_len - TDB_MIN_DATA_LEN <= 64) { - /* 0 in bucket 0, 8 in bucket 1... 64 in bucket 8. */ - bucket = (data_len - TDB_MIN_DATA_LEN) / 8; - } else { - /* After that we go power of 2. */ - bucket = fls64(data_len - TDB_MIN_DATA_LEN) + 2; - } - - if (unlikely(bucket >= TDB_FREE_BUCKETS)) - bucket = TDB_FREE_BUCKETS - 1; - return bucket; -} - -tdb_off_t first_ftable(struct tdb_context *tdb) -{ - return tdb_read_off(tdb, offsetof(struct tdb_header, free_table)); -} - -tdb_off_t next_ftable(struct tdb_context *tdb, tdb_off_t ftable) -{ - return tdb_read_off(tdb, ftable + offsetof(struct tdb_freetable,next)); -} - -enum TDB_ERROR tdb_ftable_init(struct tdb_context *tdb) -{ - /* Use reservoir sampling algorithm to select a free list at random. */ - unsigned int rnd, max = 0, count = 0; - tdb_off_t off; - - tdb->tdb2.ftable_off = off = first_ftable(tdb); - tdb->tdb2.ftable = 0; - - while (off) { - if (TDB_OFF_IS_ERR(off)) { - return TDB_OFF_TO_ERR(off); - } - - rnd = random(); - if (rnd >= max) { - tdb->tdb2.ftable_off = off; - tdb->tdb2.ftable = count; - max = rnd; - } - - off = next_ftable(tdb, off); - count++; - } - return TDB_SUCCESS; -} - -/* Offset of a given bucket. */ -tdb_off_t bucket_off(tdb_off_t ftable_off, unsigned bucket) -{ - return ftable_off + offsetof(struct tdb_freetable, buckets) - + bucket * sizeof(tdb_off_t); -} - -/* Returns free_buckets + 1, or list number to search, or -ve error. */ -static tdb_off_t find_free_head(struct tdb_context *tdb, - tdb_off_t ftable_off, - tdb_off_t bucket) -{ - /* Speculatively search for a non-zero bucket. */ - return tdb_find_nonzero_off(tdb, bucket_off(ftable_off, 0), - bucket, TDB_FREE_BUCKETS); -} - -static void check_list(struct tdb_context *tdb, tdb_off_t b_off) -{ -#ifdef CCAN_TDB2_DEBUG - tdb_off_t off, prev = 0, first; - struct tdb_free_record r; - - first = off = (tdb_read_off(tdb, b_off) & TDB_OFF_MASK); - while (off != 0) { - tdb_read_convert(tdb, off, &r, sizeof(r)); - if (frec_magic(&r) != TDB_FREE_MAGIC) - abort(); - if (prev && frec_prev(&r) != prev) - abort(); - prev = off; - off = r.next; - } - - if (first) { - tdb_read_convert(tdb, first, &r, sizeof(r)); - if (frec_prev(&r) != prev) - abort(); - } -#endif -} - -/* Remove from free bucket. */ -static enum TDB_ERROR remove_from_list(struct tdb_context *tdb, - tdb_off_t b_off, tdb_off_t r_off, - const struct tdb_free_record *r) -{ - tdb_off_t off, prev_next, head; - enum TDB_ERROR ecode; - - /* Is this only element in list? Zero out bucket, and we're done. */ - if (frec_prev(r) == r_off) - return tdb_write_off(tdb, b_off, 0); - - /* off = &r->prev->next */ - off = frec_prev(r) + offsetof(struct tdb_free_record, next); - - /* Get prev->next */ - prev_next = tdb_read_off(tdb, off); - if (TDB_OFF_IS_ERR(prev_next)) - return TDB_OFF_TO_ERR(prev_next); - - /* If prev->next == 0, we were head: update bucket to point to next. */ - if (prev_next == 0) { - /* We must preserve upper bits. */ - head = tdb_read_off(tdb, b_off); - if (TDB_OFF_IS_ERR(head)) - return TDB_OFF_TO_ERR(head); - - if ((head & TDB_OFF_MASK) != r_off) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "remove_from_list:" - " %llu head %llu on list %llu", - (long long)r_off, - (long long)head, - (long long)b_off); - } - head = ((head & ~TDB_OFF_MASK) | r->next); - ecode = tdb_write_off(tdb, b_off, head); - if (ecode != TDB_SUCCESS) - return ecode; - } else { - /* r->prev->next = r->next */ - ecode = tdb_write_off(tdb, off, r->next); - if (ecode != TDB_SUCCESS) - return ecode; - } - - /* If we were the tail, off = &head->prev. */ - if (r->next == 0) { - head = tdb_read_off(tdb, b_off); - if (TDB_OFF_IS_ERR(head)) - return TDB_OFF_TO_ERR(head); - head &= TDB_OFF_MASK; - off = head + offsetof(struct tdb_free_record, magic_and_prev); - } else { - /* off = &r->next->prev */ - off = r->next + offsetof(struct tdb_free_record, - magic_and_prev); - } - -#ifdef CCAN_TDB2_DEBUG - /* *off == r */ - if ((tdb_read_off(tdb, off) & TDB_OFF_MASK) != r_off) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "remove_from_list:" - " %llu bad prev in list %llu", - (long long)r_off, (long long)b_off); - } -#endif - /* r->next->prev = r->prev */ - return tdb_write_off(tdb, off, r->magic_and_prev); -} - -/* Enqueue in this free bucket: sets coalesce if we've added 128 - * entries to it. */ -static enum TDB_ERROR enqueue_in_free(struct tdb_context *tdb, - tdb_off_t b_off, - tdb_off_t off, - tdb_len_t len, - bool *coalesce) -{ - struct tdb_free_record new; - enum TDB_ERROR ecode; - tdb_off_t prev, head; - uint64_t magic = (TDB_FREE_MAGIC << (64 - TDB_OFF_UPPER_STEAL)); - - head = tdb_read_off(tdb, b_off); - if (TDB_OFF_IS_ERR(head)) - return TDB_OFF_TO_ERR(head); - - /* We only need to set ftable_and_len; rest is set in enqueue_in_free */ - new.ftable_and_len = ((uint64_t)tdb->tdb2.ftable << (64 - TDB_OFF_UPPER_STEAL)) - | len; - - /* new->next = head. */ - new.next = (head & TDB_OFF_MASK); - - /* First element? Prev points to ourselves. */ - if (!new.next) { - new.magic_and_prev = (magic | off); - } else { - /* new->prev = next->prev */ - prev = tdb_read_off(tdb, - new.next + offsetof(struct tdb_free_record, - magic_and_prev)); - new.magic_and_prev = prev; - if (frec_magic(&new) != TDB_FREE_MAGIC) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "enqueue_in_free: %llu bad head" - " prev %llu", - (long long)new.next, - (long long)prev); - } - /* next->prev = new. */ - ecode = tdb_write_off(tdb, new.next - + offsetof(struct tdb_free_record, - magic_and_prev), - off | magic); - if (ecode != TDB_SUCCESS) { - return ecode; - } - -#ifdef CCAN_TDB2_DEBUG - prev = tdb_read_off(tdb, frec_prev(&new) - + offsetof(struct tdb_free_record, next)); - if (prev != 0) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "enqueue_in_free:" - " %llu bad tail next ptr %llu", - (long long)frec_prev(&new) - + offsetof(struct tdb_free_record, - next), - (long long)prev); - } -#endif - } - - /* Update enqueue count, but don't set high bit: see TDB_OFF_IS_ERR */ - if (*coalesce) - head += (1ULL << (64 - TDB_OFF_UPPER_STEAL)); - head &= ~(TDB_OFF_MASK | (1ULL << 63)); - head |= off; - - ecode = tdb_write_off(tdb, b_off, head); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* It's time to coalesce if counter wrapped. */ - if (*coalesce) - *coalesce = ((head & ~TDB_OFF_MASK) == 0); - - return tdb_write_convert(tdb, off, &new, sizeof(new)); -} - -static tdb_off_t ftable_offset(struct tdb_context *tdb, unsigned int ftable) -{ - tdb_off_t off; - unsigned int i; - - if (likely(tdb->tdb2.ftable == ftable)) - return tdb->tdb2.ftable_off; - - off = first_ftable(tdb); - for (i = 0; i < ftable; i++) { - if (TDB_OFF_IS_ERR(off)) { - break; - } - off = next_ftable(tdb, off); - } - return off; -} - -/* Note: we unlock the current bucket if fail (-ve), or coalesce (+ve) and - * need to blatt the *protect record (which is set to an error). */ -static tdb_len_t coalesce(struct tdb_context *tdb, - tdb_off_t off, tdb_off_t b_off, - tdb_len_t data_len, - tdb_off_t *protect) -{ - tdb_off_t end; - struct tdb_free_record rec; - enum TDB_ERROR ecode; - - tdb->stats.alloc_coalesce_tried++; - end = off + sizeof(struct tdb_used_record) + data_len; - - while (end < tdb->file->map_size) { - const struct tdb_free_record *r; - tdb_off_t nb_off; - unsigned ftable, bucket; - - r = tdb_access_read(tdb, end, sizeof(*r), true); - if (TDB_PTR_IS_ERR(r)) { - ecode = TDB_PTR_ERR(r); - goto err; - } - - if (frec_magic(r) != TDB_FREE_MAGIC - || frec_ftable(r) == TDB_FTABLE_NONE) { - tdb_access_release(tdb, r); - break; - } - - ftable = frec_ftable(r); - bucket = size_to_bucket(frec_len(r)); - nb_off = ftable_offset(tdb, ftable); - if (TDB_OFF_IS_ERR(nb_off)) { - tdb_access_release(tdb, r); - ecode = TDB_OFF_TO_ERR(nb_off); - goto err; - } - nb_off = bucket_off(nb_off, bucket); - tdb_access_release(tdb, r); - - /* We may be violating lock order here, so best effort. */ - if (tdb_lock_free_bucket(tdb, nb_off, TDB_LOCK_NOWAIT) - != TDB_SUCCESS) { - tdb->stats.alloc_coalesce_lockfail++; - break; - } - - /* Now we have lock, re-check. */ - ecode = tdb_read_convert(tdb, end, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - tdb_unlock_free_bucket(tdb, nb_off); - goto err; - } - - if (unlikely(frec_magic(&rec) != TDB_FREE_MAGIC)) { - tdb->stats.alloc_coalesce_race++; - tdb_unlock_free_bucket(tdb, nb_off); - break; - } - - if (unlikely(frec_ftable(&rec) != ftable) - || unlikely(size_to_bucket(frec_len(&rec)) != bucket)) { - tdb->stats.alloc_coalesce_race++; - tdb_unlock_free_bucket(tdb, nb_off); - break; - } - - /* Did we just mess up a record you were hoping to use? */ - if (end == *protect) { - tdb->stats.alloc_coalesce_iterate_clash++; - *protect = TDB_ERR_TO_OFF(TDB_ERR_NOEXIST); - } - - ecode = remove_from_list(tdb, nb_off, end, &rec); - check_list(tdb, nb_off); - if (ecode != TDB_SUCCESS) { - tdb_unlock_free_bucket(tdb, nb_off); - goto err; - } - - end += sizeof(struct tdb_used_record) + frec_len(&rec); - tdb_unlock_free_bucket(tdb, nb_off); - tdb->stats.alloc_coalesce_num_merged++; - } - - /* Didn't find any adjacent free? */ - if (end == off + sizeof(struct tdb_used_record) + data_len) - return 0; - - /* Before we expand, check this isn't one you wanted protected? */ - if (off == *protect) { - *protect = TDB_ERR_TO_OFF(TDB_ERR_EXISTS); - tdb->stats.alloc_coalesce_iterate_clash++; - } - - /* OK, expand initial record */ - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - goto err; - } - - if (frec_len(&rec) != data_len) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "coalesce: expected data len %zu not %zu", - (size_t)data_len, (size_t)frec_len(&rec)); - goto err; - } - - ecode = remove_from_list(tdb, b_off, off, &rec); - check_list(tdb, b_off); - if (ecode != TDB_SUCCESS) { - goto err; - } - - /* Try locking violation first. We don't allow coalesce recursion! */ - ecode = add_free_record(tdb, off, end - off, TDB_LOCK_NOWAIT, false); - if (ecode != TDB_SUCCESS) { - /* Need to drop lock. Can't rely on anything stable. */ - tdb->stats.alloc_coalesce_lockfail++; - *protect = TDB_ERR_TO_OFF(TDB_ERR_CORRUPT); - - /* We have to drop this to avoid deadlocks, so make sure record - * doesn't get coalesced by someone else! */ - rec.ftable_and_len = (TDB_FTABLE_NONE - << (64 - TDB_OFF_UPPER_STEAL)) - | (end - off - sizeof(struct tdb_used_record)); - ecode = tdb_write_off(tdb, - off + offsetof(struct tdb_free_record, - ftable_and_len), - rec.ftable_and_len); - if (ecode != TDB_SUCCESS) { - goto err; - } - - tdb_unlock_free_bucket(tdb, b_off); - - ecode = add_free_record(tdb, off, end - off, TDB_LOCK_WAIT, - false); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - } else if (TDB_OFF_IS_ERR(*protect)) { - /* For simplicity, we always drop lock if they can't continue */ - tdb_unlock_free_bucket(tdb, b_off); - } - tdb->stats.alloc_coalesce_succeeded++; - - /* Return usable length. */ - return end - off - sizeof(struct tdb_used_record); - -err: - /* To unify error paths, we *always* unlock bucket on error. */ - tdb_unlock_free_bucket(tdb, b_off); - return TDB_ERR_TO_OFF(ecode); -} - -/* List is locked: we unlock it. */ -static enum TDB_ERROR coalesce_list(struct tdb_context *tdb, - tdb_off_t ftable_off, - tdb_off_t b_off, - unsigned int limit) -{ - enum TDB_ERROR ecode; - tdb_off_t off; - - off = tdb_read_off(tdb, b_off); - if (TDB_OFF_IS_ERR(off)) { - ecode = TDB_OFF_TO_ERR(off); - goto unlock_err; - } - /* A little bit of paranoia: counter should be 0. */ - off &= TDB_OFF_MASK; - - while (off && limit--) { - struct tdb_free_record rec; - tdb_len_t coal; - tdb_off_t next; - - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) - goto unlock_err; - - next = rec.next; - coal = coalesce(tdb, off, b_off, frec_len(&rec), &next); - if (TDB_OFF_IS_ERR(coal)) { - /* This has already unlocked on error. */ - return TDB_OFF_TO_ERR(coal); - } - if (TDB_OFF_IS_ERR(next)) { - /* Coalescing had to unlock, so stop. */ - return TDB_SUCCESS; - } - /* Keep going if we're doing well... */ - limit += size_to_bucket(coal / 16 + TDB_MIN_DATA_LEN); - off = next; - } - - /* Now, move those elements to the tail of the list so we get something - * else next time. */ - if (off) { - struct tdb_free_record oldhrec, newhrec, oldtrec, newtrec; - tdb_off_t oldhoff, oldtoff, newtoff; - - /* The record we were up to is the new head. */ - ecode = tdb_read_convert(tdb, off, &newhrec, sizeof(newhrec)); - if (ecode != TDB_SUCCESS) - goto unlock_err; - - /* Get the new tail. */ - newtoff = frec_prev(&newhrec); - ecode = tdb_read_convert(tdb, newtoff, &newtrec, - sizeof(newtrec)); - if (ecode != TDB_SUCCESS) - goto unlock_err; - - /* Get the old head. */ - oldhoff = tdb_read_off(tdb, b_off); - if (TDB_OFF_IS_ERR(oldhoff)) { - ecode = TDB_OFF_TO_ERR(oldhoff); - goto unlock_err; - } - - /* This could happen if they all coalesced away. */ - if (oldhoff == off) - goto out; - - ecode = tdb_read_convert(tdb, oldhoff, &oldhrec, - sizeof(oldhrec)); - if (ecode != TDB_SUCCESS) - goto unlock_err; - - /* Get the old tail. */ - oldtoff = frec_prev(&oldhrec); - ecode = tdb_read_convert(tdb, oldtoff, &oldtrec, - sizeof(oldtrec)); - if (ecode != TDB_SUCCESS) - goto unlock_err; - - /* Old tail's next points to old head. */ - oldtrec.next = oldhoff; - - /* Old head's prev points to old tail. */ - oldhrec.magic_and_prev - = (TDB_FREE_MAGIC << (64 - TDB_OFF_UPPER_STEAL)) - | oldtoff; - - /* New tail's next is 0. */ - newtrec.next = 0; - - /* Write out the modified versions. */ - ecode = tdb_write_convert(tdb, oldtoff, &oldtrec, - sizeof(oldtrec)); - if (ecode != TDB_SUCCESS) - goto unlock_err; - - ecode = tdb_write_convert(tdb, oldhoff, &oldhrec, - sizeof(oldhrec)); - if (ecode != TDB_SUCCESS) - goto unlock_err; - - ecode = tdb_write_convert(tdb, newtoff, &newtrec, - sizeof(newtrec)); - if (ecode != TDB_SUCCESS) - goto unlock_err; - - /* And finally link in new head. */ - ecode = tdb_write_off(tdb, b_off, off); - if (ecode != TDB_SUCCESS) - goto unlock_err; - } -out: - tdb_unlock_free_bucket(tdb, b_off); - return TDB_SUCCESS; - -unlock_err: - tdb_unlock_free_bucket(tdb, b_off); - return ecode; -} - -/* List must not be locked if coalesce_ok is set. */ -enum TDB_ERROR add_free_record(struct tdb_context *tdb, - tdb_off_t off, tdb_len_t len_with_header, - enum tdb_lock_flags waitflag, - bool coalesce) -{ - tdb_off_t b_off; - tdb_len_t len; - enum TDB_ERROR ecode; - - assert(len_with_header >= sizeof(struct tdb_free_record)); - - len = len_with_header - sizeof(struct tdb_used_record); - - b_off = bucket_off(tdb->tdb2.ftable_off, size_to_bucket(len)); - ecode = tdb_lock_free_bucket(tdb, b_off, waitflag); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - ecode = enqueue_in_free(tdb, b_off, off, len, &coalesce); - check_list(tdb, b_off); - - /* Coalescing unlocks free list. */ - if (!ecode && coalesce) - ecode = coalesce_list(tdb, tdb->tdb2.ftable_off, b_off, 2); - else - tdb_unlock_free_bucket(tdb, b_off); - return ecode; -} - -static size_t adjust_size(size_t keylen, size_t datalen) -{ - size_t size = keylen + datalen; - - if (size < TDB_MIN_DATA_LEN) - size = TDB_MIN_DATA_LEN; - - /* Round to next uint64_t boundary. */ - return (size + (sizeof(uint64_t) - 1ULL)) & ~(sizeof(uint64_t) - 1ULL); -} - -/* If we have enough left over to be useful, split that off. */ -static size_t record_leftover(size_t keylen, size_t datalen, - bool want_extra, size_t total_len) -{ - ssize_t leftover; - - if (want_extra) - datalen += datalen / 2; - leftover = total_len - adjust_size(keylen, datalen); - - if (leftover < (ssize_t)sizeof(struct tdb_free_record)) - return 0; - - return leftover; -} - -/* We need size bytes to put our key and data in. */ -static tdb_off_t lock_and_alloc(struct tdb_context *tdb, - tdb_off_t ftable_off, - tdb_off_t bucket, - size_t keylen, size_t datalen, - bool want_extra, - unsigned magic, - unsigned hashlow) -{ - tdb_off_t off, b_off,best_off; - struct tdb_free_record best = { 0 }; - double multiplier; - size_t size = adjust_size(keylen, datalen); - enum TDB_ERROR ecode; - - tdb->stats.allocs++; - b_off = bucket_off(ftable_off, bucket); - - /* FIXME: Try non-blocking wait first, to measure contention. */ - /* Lock this bucket. */ - ecode = tdb_lock_free_bucket(tdb, b_off, TDB_LOCK_WAIT); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - - best.ftable_and_len = -1ULL; - best_off = 0; - - /* Get slack if we're after extra. */ - if (want_extra) - multiplier = 1.5; - else - multiplier = 1.0; - - /* Walk the list to see if any are large enough, getting less fussy - * as we go. */ - off = tdb_read_off(tdb, b_off); - if (TDB_OFF_IS_ERR(off)) { - ecode = TDB_OFF_TO_ERR(off); - goto unlock_err; - } - off &= TDB_OFF_MASK; - - while (off) { - const struct tdb_free_record *r; - tdb_len_t len; - tdb_off_t next; - - r = tdb_access_read(tdb, off, sizeof(*r), true); - if (TDB_PTR_IS_ERR(r)) { - ecode = TDB_PTR_ERR(r); - goto unlock_err; - } - - if (frec_magic(r) != TDB_FREE_MAGIC) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "lock_and_alloc:" - " %llu non-free 0x%llx", - (long long)off, - (long long)r->magic_and_prev); - tdb_access_release(tdb, r); - goto unlock_err; - } - - if (frec_len(r) >= size && frec_len(r) < frec_len(&best)) { - best_off = off; - best = *r; - } - - if (frec_len(&best) <= size * multiplier && best_off) { - tdb_access_release(tdb, r); - break; - } - - multiplier *= 1.01; - - next = r->next; - len = frec_len(r); - tdb_access_release(tdb, r); - off = next; - } - - /* If we found anything at all, use it. */ - if (best_off) { - struct tdb_used_record rec; - size_t leftover; - - /* We're happy with this size: take it. */ - ecode = remove_from_list(tdb, b_off, best_off, &best); - check_list(tdb, b_off); - if (ecode != TDB_SUCCESS) { - goto unlock_err; - } - - leftover = record_leftover(keylen, datalen, want_extra, - frec_len(&best)); - - assert(keylen + datalen + leftover <= frec_len(&best)); - /* We need to mark non-free before we drop lock, otherwise - * coalesce() could try to merge it! */ - ecode = set_header(tdb, &rec, magic, keylen, datalen, - frec_len(&best) - leftover, hashlow); - if (ecode != TDB_SUCCESS) { - goto unlock_err; - } - - ecode = tdb_write_convert(tdb, best_off, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - goto unlock_err; - } - - /* For futureproofing, we put a 0 in any unused space. */ - if (rec_extra_padding(&rec)) { - ecode = tdb->tdb2.io->twrite(tdb, best_off + sizeof(rec) - + keylen + datalen, "", 1); - if (ecode != TDB_SUCCESS) { - goto unlock_err; - } - } - - /* Bucket of leftover will be <= current bucket, so nested - * locking is allowed. */ - if (leftover) { - tdb->stats.alloc_leftover++; - ecode = add_free_record(tdb, - best_off + sizeof(rec) - + frec_len(&best) - leftover, - leftover, TDB_LOCK_WAIT, false); - if (ecode != TDB_SUCCESS) { - best_off = TDB_ERR_TO_OFF(ecode); - } - } - tdb_unlock_free_bucket(tdb, b_off); - - return best_off; - } - - tdb_unlock_free_bucket(tdb, b_off); - return 0; - -unlock_err: - tdb_unlock_free_bucket(tdb, b_off); - return TDB_ERR_TO_OFF(ecode); -} - -/* Get a free block from current free list, or 0 if none, -ve on error. */ -static tdb_off_t get_free(struct tdb_context *tdb, - size_t keylen, size_t datalen, bool want_extra, - unsigned magic, unsigned hashlow) -{ - tdb_off_t off, ftable_off; - tdb_off_t start_b, b, ftable; - bool wrapped = false; - - /* If they are growing, add 50% to get to higher bucket. */ - if (want_extra) - start_b = size_to_bucket(adjust_size(keylen, - datalen + datalen / 2)); - else - start_b = size_to_bucket(adjust_size(keylen, datalen)); - - ftable_off = tdb->tdb2.ftable_off; - ftable = tdb->tdb2.ftable; - while (!wrapped || ftable_off != tdb->tdb2.ftable_off) { - /* Start at exact size bucket, and search up... */ - for (b = find_free_head(tdb, ftable_off, start_b); - b < TDB_FREE_BUCKETS; - b = find_free_head(tdb, ftable_off, b + 1)) { - /* Try getting one from list. */ - off = lock_and_alloc(tdb, ftable_off, - b, keylen, datalen, want_extra, - magic, hashlow); - if (TDB_OFF_IS_ERR(off)) - return off; - if (off != 0) { - if (b == start_b) - tdb->stats.alloc_bucket_exact++; - if (b == TDB_FREE_BUCKETS - 1) - tdb->stats.alloc_bucket_max++; - /* Worked? Stay using this list. */ - tdb->tdb2.ftable_off = ftable_off; - tdb->tdb2.ftable = ftable; - return off; - } - /* Didn't work. Try next bucket. */ - } - - if (TDB_OFF_IS_ERR(b)) { - return b; - } - - /* Hmm, try next table. */ - ftable_off = next_ftable(tdb, ftable_off); - if (TDB_OFF_IS_ERR(ftable_off)) { - return ftable_off; - } - ftable++; - - if (ftable_off == 0) { - wrapped = true; - ftable_off = first_ftable(tdb); - if (TDB_OFF_IS_ERR(ftable_off)) { - return ftable_off; - } - ftable = 0; - } - } - - return 0; -} - -enum TDB_ERROR set_header(struct tdb_context *tdb, - struct tdb_used_record *rec, - unsigned magic, uint64_t keylen, uint64_t datalen, - uint64_t actuallen, unsigned hashlow) -{ - uint64_t keybits = (fls64(keylen) + 1) / 2; - - /* Use bottom bits of hash, so it's independent of hash table size. */ - rec->magic_and_meta = (hashlow & ((1 << 11)-1)) - | ((actuallen - (keylen + datalen)) << 11) - | (keybits << 43) - | ((uint64_t)magic << 48); - rec->key_and_data_len = (keylen | (datalen << (keybits*2))); - - /* Encoding can fail on big values. */ - if (rec_key_length(rec) != keylen - || rec_data_length(rec) != datalen - || rec_extra_padding(rec) != actuallen - (keylen + datalen)) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "Could not encode k=%llu,d=%llu,a=%llu", - (long long)keylen, (long long)datalen, - (long long)actuallen); - } - return TDB_SUCCESS; -} - -/* You need 'size', this tells you how much you should expand by. */ -tdb_off_t tdb_expand_adjust(tdb_off_t map_size, tdb_off_t size) -{ - tdb_off_t new_size, top_size; - - /* limit size in order to avoid using up huge amounts of memory for - * in memory tdbs if an oddball huge record creeps in */ - if (size > 100 * 1024) { - top_size = map_size + size * 2; - } else { - top_size = map_size + size * 100; - } - - /* always make room for at least top_size more records, and at - least 25% more space. if the DB is smaller than 100MiB, - otherwise grow it by 10% only. */ - if (map_size > 100 * 1024 * 1024) { - new_size = map_size * 1.10; - } else { - new_size = map_size * 1.25; - } - - /* Round the database up to a multiple of the page size */ - if (new_size < top_size) - new_size = top_size; - return new_size - map_size; -} - -/* Expand the database. */ -static enum TDB_ERROR tdb_expand(struct tdb_context *tdb, tdb_len_t size) -{ - uint64_t old_size; - tdb_len_t wanted; - enum TDB_ERROR ecode; - - /* Need to hold a hash lock to expand DB: transactions rely on it. */ - if (!(tdb->flags & TDB_NOLOCK) - && !tdb->file->allrecord_lock.count && !tdb_has_hash_locks(tdb)) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_expand: must hold lock during expand"); - } - - /* Only one person can expand file at a time. */ - ecode = tdb_lock_expand(tdb, F_WRLCK); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* Someone else may have expanded the file, so retry. */ - old_size = tdb->file->map_size; - tdb->tdb2.io->oob(tdb, tdb->file->map_size, 1, true); - if (tdb->file->map_size != old_size) { - tdb_unlock_expand(tdb, F_WRLCK); - return TDB_SUCCESS; - } - - /* Overallocate. */ - wanted = tdb_expand_adjust(old_size, size); - /* We need room for the record header too. */ - wanted = adjust_size(0, sizeof(struct tdb_used_record) + wanted); - - ecode = tdb->tdb2.io->expand_file(tdb, wanted); - if (ecode != TDB_SUCCESS) { - tdb_unlock_expand(tdb, F_WRLCK); - return ecode; - } - - /* We need to drop this lock before adding free record. */ - tdb_unlock_expand(tdb, F_WRLCK); - - tdb->stats.expands++; - return add_free_record(tdb, old_size, wanted, TDB_LOCK_WAIT, true); -} - -/* This won't fail: it will expand the database if it has to. */ -tdb_off_t alloc(struct tdb_context *tdb, size_t keylen, size_t datalen, - uint64_t hash, unsigned magic, bool growing) -{ - tdb_off_t off; - - /* We can't hold pointers during this: we could unmap! */ - assert(!tdb->tdb2.direct_access); - - for (;;) { - enum TDB_ERROR ecode; - off = get_free(tdb, keylen, datalen, growing, magic, hash); - if (likely(off != 0)) - break; - - ecode = tdb_expand(tdb, adjust_size(keylen, datalen)); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - } - - return off; -} diff --git a/ccan/tdb2/hash.c b/ccan/tdb2/hash.c deleted file mode 100644 index 619d56f8..00000000 --- a/ccan/tdb2/hash.c +++ /dev/null @@ -1,913 +0,0 @@ - /* - Trivial Database 2: hash handling - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "private.h" -#include -#include - -/* Default hash function. */ -uint64_t tdb_jenkins_hash(const void *key, size_t length, uint64_t seed, - void *unused) -{ - uint64_t ret; - /* hash64_stable assumes lower bits are more important; they are a - * slightly better hash. We use the upper bits first, so swap them. */ - ret = hash64_stable((const unsigned char *)key, length, seed); - return (ret >> 32) | (ret << 32); -} - -uint64_t tdb_hash(struct tdb_context *tdb, const void *ptr, size_t len) -{ - return tdb->hash_fn(ptr, len, tdb->hash_seed, tdb->hash_data); -} - -uint64_t hash_record(struct tdb_context *tdb, tdb_off_t off) -{ - const struct tdb_used_record *r; - const void *key; - uint64_t klen, hash; - - r = tdb_access_read(tdb, off, sizeof(*r), true); - if (TDB_PTR_IS_ERR(r)) { - /* FIXME */ - return 0; - } - - klen = rec_key_length(r); - tdb_access_release(tdb, r); - - key = tdb_access_read(tdb, off + sizeof(*r), klen, false); - if (TDB_PTR_IS_ERR(key)) { - return 0; - } - - hash = tdb_hash(tdb, key, klen); - tdb_access_release(tdb, key); - return hash; -} - -/* Get bits from a value. */ -static uint32_t bits_from(uint64_t val, unsigned start, unsigned num) -{ - assert(num <= 32); - return (val >> start) & ((1U << num) - 1); -} - -/* We take bits from the top: that way we can lock whole sections of the hash - * by using lock ranges. */ -static uint32_t use_bits(struct hash_info *h, unsigned num) -{ - h->hash_used += num; - return bits_from(h->h, 64 - h->hash_used, num); -} - -static tdb_bool_err key_matches(struct tdb_context *tdb, - const struct tdb_used_record *rec, - tdb_off_t off, - const struct tdb_data *key) -{ - tdb_bool_err ret = false; - const char *rkey; - - if (rec_key_length(rec) != key->dsize) { - tdb->stats.compare_wrong_keylen++; - return ret; - } - - rkey = tdb_access_read(tdb, off + sizeof(*rec), key->dsize, false); - if (TDB_PTR_IS_ERR(rkey)) { - return (tdb_bool_err)TDB_PTR_ERR(rkey); - } - if (memcmp(rkey, key->dptr, key->dsize) == 0) - ret = true; - else - tdb->stats.compare_wrong_keycmp++; - tdb_access_release(tdb, rkey); - return ret; -} - -/* Does entry match? */ -static tdb_bool_err match(struct tdb_context *tdb, - struct hash_info *h, - const struct tdb_data *key, - tdb_off_t val, - struct tdb_used_record *rec) -{ - tdb_off_t off; - enum TDB_ERROR ecode; - - tdb->stats.compares++; - /* Desired bucket must match. */ - if (h->home_bucket != (val & TDB_OFF_HASH_GROUP_MASK)) { - tdb->stats.compare_wrong_bucket++; - return false; - } - - /* Top bits of offset == next bits of hash. */ - if (bits_from(val, TDB_OFF_HASH_EXTRA_BIT, TDB_OFF_UPPER_STEAL_EXTRA) - != bits_from(h->h, 64 - h->hash_used - TDB_OFF_UPPER_STEAL_EXTRA, - TDB_OFF_UPPER_STEAL_EXTRA)) { - tdb->stats.compare_wrong_offsetbits++; - return false; - } - - off = val & TDB_OFF_MASK; - ecode = tdb_read_convert(tdb, off, rec, sizeof(*rec)); - if (ecode != TDB_SUCCESS) { - return (tdb_bool_err)ecode; - } - - if ((h->h & ((1 << 11)-1)) != rec_hash(rec)) { - tdb->stats.compare_wrong_rechash++; - return false; - } - - return key_matches(tdb, rec, off, key); -} - -static tdb_off_t hbucket_off(tdb_off_t group_start, unsigned bucket) -{ - return group_start - + (bucket % (1 << TDB_HASH_GROUP_BITS)) * sizeof(tdb_off_t); -} - -bool is_subhash(tdb_off_t val) -{ - return (val >> TDB_OFF_UPPER_STEAL_SUBHASH_BIT) & 1; -} - -/* FIXME: Guess the depth, don't over-lock! */ -static tdb_off_t hlock_range(tdb_off_t group, tdb_off_t *size) -{ - *size = 1ULL << (64 - (TDB_TOPLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS)); - return group << (64 - (TDB_TOPLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS)); -} - -static tdb_off_t COLD find_in_chain(struct tdb_context *tdb, - struct tdb_data key, - tdb_off_t chain, - struct hash_info *h, - struct tdb_used_record *rec, - struct traverse_info *tinfo) -{ - tdb_off_t off, next; - enum TDB_ERROR ecode; - - /* In case nothing is free, we set these to zero. */ - h->home_bucket = h->found_bucket = 0; - - for (off = chain; off; off = next) { - unsigned int i; - - h->group_start = off; - ecode = tdb_read_convert(tdb, off, h->group, sizeof(h->group)); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - - for (i = 0; i < (1 << TDB_HASH_GROUP_BITS); i++) { - tdb_off_t recoff; - if (!h->group[i]) { - /* Remember this empty bucket. */ - h->home_bucket = h->found_bucket = i; - continue; - } - - /* We can insert extra bits via add_to_hash - * empty bucket logic. */ - recoff = h->group[i] & TDB_OFF_MASK; - ecode = tdb_read_convert(tdb, recoff, rec, - sizeof(*rec)); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - - ecode = TDB_OFF_TO_ERR(key_matches(tdb, rec, recoff, - &key)); - if (ecode < 0) { - return TDB_ERR_TO_OFF(ecode); - } - if (ecode == (enum TDB_ERROR)1) { - h->home_bucket = h->found_bucket = i; - - if (tinfo) { - tinfo->levels[tinfo->num_levels] - .hashtable = off; - tinfo->levels[tinfo->num_levels] - .total_buckets - = 1 << TDB_HASH_GROUP_BITS; - tinfo->levels[tinfo->num_levels].entry - = i; - tinfo->num_levels++; - } - return recoff; - } - } - next = tdb_read_off(tdb, off - + offsetof(struct tdb_chain, next)); - if (TDB_OFF_IS_ERR(next)) { - return next; - } - if (next) - next += sizeof(struct tdb_used_record); - } - return 0; -} - -/* This is the core routine which searches the hashtable for an entry. - * On error, no locks are held and -ve is returned. - * Otherwise, hinfo is filled in (and the optional tinfo). - * If not found, the return value is 0. - * If found, the return value is the offset, and *rec is the record. */ -tdb_off_t find_and_lock(struct tdb_context *tdb, - struct tdb_data key, - int ltype, - struct hash_info *h, - struct tdb_used_record *rec, - struct traverse_info *tinfo) -{ - uint32_t i, group; - tdb_off_t hashtable; - enum TDB_ERROR ecode; - - h->h = tdb_hash(tdb, key.dptr, key.dsize); - h->hash_used = 0; - group = use_bits(h, TDB_TOPLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS); - h->home_bucket = use_bits(h, TDB_HASH_GROUP_BITS); - - h->hlock_start = hlock_range(group, &h->hlock_range); - ecode = tdb_lock_hashes(tdb, h->hlock_start, h->hlock_range, ltype, - TDB_LOCK_WAIT); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - - hashtable = offsetof(struct tdb_header, hashtable); - if (tinfo) { - tinfo->toplevel_group = group; - tinfo->num_levels = 1; - tinfo->levels[0].entry = 0; - tinfo->levels[0].hashtable = hashtable - + (group << TDB_HASH_GROUP_BITS) * sizeof(tdb_off_t); - tinfo->levels[0].total_buckets = 1 << TDB_HASH_GROUP_BITS; - } - - while (h->hash_used <= 64) { - /* Read in the hash group. */ - h->group_start = hashtable - + group * (sizeof(tdb_off_t) << TDB_HASH_GROUP_BITS); - - ecode = tdb_read_convert(tdb, h->group_start, &h->group, - sizeof(h->group)); - if (ecode != TDB_SUCCESS) { - goto fail; - } - - /* Pointer to another hash table? Go down... */ - if (is_subhash(h->group[h->home_bucket])) { - hashtable = (h->group[h->home_bucket] & TDB_OFF_MASK) - + sizeof(struct tdb_used_record); - if (tinfo) { - /* When we come back, use *next* bucket */ - tinfo->levels[tinfo->num_levels-1].entry - += h->home_bucket + 1; - } - group = use_bits(h, TDB_SUBLEVEL_HASH_BITS - - TDB_HASH_GROUP_BITS); - h->home_bucket = use_bits(h, TDB_HASH_GROUP_BITS); - if (tinfo) { - tinfo->levels[tinfo->num_levels].hashtable - = hashtable; - tinfo->levels[tinfo->num_levels].total_buckets - = 1 << TDB_SUBLEVEL_HASH_BITS; - tinfo->levels[tinfo->num_levels].entry - = group << TDB_HASH_GROUP_BITS; - tinfo->num_levels++; - } - continue; - } - - /* It's in this group: search (until 0 or all searched) */ - for (i = 0, h->found_bucket = h->home_bucket; - i < (1 << TDB_HASH_GROUP_BITS); - i++, h->found_bucket = ((h->found_bucket+1) - % (1 << TDB_HASH_GROUP_BITS))) { - tdb_bool_err berr; - if (is_subhash(h->group[h->found_bucket])) - continue; - - if (!h->group[h->found_bucket]) - break; - - berr = match(tdb, h, &key, h->group[h->found_bucket], - rec); - if (berr < 0) { - ecode = TDB_OFF_TO_ERR(berr); - goto fail; - } - if (berr) { - if (tinfo) { - tinfo->levels[tinfo->num_levels-1].entry - += h->found_bucket; - } - return h->group[h->found_bucket] & TDB_OFF_MASK; - } - } - /* Didn't find it: h indicates where it would go. */ - return 0; - } - - return find_in_chain(tdb, key, hashtable, h, rec, tinfo); - -fail: - tdb_unlock_hashes(tdb, h->hlock_start, h->hlock_range, ltype); - return TDB_ERR_TO_OFF(ecode); -} - -/* I wrote a simple test, expanding a hash to 2GB, for the following - * cases: - * 1) Expanding all the buckets at once, - * 2) Expanding the bucket we wanted to place the new entry into. - * 3) Expanding the most-populated bucket, - * - * I measured the worst/average/best density during this process. - * 1) 3%/16%/30% - * 2) 4%/20%/38% - * 3) 6%/22%/41% - * - * So we figure out the busiest bucket for the moment. - */ -static unsigned fullest_bucket(struct tdb_context *tdb, - const tdb_off_t *group, - unsigned new_bucket) -{ - unsigned counts[1 << TDB_HASH_GROUP_BITS] = { 0 }; - unsigned int i, best_bucket; - - /* Count the new entry. */ - counts[new_bucket]++; - best_bucket = new_bucket; - - for (i = 0; i < (1 << TDB_HASH_GROUP_BITS); i++) { - unsigned this_bucket; - - if (is_subhash(group[i])) - continue; - this_bucket = group[i] & TDB_OFF_HASH_GROUP_MASK; - if (++counts[this_bucket] > counts[best_bucket]) - best_bucket = this_bucket; - } - - return best_bucket; -} - -static bool put_into_group(tdb_off_t *group, - unsigned bucket, tdb_off_t encoded) -{ - unsigned int i; - - for (i = 0; i < (1 << TDB_HASH_GROUP_BITS); i++) { - unsigned b = (bucket + i) % (1 << TDB_HASH_GROUP_BITS); - - if (group[b] == 0) { - group[b] = encoded; - return true; - } - } - return false; -} - -static void force_into_group(tdb_off_t *group, - unsigned bucket, tdb_off_t encoded) -{ - if (!put_into_group(group, bucket, encoded)) - abort(); -} - -static tdb_off_t encode_offset(tdb_off_t new_off, struct hash_info *h) -{ - return h->home_bucket - | new_off - | ((uint64_t)bits_from(h->h, - 64 - h->hash_used - TDB_OFF_UPPER_STEAL_EXTRA, - TDB_OFF_UPPER_STEAL_EXTRA) - << TDB_OFF_HASH_EXTRA_BIT); -} - -/* Simply overwrite the hash entry we found before. */ -enum TDB_ERROR replace_in_hash(struct tdb_context *tdb, - struct hash_info *h, - tdb_off_t new_off) -{ - return tdb_write_off(tdb, hbucket_off(h->group_start, h->found_bucket), - encode_offset(new_off, h)); -} - -/* We slot in anywhere that's empty in the chain. */ -static enum TDB_ERROR COLD add_to_chain(struct tdb_context *tdb, - tdb_off_t subhash, - tdb_off_t new_off) -{ - tdb_off_t entry; - enum TDB_ERROR ecode; - - entry = tdb_find_zero_off(tdb, subhash, 1< 64) - return add_to_chain(tdb, subhash, off); - - h.h = hash_record(tdb, off); - gnum = use_bits(&h, TDB_SUBLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS); - h.group_start = subhash - + gnum * (sizeof(tdb_off_t) << TDB_HASH_GROUP_BITS); - h.home_bucket = use_bits(&h, TDB_HASH_GROUP_BITS); - - group = tdb_access_write(tdb, h.group_start, - sizeof(*group) << TDB_HASH_GROUP_BITS, true); - if (TDB_PTR_IS_ERR(group)) { - return TDB_PTR_ERR(group); - } - force_into_group(group, h.home_bucket, encode_offset(off, &h)); - return tdb_access_commit(tdb, group); -} - -static enum TDB_ERROR expand_group(struct tdb_context *tdb, struct hash_info *h) -{ - unsigned bucket, num_vals, i, magic; - size_t subsize; - tdb_off_t subhash; - tdb_off_t vals[1 << TDB_HASH_GROUP_BITS]; - enum TDB_ERROR ecode; - - /* Attach new empty subhash under fullest bucket. */ - bucket = fullest_bucket(tdb, h->group, h->home_bucket); - - if (h->hash_used == 64) { - tdb->stats.alloc_chain++; - subsize = sizeof(struct tdb_chain); - magic = TDB_CHAIN_MAGIC; - } else { - tdb->stats.alloc_subhash++; - subsize = (sizeof(tdb_off_t) << TDB_SUBLEVEL_HASH_BITS); - magic = TDB_HTABLE_MAGIC; - } - - subhash = alloc(tdb, 0, subsize, 0, magic, false); - if (TDB_OFF_IS_ERR(subhash)) { - return TDB_OFF_TO_ERR(subhash); - } - - ecode = zero_out(tdb, subhash + sizeof(struct tdb_used_record), - subsize); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* Remove any which are destined for bucket or are in wrong place. */ - num_vals = 0; - for (i = 0; i < (1 << TDB_HASH_GROUP_BITS); i++) { - unsigned home_bucket = h->group[i] & TDB_OFF_HASH_GROUP_MASK; - if (!h->group[i] || is_subhash(h->group[i])) - continue; - if (home_bucket == bucket || home_bucket != i) { - vals[num_vals++] = h->group[i]; - h->group[i] = 0; - } - } - /* FIXME: This assert is valid, but we do this during unit test :( */ - /* assert(num_vals); */ - - /* Overwrite expanded bucket with subhash pointer. */ - h->group[bucket] = subhash | (1ULL << TDB_OFF_UPPER_STEAL_SUBHASH_BIT); - - /* Point to actual contents of record. */ - subhash += sizeof(struct tdb_used_record); - - /* Put values back. */ - for (i = 0; i < num_vals; i++) { - unsigned this_bucket = vals[i] & TDB_OFF_HASH_GROUP_MASK; - - if (this_bucket == bucket) { - ecode = add_to_subhash(tdb, subhash, h->hash_used, - vals[i]); - if (ecode != TDB_SUCCESS) - return ecode; - } else { - /* There should be room to put this back. */ - force_into_group(h->group, this_bucket, vals[i]); - } - } - return TDB_SUCCESS; -} - -enum TDB_ERROR delete_from_hash(struct tdb_context *tdb, struct hash_info *h) -{ - unsigned int i, num_movers = 0; - tdb_off_t movers[1 << TDB_HASH_GROUP_BITS]; - - h->group[h->found_bucket] = 0; - for (i = 1; i < (1 << TDB_HASH_GROUP_BITS); i++) { - unsigned this_bucket; - - this_bucket = (h->found_bucket+i) % (1 << TDB_HASH_GROUP_BITS); - /* Empty bucket? We're done. */ - if (!h->group[this_bucket]) - break; - - /* Ignore subhashes. */ - if (is_subhash(h->group[this_bucket])) - continue; - - /* If this one is not happy where it is, we'll move it. */ - if ((h->group[this_bucket] & TDB_OFF_HASH_GROUP_MASK) - != this_bucket) { - movers[num_movers++] = h->group[this_bucket]; - h->group[this_bucket] = 0; - } - } - - /* Put back the ones we erased. */ - for (i = 0; i < num_movers; i++) { - force_into_group(h->group, movers[i] & TDB_OFF_HASH_GROUP_MASK, - movers[i]); - } - - /* Now we write back the hash group */ - return tdb_write_convert(tdb, h->group_start, - h->group, sizeof(h->group)); -} - -enum TDB_ERROR add_to_hash(struct tdb_context *tdb, struct hash_info *h, - tdb_off_t new_off) -{ - enum TDB_ERROR ecode; - - /* We hit an empty bucket during search? That's where it goes. */ - if (!h->group[h->found_bucket]) { - h->group[h->found_bucket] = encode_offset(new_off, h); - /* Write back the modified group. */ - return tdb_write_convert(tdb, h->group_start, - h->group, sizeof(h->group)); - } - - if (h->hash_used > 64) - return add_to_chain(tdb, h->group_start, new_off); - - /* We're full. Expand. */ - ecode = expand_group(tdb, h); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - if (is_subhash(h->group[h->home_bucket])) { - /* We were expanded! */ - tdb_off_t hashtable; - unsigned int gnum; - - /* Write back the modified group. */ - ecode = tdb_write_convert(tdb, h->group_start, h->group, - sizeof(h->group)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* Move hashinfo down a level. */ - hashtable = (h->group[h->home_bucket] & TDB_OFF_MASK) - + sizeof(struct tdb_used_record); - gnum = use_bits(h,TDB_SUBLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS); - h->home_bucket = use_bits(h, TDB_HASH_GROUP_BITS); - h->group_start = hashtable - + gnum * (sizeof(tdb_off_t) << TDB_HASH_GROUP_BITS); - ecode = tdb_read_convert(tdb, h->group_start, &h->group, - sizeof(h->group)); - if (ecode != TDB_SUCCESS) { - return ecode; - } - } - - /* Expanding the group must have made room if it didn't choose this - * bucket. */ - if (put_into_group(h->group, h->home_bucket, encode_offset(new_off,h))){ - return tdb_write_convert(tdb, h->group_start, - h->group, sizeof(h->group)); - } - - /* This can happen if all hashes in group (and us) dropped into same - * group in subhash. */ - return add_to_hash(tdb, h, new_off); -} - -/* Traverse support: returns offset of record, or 0 or -ve error. */ -static tdb_off_t iterate_hash(struct tdb_context *tdb, - struct traverse_info *tinfo) -{ - tdb_off_t off, val, i; - struct traverse_level *tlevel; - - tlevel = &tinfo->levels[tinfo->num_levels-1]; - -again: - for (i = tdb_find_nonzero_off(tdb, tlevel->hashtable, - tlevel->entry, tlevel->total_buckets); - i != tlevel->total_buckets; - i = tdb_find_nonzero_off(tdb, tlevel->hashtable, - i+1, tlevel->total_buckets)) { - if (TDB_OFF_IS_ERR(i)) { - return i; - } - - val = tdb_read_off(tdb, tlevel->hashtable+sizeof(tdb_off_t)*i); - if (TDB_OFF_IS_ERR(val)) { - return val; - } - - off = val & TDB_OFF_MASK; - - /* This makes the delete-all-in-traverse case work - * (and simplifies our logic a little). */ - if (off == tinfo->prev) - continue; - - tlevel->entry = i; - - if (!is_subhash(val)) { - /* Found one. */ - tinfo->prev = off; - return off; - } - - /* When we come back, we want the next one */ - tlevel->entry++; - tinfo->num_levels++; - tlevel++; - tlevel->hashtable = off + sizeof(struct tdb_used_record); - tlevel->entry = 0; - /* Next level is a chain? */ - if (unlikely(tinfo->num_levels == TDB_MAX_LEVELS + 1)) - tlevel->total_buckets = (1 << TDB_HASH_GROUP_BITS); - else - tlevel->total_buckets = (1 << TDB_SUBLEVEL_HASH_BITS); - goto again; - } - - /* Nothing there? */ - if (tinfo->num_levels == 1) - return 0; - - /* Handle chained entries. */ - if (unlikely(tinfo->num_levels == TDB_MAX_LEVELS + 1)) { - tlevel->hashtable = tdb_read_off(tdb, tlevel->hashtable - + offsetof(struct tdb_chain, - next)); - if (TDB_OFF_IS_ERR(tlevel->hashtable)) { - return tlevel->hashtable; - } - if (tlevel->hashtable) { - tlevel->hashtable += sizeof(struct tdb_used_record); - tlevel->entry = 0; - goto again; - } - } - - /* Go back up and keep searching. */ - tinfo->num_levels--; - tlevel--; - goto again; -} - -/* Return success if we find something, TDB_ERR_NOEXIST if none. */ -enum TDB_ERROR next_in_hash(struct tdb_context *tdb, - struct traverse_info *tinfo, - TDB_DATA *kbuf, size_t *dlen) -{ - const unsigned group_bits = TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS; - tdb_off_t hl_start, hl_range, off; - enum TDB_ERROR ecode; - - while (tinfo->toplevel_group < (1 << group_bits)) { - hl_start = (tdb_off_t)tinfo->toplevel_group - << (64 - group_bits); - hl_range = 1ULL << group_bits; - ecode = tdb_lock_hashes(tdb, hl_start, hl_range, F_RDLCK, - TDB_LOCK_WAIT); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - off = iterate_hash(tdb, tinfo); - if (off) { - struct tdb_used_record rec; - - if (TDB_OFF_IS_ERR(off)) { - ecode = TDB_OFF_TO_ERR(off); - goto fail; - } - - ecode = tdb_read_convert(tdb, off, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - goto fail; - } - if (rec_magic(&rec) != TDB_USED_MAGIC) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "next_in_hash:" - " corrupt record at %llu", - (long long)off); - goto fail; - } - - kbuf->dsize = rec_key_length(&rec); - - /* They want data as well? */ - if (dlen) { - *dlen = rec_data_length(&rec); - kbuf->dptr = tdb_alloc_read(tdb, - off + sizeof(rec), - kbuf->dsize - + *dlen); - } else { - kbuf->dptr = tdb_alloc_read(tdb, - off + sizeof(rec), - kbuf->dsize); - } - tdb_unlock_hashes(tdb, hl_start, hl_range, F_RDLCK); - if (TDB_PTR_IS_ERR(kbuf->dptr)) { - return TDB_PTR_ERR(kbuf->dptr); - } - return TDB_SUCCESS; - } - - tdb_unlock_hashes(tdb, hl_start, hl_range, F_RDLCK); - - tinfo->toplevel_group++; - tinfo->levels[0].hashtable - += (sizeof(tdb_off_t) << TDB_HASH_GROUP_BITS); - tinfo->levels[0].entry = 0; - } - return TDB_ERR_NOEXIST; - -fail: - tdb_unlock_hashes(tdb, hl_start, hl_range, F_RDLCK); - return ecode; - -} - -enum TDB_ERROR first_in_hash(struct tdb_context *tdb, - struct traverse_info *tinfo, - TDB_DATA *kbuf, size_t *dlen) -{ - tinfo->prev = 0; - tinfo->toplevel_group = 0; - tinfo->num_levels = 1; - tinfo->levels[0].hashtable = offsetof(struct tdb_header, hashtable); - tinfo->levels[0].entry = 0; - tinfo->levels[0].total_buckets = (1 << TDB_HASH_GROUP_BITS); - - return next_in_hash(tdb, tinfo, kbuf, dlen); -} - -/* Even if the entry isn't in this hash bucket, you'd have to lock this - * bucket to find it. */ -static enum TDB_ERROR chainlock(struct tdb_context *tdb, const TDB_DATA *key, - int ltype, enum tdb_lock_flags waitflag, - const char *func) -{ - enum TDB_ERROR ecode; - uint64_t h = tdb_hash(tdb, key->dptr, key->dsize); - tdb_off_t lockstart, locksize; - unsigned int group, gbits; - - gbits = TDB_TOPLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS; - group = bits_from(h, 64 - gbits, gbits); - - lockstart = hlock_range(group, &locksize); - - ecode = tdb_lock_hashes(tdb, lockstart, locksize, ltype, waitflag); - tdb_trace_1rec(tdb, func, *key); - return ecode; -} - -/* lock/unlock one hash chain. This is meant to be used to reduce - contention - it cannot guarantee how many records will be locked */ -enum TDB_ERROR tdb_chainlock(struct tdb_context *tdb, TDB_DATA key) -{ - if (tdb->flags & TDB_VERSION1) { - if (tdb1_chainlock(tdb, key) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - return tdb->last_error = chainlock(tdb, &key, F_WRLCK, TDB_LOCK_WAIT, - "tdb_chainlock"); -} - -void tdb_chainunlock(struct tdb_context *tdb, TDB_DATA key) -{ - uint64_t h = tdb_hash(tdb, key.dptr, key.dsize); - tdb_off_t lockstart, locksize; - unsigned int group, gbits; - - if (tdb->flags & TDB_VERSION1) { - tdb1_chainunlock(tdb, key); - return; - } - - gbits = TDB_TOPLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS; - group = bits_from(h, 64 - gbits, gbits); - - lockstart = hlock_range(group, &locksize); - - tdb_trace_1rec(tdb, "tdb_chainunlock", key); - tdb_unlock_hashes(tdb, lockstart, locksize, F_WRLCK); -} - -enum TDB_ERROR tdb_chainlock_read(struct tdb_context *tdb, TDB_DATA key) -{ - if (tdb->flags & TDB_VERSION1) { - if (tdb1_chainlock_read(tdb, key) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - return tdb->last_error = chainlock(tdb, &key, F_RDLCK, TDB_LOCK_WAIT, - "tdb_chainlock_read"); -} - -void tdb_chainunlock_read(struct tdb_context *tdb, TDB_DATA key) -{ - uint64_t h = tdb_hash(tdb, key.dptr, key.dsize); - tdb_off_t lockstart, locksize; - unsigned int group, gbits; - - if (tdb->flags & TDB_VERSION1) { - tdb1_chainunlock_read(tdb, key); - return; - } - gbits = TDB_TOPLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS; - group = bits_from(h, 64 - gbits, gbits); - - lockstart = hlock_range(group, &locksize); - - tdb_trace_1rec(tdb, "tdb_chainunlock_read", key); - tdb_unlock_hashes(tdb, lockstart, locksize, F_RDLCK); -} diff --git a/ccan/tdb2/io.c b/ccan/tdb2/io.c deleted file mode 100644 index b4a6f0be..00000000 --- a/ccan/tdb2/io.c +++ /dev/null @@ -1,640 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 1999-2005 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000-2003 - Copyright (C) Rusty Russell 2010 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "private.h" -#include -#include - -void tdb_munmap(struct tdb_file *file) -{ - if (file->fd == -1) - return; - - if (file->map_ptr) { - munmap(file->map_ptr, file->map_size); - file->map_ptr = NULL; - } -} - -void tdb_mmap(struct tdb_context *tdb) -{ - int mmap_flags; - - if (tdb->flags & TDB_INTERNAL) - return; - - if (tdb->flags & TDB_NOMMAP) - return; - - if ((tdb->open_flags & O_ACCMODE) == O_RDONLY) - mmap_flags = PROT_READ; - else - mmap_flags = PROT_READ | PROT_WRITE; - - /* size_t can be smaller than off_t. */ - if ((size_t)tdb->file->map_size == tdb->file->map_size) { - tdb->file->map_ptr = mmap(NULL, tdb->file->map_size, - mmap_flags, - MAP_SHARED, tdb->file->fd, 0); - } else - tdb->file->map_ptr = MAP_FAILED; - - /* - * NB. When mmap fails it returns MAP_FAILED *NOT* NULL !!!! - */ - if (tdb->file->map_ptr == MAP_FAILED) { - tdb->file->map_ptr = NULL; - tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING, - "tdb_mmap failed for size %lld (%s)", - (long long)tdb->file->map_size, strerror(errno)); - } -} - -/* check for an out of bounds access - if it is out of bounds then - see if the database has been expanded by someone else and expand - if necessary - note that "len" is the minimum length needed for the db. - - If probe is true, len being too large isn't a failure. -*/ -static enum TDB_ERROR tdb_oob(struct tdb_context *tdb, - tdb_off_t off, tdb_len_t len, bool probe) -{ - struct stat st; - enum TDB_ERROR ecode; - - /* We can't hold pointers during this: we could unmap! */ - assert(!tdb->tdb2.direct_access - || (tdb->flags & TDB_NOLOCK) - || tdb_has_expansion_lock(tdb)); - - if (len + off < len) { - if (probe) - return TDB_SUCCESS; - - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_oob off %llu len %llu wrap\n", - (long long)off, (long long)len); - } - - if (len + off <= tdb->file->map_size) - return TDB_SUCCESS; - if (tdb->flags & TDB_INTERNAL) { - if (probe) - return TDB_SUCCESS; - - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_oob len %lld beyond internal" - " malloc size %lld", - (long long)(off + len), - (long long)tdb->file->map_size); - return TDB_ERR_IO; - } - - ecode = tdb_lock_expand(tdb, F_RDLCK); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - if (fstat(tdb->file->fd, &st) != 0) { - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "Failed to fstat file: %s", strerror(errno)); - tdb_unlock_expand(tdb, F_RDLCK); - return TDB_ERR_IO; - } - - tdb_unlock_expand(tdb, F_RDLCK); - - if (st.st_size < off + len) { - if (probe) - return TDB_SUCCESS; - - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_oob len %llu beyond eof at %zu", - (long long)(off + len), st.st_size); - return TDB_ERR_IO; - } - - /* Unmap, update size, remap */ - tdb_munmap(tdb->file); - - tdb->file->map_size = st.st_size; - tdb_mmap(tdb); - return TDB_SUCCESS; -} - -/* Endian conversion: we only ever deal with 8 byte quantities */ -void *tdb_convert(const struct tdb_context *tdb, void *buf, tdb_len_t size) -{ - assert(size % 8 == 0); - if (unlikely((tdb->flags & TDB_CONVERT)) && buf) { - uint64_t i, *p = (uint64_t *)buf; - for (i = 0; i < size / 8; i++) - p[i] = bswap_64(p[i]); - } - return buf; -} - -/* Return first non-zero offset in offset array, or end, or -ve error. */ -/* FIXME: Return the off? */ -uint64_t tdb_find_nonzero_off(struct tdb_context *tdb, - tdb_off_t base, uint64_t start, uint64_t end) -{ - uint64_t i; - const uint64_t *val; - - /* Zero vs non-zero is the same unconverted: minor optimization. */ - val = tdb_access_read(tdb, base + start * sizeof(tdb_off_t), - (end - start) * sizeof(tdb_off_t), false); - if (TDB_PTR_IS_ERR(val)) { - return TDB_ERR_TO_OFF(TDB_PTR_ERR(val)); - } - - for (i = 0; i < (end - start); i++) { - if (val[i]) - break; - } - tdb_access_release(tdb, val); - return start + i; -} - -/* Return first zero offset in num offset array, or num, or -ve error. */ -uint64_t tdb_find_zero_off(struct tdb_context *tdb, tdb_off_t off, - uint64_t num) -{ - uint64_t i; - const uint64_t *val; - - /* Zero vs non-zero is the same unconverted: minor optimization. */ - val = tdb_access_read(tdb, off, num * sizeof(tdb_off_t), false); - if (TDB_PTR_IS_ERR(val)) { - return TDB_ERR_TO_OFF(TDB_PTR_ERR(val)); - } - - for (i = 0; i < num; i++) { - if (!val[i]) - break; - } - tdb_access_release(tdb, val); - return i; -} - -enum TDB_ERROR zero_out(struct tdb_context *tdb, tdb_off_t off, tdb_len_t len) -{ - char buf[8192] = { 0 }; - void *p = tdb->tdb2.io->direct(tdb, off, len, true); - enum TDB_ERROR ecode = TDB_SUCCESS; - - assert(!(tdb->flags & TDB_RDONLY)); - if (TDB_PTR_IS_ERR(p)) { - return TDB_PTR_ERR(p); - } - if (p) { - memset(p, 0, len); - return ecode; - } - while (len) { - unsigned todo = len < sizeof(buf) ? len : sizeof(buf); - ecode = tdb->tdb2.io->twrite(tdb, off, buf, todo); - if (ecode != TDB_SUCCESS) { - break; - } - len -= todo; - off += todo; - } - return ecode; -} - -tdb_off_t tdb_read_off(struct tdb_context *tdb, tdb_off_t off) -{ - tdb_off_t ret; - enum TDB_ERROR ecode; - - if (likely(!(tdb->flags & TDB_CONVERT))) { - tdb_off_t *p = tdb->tdb2.io->direct(tdb, off, sizeof(*p), - false); - if (TDB_PTR_IS_ERR(p)) { - return TDB_ERR_TO_OFF(TDB_PTR_ERR(p)); - } - if (p) - return *p; - } - - ecode = tdb_read_convert(tdb, off, &ret, sizeof(ret)); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - return ret; -} - -/* write a lump of data at a specified offset */ -static enum TDB_ERROR tdb_write(struct tdb_context *tdb, tdb_off_t off, - const void *buf, tdb_len_t len) -{ - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_RDONLY) { - return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR, - "Write to read-only database"); - } - - ecode = tdb->tdb2.io->oob(tdb, off, len, false); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - if (tdb->file->map_ptr) { - memcpy(off + (char *)tdb->file->map_ptr, buf, len); - } else { - ssize_t ret; - ret = pwrite(tdb->file->fd, buf, len, off); - if (ret != len) { - /* This shouldn't happen: we avoid sparse files. */ - if (ret >= 0) - errno = ENOSPC; - - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_write: %zi at %zu len=%zu (%s)", - ret, (size_t)off, (size_t)len, - strerror(errno)); - } - } - return TDB_SUCCESS; -} - -/* read a lump of data at a specified offset */ -static enum TDB_ERROR tdb_read(struct tdb_context *tdb, tdb_off_t off, - void *buf, tdb_len_t len) -{ - enum TDB_ERROR ecode; - - ecode = tdb->tdb2.io->oob(tdb, off, len, false); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - if (tdb->file->map_ptr) { - memcpy(buf, off + (char *)tdb->file->map_ptr, len); - } else { - ssize_t r = pread(tdb->file->fd, buf, len, off); - if (r != len) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_read failed with %zi at %zu " - "len=%zu (%s) map_size=%zu", - r, (size_t)off, (size_t)len, - strerror(errno), - (size_t)tdb->file->map_size); - } - } - return TDB_SUCCESS; -} - -enum TDB_ERROR tdb_write_convert(struct tdb_context *tdb, tdb_off_t off, - const void *rec, size_t len) -{ - enum TDB_ERROR ecode; - - if (unlikely((tdb->flags & TDB_CONVERT))) { - void *conv = malloc(len); - if (!conv) { - return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_write: no memory converting" - " %zu bytes", len); - } - memcpy(conv, rec, len); - ecode = tdb->tdb2.io->twrite(tdb, off, - tdb_convert(tdb, conv, len), len); - free(conv); - } else { - ecode = tdb->tdb2.io->twrite(tdb, off, rec, len); - } - return ecode; -} - -enum TDB_ERROR tdb_read_convert(struct tdb_context *tdb, tdb_off_t off, - void *rec, size_t len) -{ - enum TDB_ERROR ecode = tdb->tdb2.io->tread(tdb, off, rec, len); - tdb_convert(tdb, rec, len); - return ecode; -} - -enum TDB_ERROR tdb_write_off(struct tdb_context *tdb, - tdb_off_t off, tdb_off_t val) -{ - if (tdb->flags & TDB_RDONLY) { - return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR, - "Write to read-only database"); - } - - if (likely(!(tdb->flags & TDB_CONVERT))) { - tdb_off_t *p = tdb->tdb2.io->direct(tdb, off, sizeof(*p), - true); - if (TDB_PTR_IS_ERR(p)) { - return TDB_PTR_ERR(p); - } - if (p) { - *p = val; - return TDB_SUCCESS; - } - } - return tdb_write_convert(tdb, off, &val, sizeof(val)); -} - -static void *_tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset, - tdb_len_t len, unsigned int prefix) -{ - unsigned char *buf; - enum TDB_ERROR ecode; - - /* some systems don't like zero length malloc */ - buf = malloc(prefix + len ? prefix + len : 1); - if (!buf) { - tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_USE_ERROR, - "tdb_alloc_read malloc failed len=%zu", - (size_t)(prefix + len)); - return TDB_ERR_PTR(TDB_ERR_OOM); - } else { - ecode = tdb->tdb2.io->tread(tdb, offset, buf+prefix, len); - if (unlikely(ecode != TDB_SUCCESS)) { - free(buf); - return TDB_ERR_PTR(ecode); - } - } - return buf; -} - -/* read a lump of data, allocating the space for it */ -void *tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t len) -{ - return _tdb_alloc_read(tdb, offset, len, 0); -} - -static enum TDB_ERROR fill(struct tdb_context *tdb, - const void *buf, size_t size, - tdb_off_t off, tdb_len_t len) -{ - while (len) { - size_t n = len > size ? size : len; - ssize_t ret = pwrite(tdb->file->fd, buf, n, off); - if (ret != n) { - if (ret >= 0) - errno = ENOSPC; - - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "fill failed:" - " %zi at %zu len=%zu (%s)", - ret, (size_t)off, (size_t)len, - strerror(errno)); - } - len -= n; - off += n; - } - return TDB_SUCCESS; -} - -/* expand a file. we prefer to use ftruncate, as that is what posix - says to use for mmap expansion */ -static enum TDB_ERROR tdb_expand_file(struct tdb_context *tdb, - tdb_len_t addition) -{ - char buf[8192]; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_RDONLY) { - return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR, - "Expand on read-only database"); - } - - if (tdb->flags & TDB_INTERNAL) { - char *new = realloc(tdb->file->map_ptr, - tdb->file->map_size + addition); - if (!new) { - return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "No memory to expand database"); - } - tdb->file->map_ptr = new; - tdb->file->map_size += addition; - } else { - /* Unmap before trying to write; old TDB claimed OpenBSD had - * problem with this otherwise. */ - tdb_munmap(tdb->file); - - /* If this fails, we try to fill anyway. */ - if (ftruncate(tdb->file->fd, tdb->file->map_size + addition)) - ; - - /* now fill the file with something. This ensures that the - file isn't sparse, which would be very bad if we ran out of - disk. This must be done with write, not via mmap */ - memset(buf, 0x43, sizeof(buf)); - ecode = fill(tdb, buf, sizeof(buf), tdb->file->map_size, - addition); - if (ecode != TDB_SUCCESS) - return ecode; - tdb->file->map_size += addition; - tdb_mmap(tdb); - } - return TDB_SUCCESS; -} - -const void *tdb_access_read(struct tdb_context *tdb, - tdb_off_t off, tdb_len_t len, bool convert) -{ - void *ret = NULL; - - if (likely(!(tdb->flags & TDB_CONVERT))) { - ret = tdb->tdb2.io->direct(tdb, off, len, false); - - if (TDB_PTR_IS_ERR(ret)) { - return ret; - } - } - if (!ret) { - struct tdb_access_hdr *hdr; - hdr = _tdb_alloc_read(tdb, off, len, sizeof(*hdr)); - if (TDB_PTR_IS_ERR(hdr)) { - return hdr; - } - hdr->next = tdb->tdb2.access; - tdb->tdb2.access = hdr; - ret = hdr + 1; - if (convert) { - tdb_convert(tdb, (void *)ret, len); - } - } else - tdb->tdb2.direct_access++; - - return ret; -} - -void *tdb_access_write(struct tdb_context *tdb, - tdb_off_t off, tdb_len_t len, bool convert) -{ - void *ret = NULL; - - if (tdb->flags & TDB_RDONLY) { - tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR, - "Write to read-only database"); - return TDB_ERR_PTR(TDB_ERR_RDONLY); - } - - if (likely(!(tdb->flags & TDB_CONVERT))) { - ret = tdb->tdb2.io->direct(tdb, off, len, true); - - if (TDB_PTR_IS_ERR(ret)) { - return ret; - } - } - - if (!ret) { - struct tdb_access_hdr *hdr; - hdr = _tdb_alloc_read(tdb, off, len, sizeof(*hdr)); - if (TDB_PTR_IS_ERR(hdr)) { - return hdr; - } - hdr->next = tdb->tdb2.access; - tdb->tdb2.access = hdr; - hdr->off = off; - hdr->len = len; - hdr->convert = convert; - ret = hdr + 1; - if (convert) - tdb_convert(tdb, (void *)ret, len); - } else - tdb->tdb2.direct_access++; - - return ret; -} - -static struct tdb_access_hdr **find_hdr(struct tdb_context *tdb, const void *p) -{ - struct tdb_access_hdr **hp; - - for (hp = &tdb->tdb2.access; *hp; hp = &(*hp)->next) { - if (*hp + 1 == p) - return hp; - } - return NULL; -} - -void tdb_access_release(struct tdb_context *tdb, const void *p) -{ - struct tdb_access_hdr *hdr, **hp = find_hdr(tdb, p); - - if (hp) { - hdr = *hp; - *hp = hdr->next; - free(hdr); - } else - tdb->tdb2.direct_access--; -} - -enum TDB_ERROR tdb_access_commit(struct tdb_context *tdb, void *p) -{ - struct tdb_access_hdr *hdr, **hp = find_hdr(tdb, p); - enum TDB_ERROR ecode; - - if (hp) { - hdr = *hp; - if (hdr->convert) - ecode = tdb_write_convert(tdb, hdr->off, p, hdr->len); - else - ecode = tdb_write(tdb, hdr->off, p, hdr->len); - *hp = hdr->next; - free(hdr); - } else { - tdb->tdb2.direct_access--; - ecode = TDB_SUCCESS; - } - - return ecode; -} - -static void *tdb_direct(struct tdb_context *tdb, tdb_off_t off, size_t len, - bool write_mode) -{ - enum TDB_ERROR ecode; - - if (unlikely(!tdb->file->map_ptr)) - return NULL; - - ecode = tdb_oob(tdb, off, len, false); - if (unlikely(ecode != TDB_SUCCESS)) - return TDB_ERR_PTR(ecode); - return (char *)tdb->file->map_ptr + off; -} - -void tdb_inc_seqnum(struct tdb_context *tdb) -{ - tdb_off_t seq; - - if (tdb->flags & TDB_VERSION1) { - tdb1_increment_seqnum_nonblock(tdb); - return; - } - - if (likely(!(tdb->flags & TDB_CONVERT))) { - int64_t *direct; - - direct = tdb->tdb2.io->direct(tdb, - offsetof(struct tdb_header, - seqnum), - sizeof(*direct), true); - if (likely(direct)) { - /* Don't let it go negative, even briefly */ - if (unlikely((*direct) + 1) < 0) - *direct = 0; - (*direct)++; - return; - } - } - - seq = tdb_read_off(tdb, offsetof(struct tdb_header, seqnum)); - if (!TDB_OFF_IS_ERR(seq)) { - seq++; - if (unlikely((int64_t)seq < 0)) - seq = 0; - tdb_write_off(tdb, offsetof(struct tdb_header, seqnum), seq); - } -} - -static const struct tdb_methods io_methods = { - tdb_read, - tdb_write, - tdb_oob, - tdb_expand_file, - tdb_direct, -}; - -/* - initialise the default methods table -*/ -void tdb_io_init(struct tdb_context *tdb) -{ - tdb->tdb2.io = &io_methods; -} diff --git a/ccan/tdb2/lock.c b/ccan/tdb2/lock.c deleted file mode 100644 index a71c95f6..00000000 --- a/ccan/tdb2/lock.c +++ /dev/null @@ -1,895 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 1999-2005 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000-2003 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#include "private.h" -#include -#include - -/* If we were threaded, we could wait for unlock, but we're not, so fail. */ -enum TDB_ERROR owner_conflict(struct tdb_context *tdb, const char *call) -{ - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "%s: lock owned by another tdb in this process.", - call); -} - -/* If we fork, we no longer really own locks. */ -bool check_lock_pid(struct tdb_context *tdb, const char *call, bool log) -{ - /* No locks? No problem! */ - if (tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0) { - return true; - } - - /* No fork? No problem! */ - if (tdb->file->locker == getpid()) { - return true; - } - - if (log) { - tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "%s: fork() detected after lock acquisition!" - " (%u vs %u)", call, tdb->file->locker, getpid()); - } - return false; -} - -int tdb_fcntl_lock(int fd, int rw, off_t off, off_t len, bool waitflag, - void *unused) -{ - struct flock fl; - int ret; - - do { - fl.l_type = rw; - fl.l_whence = SEEK_SET; - fl.l_start = off; - fl.l_len = len; - - if (waitflag) - ret = fcntl(fd, F_SETLKW, &fl); - else - ret = fcntl(fd, F_SETLK, &fl); - } while (ret != 0 && errno == EINTR); - return ret; -} - -int tdb_fcntl_unlock(int fd, int rw, off_t off, off_t len, void *unused) -{ - struct flock fl; - int ret; - - do { - fl.l_type = F_UNLCK; - fl.l_whence = SEEK_SET; - fl.l_start = off; - fl.l_len = len; - - ret = fcntl(fd, F_SETLKW, &fl); - } while (ret != 0 && errno == EINTR); - return ret; -} - -static int lock(struct tdb_context *tdb, - int rw, off_t off, off_t len, bool waitflag) -{ - int ret; - if (tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0) { - tdb->file->locker = getpid(); - } - - tdb->stats.lock_lowlevel++; - ret = tdb->lock_fn(tdb->file->fd, rw, off, len, waitflag, - tdb->lock_data); - if (!waitflag) { - tdb->stats.lock_nonblock++; - if (ret != 0) - tdb->stats.lock_nonblock_fail++; - } - return ret; -} - -static int unlock(struct tdb_context *tdb, int rw, off_t off, off_t len) -{ -#if 0 /* Check they matched up locks and unlocks correctly. */ - char line[80]; - FILE *locks; - bool found = false; - - locks = fopen("/proc/locks", "r"); - - while (fgets(line, 80, locks)) { - char *p; - int type, start, l; - - /* eg. 1: FLOCK ADVISORY WRITE 2440 08:01:2180826 0 EOF */ - p = strchr(line, ':') + 1; - if (strncmp(p, " POSIX ADVISORY ", strlen(" POSIX ADVISORY "))) - continue; - p += strlen(" FLOCK ADVISORY "); - if (strncmp(p, "READ ", strlen("READ ")) == 0) - type = F_RDLCK; - else if (strncmp(p, "WRITE ", strlen("WRITE ")) == 0) - type = F_WRLCK; - else - abort(); - p += 6; - if (atoi(p) != getpid()) - continue; - p = strchr(strchr(p, ' ') + 1, ' ') + 1; - start = atoi(p); - p = strchr(p, ' ') + 1; - if (strncmp(p, "EOF", 3) == 0) - l = 0; - else - l = atoi(p) - start + 1; - - if (off == start) { - if (len != l) { - fprintf(stderr, "Len %u should be %u: %s", - (int)len, l, line); - abort(); - } - if (type != rw) { - fprintf(stderr, "Type %s wrong: %s", - rw == F_RDLCK ? "READ" : "WRITE", line); - abort(); - } - found = true; - break; - } - } - - if (!found) { - fprintf(stderr, "Unlock on %u@%u not found!", - (int)off, (int)len); - abort(); - } - - fclose(locks); -#endif - - return tdb->unlock_fn(tdb->file->fd, rw, off, len, tdb->lock_data); -} - -/* a byte range locking function - return 0 on success - this functions locks len bytes at the specified offset. - - note that a len of zero means lock to end of file -*/ -enum TDB_ERROR tdb_brlock(struct tdb_context *tdb, - int rw_type, tdb_off_t offset, tdb_off_t len, - enum tdb_lock_flags flags) -{ - int ret; - - if (tdb->flags & TDB_NOLOCK) { - return TDB_SUCCESS; - } - - if (rw_type == F_WRLCK && (tdb->flags & TDB_RDONLY)) { - return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_USE_ERROR, - "Write lock attempted on read-only database"); - } - - /* A 32 bit system cannot open a 64-bit file, but it could have - * expanded since then: check here. */ - if ((size_t)(offset + len) != offset + len) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_brlock: lock on giant offset %llu", - (long long)(offset + len)); - } - - ret = lock(tdb, rw_type, offset, len, flags & TDB_LOCK_WAIT); - if (ret != 0) { - /* Generic lock error. errno set by fcntl. - * EAGAIN is an expected return from non-blocking - * locks. */ - if (!(flags & TDB_LOCK_PROBE) - && (errno != EAGAIN && errno != EINTR)) { - tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_brlock failed (fd=%d) at" - " offset %zu rw_type=%d flags=%d len=%zu:" - " %s", - tdb->file->fd, (size_t)offset, rw_type, - flags, (size_t)len, strerror(errno)); - } - return TDB_ERR_LOCK; - } - return TDB_SUCCESS; -} - -enum TDB_ERROR tdb_brunlock(struct tdb_context *tdb, - int rw_type, tdb_off_t offset, size_t len) -{ - if (tdb->flags & TDB_NOLOCK) { - return TDB_SUCCESS; - } - - if (!check_lock_pid(tdb, "tdb_brunlock", true)) - return TDB_ERR_LOCK; - - if (unlock(tdb, rw_type, offset, len) == -1) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_brunlock failed (fd=%d) at offset %zu" - " rw_type=%d len=%zu: %s", - tdb->file->fd, (size_t)offset, rw_type, - (size_t)len, strerror(errno)); - } - return TDB_SUCCESS; -} - -/* - upgrade a read lock to a write lock. This needs to be handled in a - special way as some OSes (such as solaris) have too conservative - deadlock detection and claim a deadlock when progress can be - made. For those OSes we may loop for a while. -*/ -enum TDB_ERROR tdb_allrecord_upgrade(struct tdb_context *tdb, off_t start) -{ - int count = 1000; - - if (!check_lock_pid(tdb, "tdb_transaction_prepare_commit", true)) - return TDB_ERR_LOCK; - - if (tdb->file->allrecord_lock.count != 1) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_allrecord_upgrade failed:" - " count %u too high", - tdb->file->allrecord_lock.count); - } - - if (tdb->file->allrecord_lock.off != 1) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_allrecord_upgrade failed:" - " already upgraded?"); - } - - if (tdb->file->allrecord_lock.owner != tdb) { - return owner_conflict(tdb, "tdb_allrecord_upgrade"); - } - - while (count--) { - struct timeval tv; - if (tdb_brlock(tdb, F_WRLCK, start, 0, - TDB_LOCK_WAIT|TDB_LOCK_PROBE) == TDB_SUCCESS) { - tdb->file->allrecord_lock.ltype = F_WRLCK; - tdb->file->allrecord_lock.off = 0; - return TDB_SUCCESS; - } - if (errno != EDEADLK) { - break; - } - /* sleep for as short a time as we can - more portable than usleep() */ - tv.tv_sec = 0; - tv.tv_usec = 1; - select(0, NULL, NULL, NULL, &tv); - } - - if (errno != EAGAIN && errno != EINTR) - tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_allrecord_upgrade failed"); - return TDB_ERR_LOCK; -} - -static struct tdb_lock *find_nestlock(struct tdb_context *tdb, tdb_off_t offset, - const struct tdb_context *owner) -{ - unsigned int i; - - for (i=0; ifile->num_lockrecs; i++) { - if (tdb->file->lockrecs[i].off == offset) { - if (owner && tdb->file->lockrecs[i].owner != owner) - return NULL; - return &tdb->file->lockrecs[i]; - } - } - return NULL; -} - -enum TDB_ERROR tdb_lock_and_recover(struct tdb_context *tdb) -{ - enum TDB_ERROR ecode; - - if (!check_lock_pid(tdb, "tdb_transaction_prepare_commit", true)) - return TDB_ERR_LOCK; - - ecode = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT|TDB_LOCK_NOCHECK, - false); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - ecode = tdb_lock_open(tdb, F_WRLCK, TDB_LOCK_WAIT|TDB_LOCK_NOCHECK); - if (ecode != TDB_SUCCESS) { - tdb_allrecord_unlock(tdb, F_WRLCK); - return ecode; - } - ecode = tdb_transaction_recover(tdb); - tdb_unlock_open(tdb, F_WRLCK); - tdb_allrecord_unlock(tdb, F_WRLCK); - - return ecode; -} - -/* lock an offset in the database. */ -enum TDB_ERROR tdb_nest_lock(struct tdb_context *tdb, - tdb_off_t offset, int ltype, - enum tdb_lock_flags flags) -{ - struct tdb_lock *new_lck; - enum TDB_ERROR ecode; - - if (!(tdb->flags & TDB_VERSION1) - && offset > (TDB_HASH_LOCK_START + TDB_HASH_LOCK_RANGE - + tdb->file->map_size / 8)) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_nest_lock: invalid offset %zu ltype=%d", - (size_t)offset, ltype); - } - - if (tdb->flags & TDB_NOLOCK) - return TDB_SUCCESS; - - if (!check_lock_pid(tdb, "tdb_nest_lock", true)) { - return TDB_ERR_LOCK; - } - - tdb->stats.locks++; - - new_lck = find_nestlock(tdb, offset, NULL); - if (new_lck) { - if (new_lck->owner != tdb) { - return owner_conflict(tdb, "tdb_nest_lock"); - } - - if (new_lck->ltype == F_RDLCK && ltype == F_WRLCK) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_nest_lock:" - " offset %zu has read lock", - (size_t)offset); - } - /* Just increment the struct, posix locks don't stack. */ - new_lck->count++; - return TDB_SUCCESS; - } - -#if 0 - if (tdb->file->num_lockrecs - && offset >= TDB_HASH_LOCK_START - && offset < TDB_HASH_LOCK_START + TDB_HASH_LOCK_RANGE) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_nest_lock: already have a hash lock?"); - } -#endif - - new_lck = (struct tdb_lock *)realloc( - tdb->file->lockrecs, - sizeof(*tdb->file->lockrecs) * (tdb->file->num_lockrecs+1)); - if (new_lck == NULL) { - return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_nest_lock:" - " unable to allocate %zu lock struct", - tdb->file->num_lockrecs + 1); - } - tdb->file->lockrecs = new_lck; - - /* Since fcntl locks don't nest, we do a lock for the first one, - and simply bump the count for future ones */ - ecode = tdb_brlock(tdb, ltype, offset, 1, flags); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* First time we grab a lock, perhaps someone died in commit? */ - if (!(flags & TDB_LOCK_NOCHECK) - && tdb->file->num_lockrecs == 0) { - tdb_bool_err berr = tdb_needs_recovery(tdb); - if (berr != false) { - tdb_brunlock(tdb, ltype, offset, 1); - - if (berr < 0) - return TDB_OFF_TO_ERR(berr); - ecode = tdb_lock_and_recover(tdb); - if (ecode == TDB_SUCCESS) { - ecode = tdb_brlock(tdb, ltype, offset, 1, - flags); - } - if (ecode != TDB_SUCCESS) { - return ecode; - } - } - } - - tdb->file->lockrecs[tdb->file->num_lockrecs].owner = tdb; - tdb->file->lockrecs[tdb->file->num_lockrecs].off = offset; - tdb->file->lockrecs[tdb->file->num_lockrecs].count = 1; - tdb->file->lockrecs[tdb->file->num_lockrecs].ltype = ltype; - tdb->file->num_lockrecs++; - - return TDB_SUCCESS; -} - -enum TDB_ERROR tdb_nest_unlock(struct tdb_context *tdb, - tdb_off_t off, int ltype) -{ - struct tdb_lock *lck; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_NOLOCK) - return TDB_SUCCESS; - - lck = find_nestlock(tdb, off, tdb); - if ((lck == NULL) || (lck->count == 0)) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_nest_unlock: no lock for %zu", - (size_t)off); - } - - if (lck->count > 1) { - lck->count--; - return TDB_SUCCESS; - } - - /* - * This lock has count==1 left, so we need to unlock it in the - * kernel. We don't bother with decrementing the in-memory array - * element, we're about to overwrite it with the last array element - * anyway. - */ - ecode = tdb_brunlock(tdb, ltype, off, 1); - - /* - * Shrink the array by overwriting the element just unlocked with the - * last array element. - */ - *lck = tdb->file->lockrecs[--tdb->file->num_lockrecs]; - - return ecode; -} - -/* - get the transaction lock - */ -enum TDB_ERROR tdb_transaction_lock(struct tdb_context *tdb, int ltype) -{ - return tdb_nest_lock(tdb, TDB_TRANSACTION_LOCK, ltype, TDB_LOCK_WAIT); -} - -/* - release the transaction lock - */ -void tdb_transaction_unlock(struct tdb_context *tdb, int ltype) -{ - tdb_nest_unlock(tdb, TDB_TRANSACTION_LOCK, ltype); -} - -/* We only need to lock individual bytes, but Linux merges consecutive locks - * so we lock in contiguous ranges. */ -enum TDB_ERROR tdb_lock_gradual(struct tdb_context *tdb, - int ltype, enum tdb_lock_flags flags, - tdb_off_t off, tdb_off_t len) -{ - enum TDB_ERROR ecode; - enum tdb_lock_flags nb_flags = (flags & ~TDB_LOCK_WAIT); - - if (len <= 1) { - /* 0 would mean to end-of-file... */ - assert(len != 0); - /* Single hash. Just do blocking lock. */ - return tdb_brlock(tdb, ltype, off, len, flags); - } - - /* First we try non-blocking. */ - ecode = tdb_brlock(tdb, ltype, off, len, nb_flags); - if (ecode != TDB_ERR_LOCK) { - return ecode; - } - - /* Try locking first half, then second. */ - ecode = tdb_lock_gradual(tdb, ltype, flags, off, len / 2); - if (ecode != TDB_SUCCESS) - return ecode; - - ecode = tdb_lock_gradual(tdb, ltype, flags, - off + len / 2, len - len / 2); - if (ecode != TDB_SUCCESS) { - tdb_brunlock(tdb, ltype, off, len / 2); - } - return ecode; -} - -/* lock/unlock entire database. It can only be upgradable if you have some - * other way of guaranteeing exclusivity (ie. transaction write lock). */ -enum TDB_ERROR tdb_allrecord_lock(struct tdb_context *tdb, int ltype, - enum tdb_lock_flags flags, bool upgradable) -{ - enum TDB_ERROR ecode; - tdb_bool_err berr; - - if (tdb->flags & TDB_VERSION1) { - if (tdb1_allrecord_lock(tdb, ltype, flags, upgradable) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - - if (tdb->flags & TDB_NOLOCK) - return TDB_SUCCESS; - - if (!check_lock_pid(tdb, "tdb_allrecord_lock", true)) { - return TDB_ERR_LOCK; - } - - if (tdb->file->allrecord_lock.count) { - if (tdb->file->allrecord_lock.owner != tdb) { - return owner_conflict(tdb, "tdb_allrecord_lock"); - } - - if (ltype == F_RDLCK - || tdb->file->allrecord_lock.ltype == F_WRLCK) { - tdb->file->allrecord_lock.count++; - return TDB_SUCCESS; - } - - /* a global lock of a different type exists */ - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb_allrecord_lock: already have %s lock", - tdb->file->allrecord_lock.ltype == F_RDLCK - ? "read" : "write"); - } - - if (tdb_has_hash_locks(tdb)) { - /* can't combine global and chain locks */ - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb_allrecord_lock:" - " already have chain lock"); - } - - if (upgradable && ltype != F_RDLCK) { - /* tdb error: you can't upgrade a write lock! */ - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_allrecord_lock:" - " can't upgrade a write lock"); - } - - tdb->stats.locks++; -again: - /* Lock hashes, gradually. */ - ecode = tdb_lock_gradual(tdb, ltype, flags, TDB_HASH_LOCK_START, - TDB_HASH_LOCK_RANGE); - if (ecode != TDB_SUCCESS) - return ecode; - - /* Lock free tables: there to end of file. */ - ecode = tdb_brlock(tdb, ltype, - TDB_HASH_LOCK_START + TDB_HASH_LOCK_RANGE, - 0, flags); - if (ecode != TDB_SUCCESS) { - tdb_brunlock(tdb, ltype, TDB_HASH_LOCK_START, - TDB_HASH_LOCK_RANGE); - return ecode; - } - - tdb->file->allrecord_lock.owner = tdb; - tdb->file->allrecord_lock.count = 1; - /* If it's upgradable, it's actually exclusive so we can treat - * it as a write lock. */ - tdb->file->allrecord_lock.ltype = upgradable ? F_WRLCK : ltype; - tdb->file->allrecord_lock.off = upgradable; - - /* Now check for needing recovery. */ - if (flags & TDB_LOCK_NOCHECK) - return TDB_SUCCESS; - - berr = tdb_needs_recovery(tdb); - if (likely(berr == false)) - return TDB_SUCCESS; - - tdb_allrecord_unlock(tdb, ltype); - if (berr < 0) - return TDB_OFF_TO_ERR(berr); - ecode = tdb_lock_and_recover(tdb); - if (ecode != TDB_SUCCESS) { - return ecode; - } - goto again; -} - -enum TDB_ERROR tdb_lock_open(struct tdb_context *tdb, - int ltype, enum tdb_lock_flags flags) -{ - return tdb_nest_lock(tdb, TDB_OPEN_LOCK, ltype, flags); -} - -void tdb_unlock_open(struct tdb_context *tdb, int ltype) -{ - tdb_nest_unlock(tdb, TDB_OPEN_LOCK, ltype); -} - -bool tdb_has_open_lock(struct tdb_context *tdb) -{ - return !(tdb->flags & TDB_NOLOCK) - && find_nestlock(tdb, TDB_OPEN_LOCK, tdb) != NULL; -} - -enum TDB_ERROR tdb_lock_expand(struct tdb_context *tdb, int ltype) -{ - /* Lock doesn't protect data, so don't check (we recurse if we do!) */ - return tdb_nest_lock(tdb, TDB_EXPANSION_LOCK, ltype, - TDB_LOCK_WAIT | TDB_LOCK_NOCHECK); -} - -void tdb_unlock_expand(struct tdb_context *tdb, int ltype) -{ - tdb_nest_unlock(tdb, TDB_EXPANSION_LOCK, ltype); -} - -/* unlock entire db */ -void tdb_allrecord_unlock(struct tdb_context *tdb, int ltype) -{ - if (tdb->flags & TDB_VERSION1) { - tdb1_allrecord_unlock(tdb, ltype); - return; - } - - if (tdb->flags & TDB_NOLOCK) - return; - - if (tdb->file->allrecord_lock.count == 0) { - tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb_allrecord_unlock: not locked!"); - return; - } - - if (tdb->file->allrecord_lock.owner != tdb) { - tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb_allrecord_unlock: not locked by us!"); - return; - } - - /* Upgradable locks are marked as write locks. */ - if (tdb->file->allrecord_lock.ltype != ltype - && (!tdb->file->allrecord_lock.off || ltype != F_RDLCK)) { - tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_allrecord_unlock: have %s lock", - tdb->file->allrecord_lock.ltype == F_RDLCK - ? "read" : "write"); - return; - } - - if (tdb->file->allrecord_lock.count > 1) { - tdb->file->allrecord_lock.count--; - return; - } - - tdb->file->allrecord_lock.count = 0; - tdb->file->allrecord_lock.ltype = 0; - - tdb_brunlock(tdb, ltype, TDB_HASH_LOCK_START, 0); -} - -bool tdb_has_expansion_lock(struct tdb_context *tdb) -{ - return find_nestlock(tdb, TDB_EXPANSION_LOCK, tdb) != NULL; -} - -bool tdb_has_hash_locks(struct tdb_context *tdb) -{ - unsigned int i; - - for (i=0; ifile->num_lockrecs; i++) { - if (tdb->file->lockrecs[i].off >= TDB_HASH_LOCK_START - && tdb->file->lockrecs[i].off < (TDB_HASH_LOCK_START - + TDB_HASH_LOCK_RANGE)) - return true; - } - return false; -} - -static bool tdb_has_free_lock(struct tdb_context *tdb) -{ - unsigned int i; - - if (tdb->flags & TDB_NOLOCK) - return false; - - for (i=0; ifile->num_lockrecs; i++) { - if (tdb->file->lockrecs[i].off - > TDB_HASH_LOCK_START + TDB_HASH_LOCK_RANGE) - return true; - } - return false; -} - -enum TDB_ERROR tdb_lock_hashes(struct tdb_context *tdb, - tdb_off_t hash_lock, - tdb_len_t hash_range, - int ltype, enum tdb_lock_flags waitflag) -{ - /* FIXME: Do this properly, using hlock_range */ - unsigned l = TDB_HASH_LOCK_START - + (hash_lock >> (64 - TDB_HASH_LOCK_RANGE_BITS)); - - /* a allrecord lock allows us to avoid per chain locks */ - if (tdb->file->allrecord_lock.count) { - if (!check_lock_pid(tdb, "tdb_lock_hashes", true)) - return TDB_ERR_LOCK; - - if (tdb->file->allrecord_lock.owner != tdb) - return owner_conflict(tdb, "tdb_lock_hashes"); - if (ltype == tdb->file->allrecord_lock.ltype - || ltype == F_RDLCK) { - return TDB_SUCCESS; - } - - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb_lock_hashes:" - " already have %s allrecordlock", - tdb->file->allrecord_lock.ltype == F_RDLCK - ? "read" : "write"); - } - - if (tdb_has_free_lock(tdb)) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_lock_hashes: already have free lock"); - } - - if (tdb_has_expansion_lock(tdb)) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_lock_hashes:" - " already have expansion lock"); - } - - return tdb_nest_lock(tdb, l, ltype, waitflag); -} - -enum TDB_ERROR tdb_unlock_hashes(struct tdb_context *tdb, - tdb_off_t hash_lock, - tdb_len_t hash_range, int ltype) -{ - unsigned l = TDB_HASH_LOCK_START - + (hash_lock >> (64 - TDB_HASH_LOCK_RANGE_BITS)); - - if (tdb->flags & TDB_NOLOCK) - return 0; - - /* a allrecord lock allows us to avoid per chain locks */ - if (tdb->file->allrecord_lock.count) { - if (tdb->file->allrecord_lock.ltype == F_RDLCK - && ltype == F_WRLCK) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_unlock_hashes RO allrecord!"); - } - if (tdb->file->allrecord_lock.owner != tdb) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb_unlock_hashes:" - " not locked by us!"); - } - return TDB_SUCCESS; - } - - return tdb_nest_unlock(tdb, l, ltype); -} - -/* Hash locks use TDB_HASH_LOCK_START + the next 30 bits. - * Then we begin; bucket offsets are sizeof(tdb_len_t) apart, so we divide. - * The result is that on 32 bit systems we don't use lock values > 2^31 on - * files that are less than 4GB. - */ -static tdb_off_t free_lock_off(tdb_off_t b_off) -{ - return TDB_HASH_LOCK_START + TDB_HASH_LOCK_RANGE - + b_off / sizeof(tdb_off_t); -} - -enum TDB_ERROR tdb_lock_free_bucket(struct tdb_context *tdb, tdb_off_t b_off, - enum tdb_lock_flags waitflag) -{ - assert(b_off >= sizeof(struct tdb_header)); - - if (tdb->flags & TDB_NOLOCK) - return 0; - - /* a allrecord lock allows us to avoid per chain locks */ - if (tdb->file->allrecord_lock.count) { - if (!check_lock_pid(tdb, "tdb_lock_free_bucket", true)) - return TDB_ERR_LOCK; - - if (tdb->file->allrecord_lock.owner != tdb) { - return owner_conflict(tdb, "tdb_lock_free_bucket"); - } - - if (tdb->file->allrecord_lock.ltype == F_WRLCK) - return 0; - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_lock_free_bucket with" - " read-only allrecordlock!"); - } - -#if 0 /* FIXME */ - if (tdb_has_expansion_lock(tdb)) { - return tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb_lock_free_bucket:" - " already have expansion lock"); - } -#endif - - return tdb_nest_lock(tdb, free_lock_off(b_off), F_WRLCK, waitflag); -} - -void tdb_unlock_free_bucket(struct tdb_context *tdb, tdb_off_t b_off) -{ - if (tdb->file->allrecord_lock.count) - return; - - tdb_nest_unlock(tdb, free_lock_off(b_off), F_WRLCK); -} - -enum TDB_ERROR tdb_lockall(struct tdb_context *tdb) -{ - return tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false); -} - -void tdb_unlockall(struct tdb_context *tdb) -{ - tdb_allrecord_unlock(tdb, F_WRLCK); -} - -enum TDB_ERROR tdb_lockall_read(struct tdb_context *tdb) -{ - return tdb_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_WAIT, false); -} - -void tdb_unlockall_read(struct tdb_context *tdb) -{ - tdb_allrecord_unlock(tdb, F_RDLCK); -} - -void tdb_lock_cleanup(struct tdb_context *tdb) -{ - unsigned int i; - - /* We don't want to warn: they're allowed to close tdb after fork. */ - if (!check_lock_pid(tdb, "tdb_close", false)) - return; - - while (tdb->file->allrecord_lock.count - && tdb->file->allrecord_lock.owner == tdb) { - tdb_allrecord_unlock(tdb, tdb->file->allrecord_lock.ltype); - } - - for (i=0; ifile->num_lockrecs; i++) { - if (tdb->file->lockrecs[i].owner == tdb) { - tdb_nest_unlock(tdb, - tdb->file->lockrecs[i].off, - tdb->file->lockrecs[i].ltype); - i--; - } - } -} diff --git a/ccan/tdb2/open.c b/ccan/tdb2/open.c deleted file mode 100644 index e238d992..00000000 --- a/ccan/tdb2/open.c +++ /dev/null @@ -1,884 +0,0 @@ - /* - Trivial Database 2: opening and closing TDBs - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "private.h" -#include -#include - -/* all tdbs, to detect double-opens (fcntl file don't nest!) */ -static struct tdb_context *tdbs = NULL; - -static struct tdb_file *find_file(dev_t device, ino_t ino) -{ - struct tdb_context *i; - - for (i = tdbs; i; i = i->next) { - if (i->file->device == device && i->file->inode == ino) { - i->file->refcnt++; - return i->file; - } - } - return NULL; -} - -static bool read_all(int fd, void *buf, size_t len) -{ - while (len) { - ssize_t ret; - ret = read(fd, buf, len); - if (ret < 0) - return false; - if (ret == 0) { - /* ETOOSHORT? */ - errno = EWOULDBLOCK; - return false; - } - buf = (char *)buf + ret; - len -= ret; - } - return true; -} - -static uint64_t random_number(struct tdb_context *tdb) -{ - int fd; - uint64_t ret = 0; - struct timeval now; - - fd = open("/dev/urandom", O_RDONLY); - if (fd >= 0) { - if (read_all(fd, &ret, sizeof(ret))) { - close(fd); - return ret; - } - close(fd); - } - /* FIXME: Untested! Based on Wikipedia protocol description! */ - fd = open("/dev/egd-pool", O_RDWR); - if (fd >= 0) { - /* Command is 1, next byte is size we want to read. */ - char cmd[2] = { 1, sizeof(uint64_t) }; - if (write(fd, cmd, sizeof(cmd)) == sizeof(cmd)) { - char reply[1 + sizeof(uint64_t)]; - int r = read(fd, reply, sizeof(reply)); - if (r > 1) { - /* Copy at least some bytes. */ - memcpy(&ret, reply+1, r - 1); - if (reply[0] == sizeof(uint64_t) - && r == sizeof(reply)) { - close(fd); - return ret; - } - } - } - close(fd); - } - - /* Fallback: pid and time. */ - gettimeofday(&now, NULL); - ret = getpid() * 100132289ULL + now.tv_sec * 1000000ULL + now.tv_usec; - tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING, - "tdb_open: random from getpid and time"); - return ret; -} - -static void tdb2_context_init(struct tdb_context *tdb) -{ - /* Initialize the TDB2 fields here */ - tdb_io_init(tdb); - tdb->tdb2.direct_access = 0; - tdb->tdb2.transaction = NULL; - tdb->tdb2.access = NULL; -} - -struct new_database { - struct tdb_header hdr; - struct tdb_freetable ftable; -}; - -/* initialise a new database */ -static enum TDB_ERROR tdb_new_database(struct tdb_context *tdb, - struct tdb_attribute_seed *seed, - struct tdb_header *hdr) -{ - /* We make it up in memory, then write it out if not internal */ - struct new_database newdb; - unsigned int magic_len; - ssize_t rlen; - enum TDB_ERROR ecode; - - /* Fill in the header */ - newdb.hdr.version = TDB_VERSION; - if (seed) - newdb.hdr.hash_seed = seed->seed; - else - newdb.hdr.hash_seed = random_number(tdb); - newdb.hdr.hash_test = TDB_HASH_MAGIC; - newdb.hdr.hash_test = tdb->hash_fn(&newdb.hdr.hash_test, - sizeof(newdb.hdr.hash_test), - newdb.hdr.hash_seed, - tdb->hash_data); - newdb.hdr.recovery = 0; - newdb.hdr.features_used = newdb.hdr.features_offered = TDB_FEATURE_MASK; - newdb.hdr.seqnum = 0; - newdb.hdr.capabilities = 0; - memset(newdb.hdr.reserved, 0, sizeof(newdb.hdr.reserved)); - /* Initial hashes are empty. */ - memset(newdb.hdr.hashtable, 0, sizeof(newdb.hdr.hashtable)); - - /* Free is empty. */ - newdb.hdr.free_table = offsetof(struct new_database, ftable); - memset(&newdb.ftable, 0, sizeof(newdb.ftable)); - ecode = set_header(NULL, &newdb.ftable.hdr, TDB_FTABLE_MAGIC, 0, - sizeof(newdb.ftable) - sizeof(newdb.ftable.hdr), - sizeof(newdb.ftable) - sizeof(newdb.ftable.hdr), - 0); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* Magic food */ - memset(newdb.hdr.magic_food, 0, sizeof(newdb.hdr.magic_food)); - strcpy(newdb.hdr.magic_food, TDB_MAGIC_FOOD); - - /* This creates an endian-converted database, as if read from disk */ - magic_len = sizeof(newdb.hdr.magic_food); - tdb_convert(tdb, - (char *)&newdb.hdr + magic_len, sizeof(newdb) - magic_len); - - *hdr = newdb.hdr; - - if (tdb->flags & TDB_INTERNAL) { - tdb->file->map_size = sizeof(newdb); - tdb->file->map_ptr = malloc(tdb->file->map_size); - if (!tdb->file->map_ptr) { - return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_new_database:" - " failed to allocate"); - } - memcpy(tdb->file->map_ptr, &newdb, tdb->file->map_size); - return TDB_SUCCESS; - } - if (lseek(tdb->file->fd, 0, SEEK_SET) == -1) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_new_database:" - " failed to seek: %s", strerror(errno)); - } - - if (ftruncate(tdb->file->fd, 0) == -1) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_new_database:" - " failed to truncate: %s", strerror(errno)); - } - - rlen = write(tdb->file->fd, &newdb, sizeof(newdb)); - if (rlen != sizeof(newdb)) { - if (rlen >= 0) - errno = ENOSPC; - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_new_database: %zi writing header: %s", - rlen, strerror(errno)); - } - return TDB_SUCCESS; -} - -static enum TDB_ERROR tdb_new_file(struct tdb_context *tdb) -{ - tdb->file = malloc(sizeof(*tdb->file)); - if (!tdb->file) - return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_open: cannot alloc tdb_file structure"); - tdb->file->num_lockrecs = 0; - tdb->file->lockrecs = NULL; - tdb->file->allrecord_lock.count = 0; - tdb->file->refcnt = 1; - tdb->file->map_ptr = NULL; - return TDB_SUCCESS; -} - -enum TDB_ERROR tdb_set_attribute(struct tdb_context *tdb, - const union tdb_attribute *attr) -{ - switch (attr->base.attr) { - case TDB_ATTRIBUTE_LOG: - tdb->log_fn = attr->log.fn; - tdb->log_data = attr->log.data; - break; - case TDB_ATTRIBUTE_HASH: - case TDB_ATTRIBUTE_SEED: - case TDB_ATTRIBUTE_OPENHOOK: - case TDB_ATTRIBUTE_TDB1_HASHSIZE: - return tdb->last_error - = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_set_attribute:" - " cannot set %s after opening", - attr->base.attr == TDB_ATTRIBUTE_HASH - ? "TDB_ATTRIBUTE_HASH" - : attr->base.attr == TDB_ATTRIBUTE_SEED - ? "TDB_ATTRIBUTE_SEED" - : attr->base.attr == TDB_ATTRIBUTE_OPENHOOK - ? "TDB_ATTRIBUTE_OPENHOOK" - : "TDB_ATTRIBUTE_TDB1_HASHSIZE"); - case TDB_ATTRIBUTE_STATS: - return tdb->last_error - = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_set_attribute:" - " cannot set TDB_ATTRIBUTE_STATS"); - case TDB_ATTRIBUTE_FLOCK: - tdb->lock_fn = attr->flock.lock; - tdb->unlock_fn = attr->flock.unlock; - tdb->lock_data = attr->flock.data; - break; - default: - return tdb->last_error - = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_set_attribute:" - " unknown attribute type %u", - attr->base.attr); - } - return TDB_SUCCESS; -} - -enum TDB_ERROR tdb_get_attribute(struct tdb_context *tdb, - union tdb_attribute *attr) -{ - switch (attr->base.attr) { - case TDB_ATTRIBUTE_LOG: - if (!tdb->log_fn) - return tdb->last_error = TDB_ERR_NOEXIST; - attr->log.fn = tdb->log_fn; - attr->log.data = tdb->log_data; - break; - case TDB_ATTRIBUTE_HASH: - attr->hash.fn = tdb->hash_fn; - attr->hash.data = tdb->hash_data; - break; - case TDB_ATTRIBUTE_SEED: - if (tdb->flags & TDB_VERSION1) - return tdb->last_error - = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_get_attribute:" - " cannot get TDB_ATTRIBUTE_SEED" - " on TDB1 tdb."); - attr->seed.seed = tdb->hash_seed; - break; - case TDB_ATTRIBUTE_OPENHOOK: - if (!tdb->openhook) - return tdb->last_error = TDB_ERR_NOEXIST; - attr->openhook.fn = tdb->openhook; - attr->openhook.data = tdb->openhook_data; - break; - case TDB_ATTRIBUTE_STATS: { - size_t size = attr->stats.size; - if (size > tdb->stats.size) - size = tdb->stats.size; - memcpy(&attr->stats, &tdb->stats, size); - break; - } - case TDB_ATTRIBUTE_FLOCK: - attr->flock.lock = tdb->lock_fn; - attr->flock.unlock = tdb->unlock_fn; - attr->flock.data = tdb->lock_data; - break; - case TDB_ATTRIBUTE_TDB1_HASHSIZE: - if (!(tdb->flags & TDB_VERSION1)) - return tdb->last_error - = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_get_attribute:" - " cannot get TDB_ATTRIBUTE_TDB1_HASHSIZE" - " on TDB2 tdb."); - attr->tdb1_hashsize.hsize = tdb->tdb1.header.hash_size; - break; - default: - return tdb->last_error - = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_get_attribute:" - " unknown attribute type %u", - attr->base.attr); - } - attr->base.next = NULL; - return TDB_SUCCESS; -} - -void tdb_unset_attribute(struct tdb_context *tdb, - enum tdb_attribute_type type) -{ - switch (type) { - case TDB_ATTRIBUTE_LOG: - tdb->log_fn = NULL; - break; - case TDB_ATTRIBUTE_OPENHOOK: - tdb->openhook = NULL; - break; - case TDB_ATTRIBUTE_HASH: - case TDB_ATTRIBUTE_SEED: - case TDB_ATTRIBUTE_TDB1_HASHSIZE: - tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb_unset_attribute: cannot unset %s after opening", - type == TDB_ATTRIBUTE_HASH - ? "TDB_ATTRIBUTE_HASH" - : type == TDB_ATTRIBUTE_SEED - ? "TDB_ATTRIBUTE_SEED" - : "TDB_ATTRIBUTE_TDB1_HASHSIZE"); - break; - case TDB_ATTRIBUTE_STATS: - tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_unset_attribute:" - "cannot unset TDB_ATTRIBUTE_STATS"); - break; - case TDB_ATTRIBUTE_FLOCK: - tdb->lock_fn = tdb_fcntl_lock; - tdb->unlock_fn = tdb_fcntl_unlock; - break; - default: - tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_unset_attribute: unknown attribute type %u", - type); - } -} - -static bool is_tdb1(struct tdb1_header *hdr, const void *buf, ssize_t rlen) -{ - /* This code assumes we've tried to read entire tdb1 header. */ - BUILD_ASSERT(sizeof(*hdr) <= sizeof(struct tdb_header)); - - if (rlen < (ssize_t)sizeof(*hdr)) { - return false; - } - - memcpy(hdr, buf, sizeof(*hdr)); - if (strcmp(hdr->magic_food, TDB_MAGIC_FOOD) != 0) - return false; - - return hdr->version == TDB1_VERSION - || hdr->version == TDB1_BYTEREV(TDB1_VERSION); -} - -/* The top three bits of the capability tell us whether it matters. */ -enum TDB_ERROR unknown_capability(struct tdb_context *tdb, const char *caller, - tdb_off_t type) -{ - if (type & TDB_CAP_NOOPEN) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "%s: file has unknown capability %llu", - caller, type & TDB_CAP_NOOPEN); - } - - if ((type & TDB_CAP_NOWRITE) && !(tdb->flags & TDB_RDONLY)) { - return tdb_logerr(tdb, TDB_ERR_RDONLY, TDB_LOG_ERROR, - "%s: file has unknown capability %llu" - " (cannot write to it)", - caller, type & TDB_CAP_NOOPEN); - } - - if (type & TDB_CAP_NOCHECK) { - tdb->flags |= TDB_CANT_CHECK; - } - return TDB_SUCCESS; -} - -static enum TDB_ERROR capabilities_ok(struct tdb_context *tdb, - tdb_off_t capabilities) -{ - tdb_off_t off, next; - enum TDB_ERROR ecode = TDB_SUCCESS; - const struct tdb_capability *cap; - - /* Check capability list. */ - for (off = capabilities; off && ecode == TDB_SUCCESS; off = next) { - cap = tdb_access_read(tdb, off, sizeof(*cap), true); - if (TDB_PTR_IS_ERR(cap)) { - return TDB_PTR_ERR(cap); - } - - switch (cap->type & TDB_CAP_TYPE_MASK) { - /* We don't understand any capabilities (yet). */ - default: - ecode = unknown_capability(tdb, "tdb_open", cap->type); - } - next = cap->next; - tdb_access_release(tdb, cap); - } - return ecode; -} - -struct tdb_context *tdb_open(const char *name, int tdb_flags, - int open_flags, mode_t mode, - union tdb_attribute *attr) -{ - struct tdb_context *tdb; - struct stat st; - int saved_errno = 0; - uint64_t hash_test; - unsigned v; - ssize_t rlen; - struct tdb_header hdr; - struct tdb_attribute_seed *seed = NULL; - struct tdb_attribute_tdb1_hashsize *hsize_attr = NULL; - struct tdb_attribute_tdb1_max_dead *maxsize_attr = NULL; - tdb_bool_err berr; - enum TDB_ERROR ecode; - int openlock; - - tdb = malloc(sizeof(*tdb) + (name ? strlen(name) + 1 : 0)); - if (!tdb) { - /* Can't log this */ - errno = ENOMEM; - return NULL; - } - /* Set name immediately for logging functions. */ - if (name) { - tdb->name = strcpy((char *)(tdb + 1), name); - } else { - tdb->name = NULL; - } - tdb->flags = tdb_flags; - tdb->log_fn = NULL; - tdb->open_flags = open_flags; - tdb->last_error = TDB_SUCCESS; - tdb->file = NULL; - tdb->openhook = NULL; - tdb->lock_fn = tdb_fcntl_lock; - tdb->unlock_fn = tdb_fcntl_unlock; - tdb->hash_fn = tdb_jenkins_hash; - memset(&tdb->stats, 0, sizeof(tdb->stats)); - tdb->stats.base.attr = TDB_ATTRIBUTE_STATS; - tdb->stats.size = sizeof(tdb->stats); - - while (attr) { - switch (attr->base.attr) { - case TDB_ATTRIBUTE_HASH: - tdb->hash_fn = attr->hash.fn; - tdb->hash_data = attr->hash.data; - break; - case TDB_ATTRIBUTE_SEED: - seed = &attr->seed; - break; - case TDB_ATTRIBUTE_OPENHOOK: - tdb->openhook = attr->openhook.fn; - tdb->openhook_data = attr->openhook.data; - break; - case TDB_ATTRIBUTE_TDB1_HASHSIZE: - hsize_attr = &attr->tdb1_hashsize; - break; - case TDB_ATTRIBUTE_TDB1_MAX_DEAD: - maxsize_attr = &attr->tdb1_max_dead; - break; - default: - /* These are set as normal. */ - ecode = tdb_set_attribute(tdb, attr); - if (ecode != TDB_SUCCESS) - goto fail; - } - attr = attr->base.next; - } - - if (tdb_flags & ~(TDB_INTERNAL | TDB_NOLOCK | TDB_NOMMAP | TDB_CONVERT - | TDB_NOSYNC | TDB_SEQNUM | TDB_ALLOW_NESTING - | TDB_RDONLY | TDB_VERSION1)) { - ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb_open: unknown flags %u", tdb_flags); - goto fail; - } - - if (hsize_attr) { - if (!(tdb_flags & TDB_VERSION1) || - (!(tdb_flags & TDB_INTERNAL) && !(open_flags & O_CREAT))) { - ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_open: can only use" - " TDB_ATTRIBUTE_TDB1_HASHSIZE when" - " creating a TDB_VERSION1 tdb"); - goto fail; - } - } - - if (seed) { - if (tdb_flags & TDB_VERSION1) { - ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_open:" - " cannot set TDB_ATTRIBUTE_SEED" - " on TDB1 tdb."); - goto fail; - } else if (!(tdb_flags & TDB_INTERNAL) - && !(open_flags & O_CREAT)) { - ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_open:" - " cannot set TDB_ATTRIBUTE_SEED" - " without O_CREAT."); - goto fail; - } - } - - if ((open_flags & O_ACCMODE) == O_WRONLY) { - ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb_open: can't open tdb %s write-only", - name); - goto fail; - } - - if ((open_flags & O_ACCMODE) == O_RDONLY) { - openlock = F_RDLCK; - tdb->flags |= TDB_RDONLY; - } else { - if (tdb_flags & TDB_RDONLY) { - ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_open: can't use TDB_RDONLY" - " without O_RDONLY"); - goto fail; - } - openlock = F_WRLCK; - } - - /* internal databases don't need any of the rest. */ - if (tdb->flags & TDB_INTERNAL) { - tdb->flags |= (TDB_NOLOCK | TDB_NOMMAP); - ecode = tdb_new_file(tdb); - if (ecode != TDB_SUCCESS) { - goto fail; - } - tdb->file->fd = -1; - if (tdb->flags & TDB_VERSION1) - ecode = tdb1_new_database(tdb, hsize_attr, maxsize_attr); - else { - ecode = tdb_new_database(tdb, seed, &hdr); - if (ecode == TDB_SUCCESS) { - tdb_convert(tdb, &hdr.hash_seed, - sizeof(hdr.hash_seed)); - tdb->hash_seed = hdr.hash_seed; - tdb2_context_init(tdb); - tdb_ftable_init(tdb); - } - } - if (ecode != TDB_SUCCESS) { - goto fail; - } - return tdb; - } - - if (stat(name, &st) != -1) - tdb->file = find_file(st.st_dev, st.st_ino); - - if (!tdb->file) { - int fd; - - if ((fd = open(name, open_flags, mode)) == -1) { - /* errno set by open(2) */ - saved_errno = errno; - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_open: could not open file %s: %s", - name, strerror(errno)); - goto fail_errno; - } - - /* on exec, don't inherit the fd */ - v = fcntl(fd, F_GETFD, 0); - fcntl(fd, F_SETFD, v | FD_CLOEXEC); - - if (fstat(fd, &st) == -1) { - saved_errno = errno; - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_open: could not stat open %s: %s", - name, strerror(errno)); - close(fd); - goto fail_errno; - } - - ecode = tdb_new_file(tdb); - if (ecode != TDB_SUCCESS) { - close(fd); - goto fail; - } - - tdb->file->fd = fd; - tdb->file->device = st.st_dev; - tdb->file->inode = st.st_ino; - tdb->file->map_ptr = NULL; - tdb->file->map_size = 0; - } - - /* ensure there is only one process initialising at once */ - ecode = tdb_lock_open(tdb, openlock, TDB_LOCK_WAIT|TDB_LOCK_NOCHECK); - if (ecode != TDB_SUCCESS) { - saved_errno = errno; - goto fail_errno; - } - - /* call their open hook if they gave us one. */ - if (tdb->openhook) { - ecode = tdb->openhook(tdb->file->fd, tdb->openhook_data); - if (ecode != TDB_SUCCESS) { - tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_open: open hook failed"); - goto fail; - } - open_flags |= O_CREAT; - } - - /* If they used O_TRUNC, read will return 0. */ - rlen = pread(tdb->file->fd, &hdr, sizeof(hdr), 0); - if (rlen == 0 && (open_flags & O_CREAT)) { - if (tdb->flags & TDB_VERSION1) { - ecode = tdb1_new_database(tdb, hsize_attr, maxsize_attr); - if (ecode != TDB_SUCCESS) - goto fail; - goto finished; - } - ecode = tdb_new_database(tdb, seed, &hdr); - if (ecode != TDB_SUCCESS) { - goto fail; - } - } else if (rlen < 0) { - ecode = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_open: error %s reading %s", - strerror(errno), name); - goto fail; - } else if (rlen < sizeof(hdr) - || strcmp(hdr.magic_food, TDB_MAGIC_FOOD) != 0) { - if (is_tdb1(&tdb->tdb1.header, &hdr, rlen)) { - ecode = tdb1_open(tdb, maxsize_attr); - if (!ecode) - goto finished; - goto fail; - } - ecode = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_open: %s is not a tdb file", name); - goto fail; - } - - if (hdr.version != TDB_VERSION) { - if (hdr.version == bswap_64(TDB_VERSION)) - tdb->flags |= TDB_CONVERT; - else { - if (is_tdb1(&tdb->tdb1.header, &hdr, rlen)) { - ecode = tdb1_open(tdb, maxsize_attr); - if (!ecode) - goto finished; - goto fail; - } - /* wrong version */ - ecode = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_open:" - " %s is unknown version 0x%llx", - name, (long long)hdr.version); - goto fail; - } - } else if (tdb->flags & TDB_CONVERT) { - ecode = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_open:" - " %s does not need TDB_CONVERT", - name); - goto fail; - } - - /* This is a version2 tdb. */ - if (tdb->flags & TDB_VERSION1) { - tdb->flags &= ~TDB_VERSION1; - } - - tdb2_context_init(tdb); - - tdb_convert(tdb, &hdr, sizeof(hdr)); - tdb->hash_seed = hdr.hash_seed; - hash_test = TDB_HASH_MAGIC; - hash_test = tdb_hash(tdb, &hash_test, sizeof(hash_test)); - if (hdr.hash_test != hash_test) { - /* wrong hash variant */ - ecode = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_open:" - " %s uses a different hash function", - name); - goto fail; - } - - ecode = capabilities_ok(tdb, hdr.capabilities); - if (ecode != TDB_SUCCESS) { - goto fail; - } - - /* Clear any features we don't understand. */ - if ((open_flags & O_ACCMODE) != O_RDONLY) { - hdr.features_used &= TDB_FEATURE_MASK; - ecode = tdb_write_convert(tdb, offsetof(struct tdb_header, - features_used), - &hdr.features_used, - sizeof(hdr.features_used)); - if (ecode != TDB_SUCCESS) - goto fail; - } - -finished: - if (tdb->flags & TDB_VERSION1) { - /* if needed, run recovery */ - if (tdb1_transaction_recover(tdb) == -1) { - ecode = tdb->last_error; - goto fail; - } - } - - tdb_unlock_open(tdb, openlock); - - /* This makes sure we have current map_size and mmap. */ - if (tdb->flags & TDB_VERSION1) { - ecode = tdb1_probe_length(tdb); - } else { - ecode = tdb->tdb2.io->oob(tdb, tdb->file->map_size, 1, true); - } - if (unlikely(ecode != TDB_SUCCESS)) - goto fail; - - if (!(tdb->flags & TDB_VERSION1)) { - /* Now it's fully formed, recover if necessary. */ - berr = tdb_needs_recovery(tdb); - if (unlikely(berr != false)) { - if (berr < 0) { - ecode = TDB_OFF_TO_ERR(berr); - goto fail; - } - ecode = tdb_lock_and_recover(tdb); - if (ecode != TDB_SUCCESS) { - goto fail; - } - } - - ecode = tdb_ftable_init(tdb); - if (ecode != TDB_SUCCESS) { - goto fail; - } - } - - tdb->next = tdbs; - tdbs = tdb; - return tdb; - - fail: - /* Map ecode to some logical errno. */ - switch (TDB_ERR_TO_OFF(ecode)) { - case TDB_ERR_TO_OFF(TDB_ERR_CORRUPT): - case TDB_ERR_TO_OFF(TDB_ERR_IO): - saved_errno = EIO; - break; - case TDB_ERR_TO_OFF(TDB_ERR_LOCK): - saved_errno = EWOULDBLOCK; - break; - case TDB_ERR_TO_OFF(TDB_ERR_OOM): - saved_errno = ENOMEM; - break; - case TDB_ERR_TO_OFF(TDB_ERR_EINVAL): - saved_errno = EINVAL; - break; - default: - saved_errno = EINVAL; - break; - } - -fail_errno: -#ifdef TDB_TRACE - close(tdb->tracefd); -#endif - if (tdb->file) { - tdb_lock_cleanup(tdb); - if (--tdb->file->refcnt == 0) { - assert(tdb->file->num_lockrecs == 0); - if (tdb->file->map_ptr) { - if (tdb->flags & TDB_INTERNAL) { - free(tdb->file->map_ptr); - } else - tdb_munmap(tdb->file); - } - if (close(tdb->file->fd) != 0) - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_open: failed to close tdb fd" - " on error: %s", strerror(errno)); - free(tdb->file->lockrecs); - free(tdb->file); - } - } - - free(tdb); - errno = saved_errno; - return NULL; -} - -int tdb_close(struct tdb_context *tdb) -{ - int ret = 0; - struct tdb_context **i; - - tdb_trace(tdb, "tdb_close"); - - if (tdb->flags & TDB_VERSION1) { - if (tdb->tdb1.transaction) { - tdb1_transaction_cancel(tdb); - } - } else { - if (tdb->tdb2.transaction) { - tdb_transaction_cancel(tdb); - } - } - - if (tdb->file->map_ptr) { - if (tdb->flags & TDB_INTERNAL) - free(tdb->file->map_ptr); - else - tdb_munmap(tdb->file); - } - if (tdb->file) { - tdb_lock_cleanup(tdb); - if (--tdb->file->refcnt == 0) { - ret = close(tdb->file->fd); - free(tdb->file->lockrecs); - free(tdb->file); - } - } - - /* Remove from tdbs list */ - for (i = &tdbs; *i; i = &(*i)->next) { - if (*i == tdb) { - *i = tdb->next; - break; - } - } - -#ifdef TDB_TRACE - close(tdb->tracefd); -#endif - free(tdb); - - return ret; -} - -void tdb_foreach_(int (*fn)(struct tdb_context *, void *), void *p) -{ - struct tdb_context *i; - - for (i = tdbs; i; i = i->next) { - if (fn(i, p) != 0) - break; - } -} diff --git a/ccan/tdb2/private.h b/ccan/tdb2/private.h deleted file mode 100644 index ba7de3be..00000000 --- a/ccan/tdb2/private.h +++ /dev/null @@ -1,762 +0,0 @@ -#ifndef TDB_PRIVATE_H -#define TDB_PRIVATE_H - /* - Trivial Database 2: private types and prototypes - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef TEST_IT -#define TEST_IT(cond) -#endif - -/* #define TDB_TRACE 1 */ - -#ifndef __STRING -#define __STRING(x) #x -#endif - -#ifndef __STRINGSTRING -#define __STRINGSTRING(x) __STRING(x) -#endif - -#ifndef __location__ -#define __location__ __FILE__ ":" __STRINGSTRING(__LINE__) -#endif - -typedef uint64_t tdb_len_t; -typedef uint64_t tdb_off_t; - -#define TDB_MAGIC_FOOD "TDB file\n" -#define TDB_VERSION ((uint64_t)(0x26011967 + 7)) -#define TDB1_VERSION (0x26011967 + 6) -#define TDB_USED_MAGIC ((uint64_t)0x1999) -#define TDB_HTABLE_MAGIC ((uint64_t)0x1888) -#define TDB_CHAIN_MAGIC ((uint64_t)0x1777) -#define TDB_FTABLE_MAGIC ((uint64_t)0x1666) -#define TDB_CAP_MAGIC ((uint64_t)0x1555) -#define TDB_FREE_MAGIC ((uint64_t)0xFE) -#define TDB_HASH_MAGIC (0xA1ABE11A01092008ULL) -#define TDB_RECOVERY_MAGIC (0xf53bc0e7ad124589ULL) -#define TDB_RECOVERY_INVALID_MAGIC (0x0ULL) - -/* Capability bits. */ -#define TDB_CAP_TYPE_MASK 0x1FFFFFFFFFFFFFFFULL -#define TDB_CAP_NOCHECK 0x8000000000000000ULL -#define TDB_CAP_NOWRITE 0x4000000000000000ULL -#define TDB_CAP_NOOPEN 0x2000000000000000ULL - -#define TDB_OFF_IS_ERR(off) unlikely(off >= (tdb_off_t)(long)TDB_ERR_LAST) -#define TDB_OFF_TO_ERR(off) ((enum TDB_ERROR)(long)(off)) -#define TDB_ERR_TO_OFF(ecode) ((tdb_off_t)(long)(ecode)) - -/* Packing errors into pointers and v.v. */ -#define TDB_PTR_IS_ERR(ptr) \ - unlikely((unsigned long)(ptr) >= (unsigned long)TDB_ERR_LAST) -#define TDB_PTR_ERR(p) ((enum TDB_ERROR)(long)(p)) -#define TDB_ERR_PTR(err) ((void *)(long)(err)) - -/* Common case of returning true, false or -ve error. */ -typedef int tdb_bool_err; - -/* Prevent others from opening the file. */ -#define TDB_OPEN_LOCK 0 -/* Expanding file. */ -#define TDB_EXPANSION_LOCK 2 -/* Doing a transaction. */ -#define TDB_TRANSACTION_LOCK 8 -/* Hash chain locks. */ -#define TDB_HASH_LOCK_START 64 - -/* Range for hash locks. */ -#define TDB_HASH_LOCK_RANGE_BITS 30 -#define TDB_HASH_LOCK_RANGE (1 << TDB_HASH_LOCK_RANGE_BITS) - -/* We have 1024 entries in the top level. */ -#define TDB_TOPLEVEL_HASH_BITS 10 -/* And 64 entries in each sub-level: thus 64 bits exactly after 9 levels. */ -#define TDB_SUBLEVEL_HASH_BITS 6 -/* And 8 entries in each group, ie 8 groups per sublevel. */ -#define TDB_HASH_GROUP_BITS 3 -/* This is currently 10: beyond this we chain. */ -#define TDB_MAX_LEVELS (1+(64-TDB_TOPLEVEL_HASH_BITS) / TDB_SUBLEVEL_HASH_BITS) - -/* Extend file by least 100 times larger than needed. */ -#define TDB_EXTENSION_FACTOR 100 - -/* We steal bits from the offsets to store hash info. */ -#define TDB_OFF_HASH_GROUP_MASK ((1ULL << TDB_HASH_GROUP_BITS) - 1) -/* We steal this many upper bits, giving a maximum offset of 64 exabytes. */ -#define TDB_OFF_UPPER_STEAL 8 -#define TDB_OFF_UPPER_STEAL_EXTRA 7 -/* The bit number where we store extra hash bits. */ -#define TDB_OFF_HASH_EXTRA_BIT 57 -#define TDB_OFF_UPPER_STEAL_SUBHASH_BIT 56 - -/* Additional features we understand. Currently: none. */ -#define TDB_FEATURE_MASK ((uint64_t)0) - -/* The bit number where we store the extra hash bits. */ -/* Convenience mask to get actual offset. */ -#define TDB_OFF_MASK \ - (((1ULL << (64 - TDB_OFF_UPPER_STEAL)) - 1) - TDB_OFF_HASH_GROUP_MASK) - -/* How many buckets in a free list: see size_to_bucket(). */ -#define TDB_FREE_BUCKETS (64 - TDB_OFF_UPPER_STEAL) - -/* We have to be able to fit a free record here. */ -#define TDB_MIN_DATA_LEN \ - (sizeof(struct tdb_free_record) - sizeof(struct tdb_used_record)) - -/* Indicates this entry is not on an flist (can happen during coalescing) */ -#define TDB_FTABLE_NONE ((1ULL << TDB_OFF_UPPER_STEAL) - 1) - -struct tdb_used_record { - /* For on-disk compatibility, we avoid bitfields: - magic: 16, (highest) - key_len_bits: 5, - extra_padding: 32 - hash_bits: 11 - */ - uint64_t magic_and_meta; - /* The bottom key_len_bits*2 are key length, rest is data length. */ - uint64_t key_and_data_len; -}; - -static inline unsigned rec_key_bits(const struct tdb_used_record *r) -{ - return ((r->magic_and_meta >> 43) & ((1 << 5)-1)) * 2; -} - -static inline uint64_t rec_key_length(const struct tdb_used_record *r) -{ - return r->key_and_data_len & ((1ULL << rec_key_bits(r)) - 1); -} - -static inline uint64_t rec_data_length(const struct tdb_used_record *r) -{ - return r->key_and_data_len >> rec_key_bits(r); -} - -static inline uint64_t rec_extra_padding(const struct tdb_used_record *r) -{ - return (r->magic_and_meta >> 11) & 0xFFFFFFFF; -} - -static inline uint32_t rec_hash(const struct tdb_used_record *r) -{ - return r->magic_and_meta & ((1 << 11) - 1); -} - -static inline uint16_t rec_magic(const struct tdb_used_record *r) -{ - return (r->magic_and_meta >> 48); -} - -struct tdb_free_record { - uint64_t magic_and_prev; /* TDB_OFF_UPPER_STEAL bits magic, then prev */ - uint64_t ftable_and_len; /* Len not counting these two fields. */ - /* This is why the minimum record size is 8 bytes. */ - uint64_t next; -}; - -static inline uint64_t frec_prev(const struct tdb_free_record *f) -{ - return f->magic_and_prev & ((1ULL << (64 - TDB_OFF_UPPER_STEAL)) - 1); -} - -static inline uint64_t frec_magic(const struct tdb_free_record *f) -{ - return f->magic_and_prev >> (64 - TDB_OFF_UPPER_STEAL); -} - -static inline uint64_t frec_len(const struct tdb_free_record *f) -{ - return f->ftable_and_len & ((1ULL << (64 - TDB_OFF_UPPER_STEAL))-1); -} - -static inline unsigned frec_ftable(const struct tdb_free_record *f) -{ - return f->ftable_and_len >> (64 - TDB_OFF_UPPER_STEAL); -} - -struct tdb_recovery_record { - uint64_t magic; - /* Length of record (add this header to get total length). */ - uint64_t max_len; - /* Length used. */ - uint64_t len; - /* Old length of file before transaction. */ - uint64_t eof; -}; - -/* If we bottom out of the subhashes, we chain. */ -struct tdb_chain { - tdb_off_t rec[1 << TDB_HASH_GROUP_BITS]; - tdb_off_t next; -}; - -/* this is stored at the front of every database */ -struct tdb_header { - char magic_food[64]; /* for /etc/magic */ - /* FIXME: Make me 32 bit? */ - uint64_t version; /* version of the code */ - uint64_t hash_test; /* result of hashing HASH_MAGIC. */ - uint64_t hash_seed; /* "random" seed written at creation time. */ - tdb_off_t free_table; /* (First) free table. */ - tdb_off_t recovery; /* Transaction recovery area. */ - - uint64_t features_used; /* Features all writers understand */ - uint64_t features_offered; /* Features offered */ - - uint64_t seqnum; /* Sequence number for TDB_SEQNUM */ - - tdb_off_t capabilities; /* Optional linked list of capabilities. */ - tdb_off_t reserved[22]; - - /* Top level hash table. */ - tdb_off_t hashtable[1ULL << TDB_TOPLEVEL_HASH_BITS]; -}; - -struct tdb_freetable { - struct tdb_used_record hdr; - tdb_off_t next; - tdb_off_t buckets[TDB_FREE_BUCKETS]; -}; - -struct tdb_capability { - struct tdb_used_record hdr; - tdb_off_t type; - tdb_off_t next; - /* ... */ -}; - -/* Information about a particular (locked) hash entry. */ -struct hash_info { - /* Full hash value of entry. */ - uint64_t h; - /* Start and length of lock acquired. */ - tdb_off_t hlock_start; - tdb_len_t hlock_range; - /* Start of hash group. */ - tdb_off_t group_start; - /* Bucket we belong in. */ - unsigned int home_bucket; - /* Bucket we (or an empty space) were found in. */ - unsigned int found_bucket; - /* How many bits of the hash are already used. */ - unsigned int hash_used; - /* Current working group. */ - tdb_off_t group[1 << TDB_HASH_GROUP_BITS]; -}; - -struct traverse_info { - struct traverse_level { - tdb_off_t hashtable; - /* We ignore groups here, and treat it as a big array. */ - unsigned entry; - unsigned int total_buckets; - } levels[TDB_MAX_LEVELS + 1]; - unsigned int num_levels; - unsigned int toplevel_group; - /* This makes delete-everything-inside-traverse work as expected. */ - tdb_off_t prev; -}; - -typedef uint32_t tdb1_len_t; -typedef uint32_t tdb1_off_t; - -enum tdb_lock_flags { - /* WAIT == F_SETLKW, NOWAIT == F_SETLK */ - TDB_LOCK_NOWAIT = 0, - TDB_LOCK_WAIT = 1, - /* If set, don't log an error on failure. */ - TDB_LOCK_PROBE = 2, - /* If set, don't check for recovery (used by recovery code). */ - TDB_LOCK_NOCHECK = 4, -}; - -struct tdb_lock { - struct tdb_context *owner; - off_t off; - uint32_t count; - uint32_t ltype; -}; - -/* This is only needed for tdb_access_commit, but used everywhere to - * simplify. */ -struct tdb_access_hdr { - struct tdb_access_hdr *next; - tdb_off_t off; - tdb_len_t len; - bool convert; -}; - -struct tdb_file { - /* How many are sharing us? */ - unsigned int refcnt; - - /* Mmap (if any), or malloc (for TDB_INTERNAL). */ - void *map_ptr; - - /* How much space has been mapped (<= current file size) */ - tdb_len_t map_size; - - /* The file descriptor (-1 for TDB_INTERNAL). */ - int fd; - - /* Lock information */ - pid_t locker; - struct tdb_lock allrecord_lock; - size_t num_lockrecs; - struct tdb_lock *lockrecs; - - /* Identity of this file. */ - dev_t device; - ino_t inode; -}; - -struct tdb_methods { - enum TDB_ERROR (*tread)(struct tdb_context *, tdb_off_t, void *, - tdb_len_t); - enum TDB_ERROR (*twrite)(struct tdb_context *, tdb_off_t, const void *, - tdb_len_t); - enum TDB_ERROR (*oob)(struct tdb_context *, tdb_off_t, tdb_len_t, bool); - enum TDB_ERROR (*expand_file)(struct tdb_context *, tdb_len_t); - void *(*direct)(struct tdb_context *, tdb_off_t, size_t, bool); -}; - -/* - internal prototypes -*/ -/* hash.c: */ -uint64_t tdb_jenkins_hash(const void *key, size_t length, uint64_t seed, - void *unused); - -enum TDB_ERROR first_in_hash(struct tdb_context *tdb, - struct traverse_info *tinfo, - TDB_DATA *kbuf, size_t *dlen); - -enum TDB_ERROR next_in_hash(struct tdb_context *tdb, - struct traverse_info *tinfo, - TDB_DATA *kbuf, size_t *dlen); - -/* Hash random memory. */ -uint64_t tdb_hash(struct tdb_context *tdb, const void *ptr, size_t len); - -/* Hash on disk. */ -uint64_t hash_record(struct tdb_context *tdb, tdb_off_t off); - -/* Find and lock a hash entry (or where it would be). */ -tdb_off_t find_and_lock(struct tdb_context *tdb, - struct tdb_data key, - int ltype, - struct hash_info *h, - struct tdb_used_record *rec, - struct traverse_info *tinfo); - -enum TDB_ERROR replace_in_hash(struct tdb_context *tdb, - struct hash_info *h, - tdb_off_t new_off); - -enum TDB_ERROR add_to_hash(struct tdb_context *tdb, struct hash_info *h, - tdb_off_t new_off); - -enum TDB_ERROR delete_from_hash(struct tdb_context *tdb, struct hash_info *h); - -/* For tdb_check */ -bool is_subhash(tdb_off_t val); -enum TDB_ERROR unknown_capability(struct tdb_context *tdb, const char *caller, - tdb_off_t type); - -/* free.c: */ -enum TDB_ERROR tdb_ftable_init(struct tdb_context *tdb); - -/* check.c needs these to iterate through free lists. */ -tdb_off_t first_ftable(struct tdb_context *tdb); -tdb_off_t next_ftable(struct tdb_context *tdb, tdb_off_t ftable); - -/* This returns space or -ve error number. */ -tdb_off_t alloc(struct tdb_context *tdb, size_t keylen, size_t datalen, - uint64_t hash, unsigned magic, bool growing); - -/* Put this record in a free list. */ -enum TDB_ERROR add_free_record(struct tdb_context *tdb, - tdb_off_t off, tdb_len_t len_with_header, - enum tdb_lock_flags waitflag, - bool coalesce_ok); - -/* Set up header for a used/ftable/htable/chain/capability record. */ -enum TDB_ERROR set_header(struct tdb_context *tdb, - struct tdb_used_record *rec, - unsigned magic, uint64_t keylen, uint64_t datalen, - uint64_t actuallen, unsigned hashlow); - -/* Used by tdb_check to verify. */ -unsigned int size_to_bucket(tdb_len_t data_len); -tdb_off_t bucket_off(tdb_off_t ftable_off, unsigned bucket); - -/* Used by tdb_summary */ -tdb_off_t dead_space(struct tdb_context *tdb, tdb_off_t off); - -/* Adjust expansion, used by create_recovery_area */ -tdb_off_t tdb_expand_adjust(tdb_off_t map_size, tdb_off_t size); - -/* io.c: */ -/* Initialize tdb->methods. */ -void tdb_io_init(struct tdb_context *tdb); - -/* Convert endian of the buffer if required. */ -void *tdb_convert(const struct tdb_context *tdb, void *buf, tdb_len_t size); - -/* Unmap and try to map the tdb. */ -void tdb_munmap(struct tdb_file *file); -void tdb_mmap(struct tdb_context *tdb); - -/* Either alloc a copy, or give direct access. Release frees or noop. */ -const void *tdb_access_read(struct tdb_context *tdb, - tdb_off_t off, tdb_len_t len, bool convert); -void *tdb_access_write(struct tdb_context *tdb, - tdb_off_t off, tdb_len_t len, bool convert); - -/* Release result of tdb_access_read/write. */ -void tdb_access_release(struct tdb_context *tdb, const void *p); -/* Commit result of tdb_acces_write. */ -enum TDB_ERROR tdb_access_commit(struct tdb_context *tdb, void *p); - -/* Convenience routine to get an offset. */ -tdb_off_t tdb_read_off(struct tdb_context *tdb, tdb_off_t off); - -/* Write an offset at an offset. */ -enum TDB_ERROR tdb_write_off(struct tdb_context *tdb, tdb_off_t off, - tdb_off_t val); - -/* Clear an ondisk area. */ -enum TDB_ERROR zero_out(struct tdb_context *tdb, tdb_off_t off, tdb_len_t len); - -/* Return a non-zero offset between >= start < end in this array (or end). */ -tdb_off_t tdb_find_nonzero_off(struct tdb_context *tdb, - tdb_off_t base, - uint64_t start, - uint64_t end); - -/* Return a zero offset in this array, or num. */ -tdb_off_t tdb_find_zero_off(struct tdb_context *tdb, tdb_off_t off, - uint64_t num); - -/* Allocate and make a copy of some offset. */ -void *tdb_alloc_read(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t len); - -/* Writes a converted copy of a record. */ -enum TDB_ERROR tdb_write_convert(struct tdb_context *tdb, tdb_off_t off, - const void *rec, size_t len); - -/* Reads record and converts it */ -enum TDB_ERROR tdb_read_convert(struct tdb_context *tdb, tdb_off_t off, - void *rec, size_t len); - -/* Bump the seqnum (caller checks for tdb->flags & TDB_SEQNUM) */ -void tdb_inc_seqnum(struct tdb_context *tdb); - -/* lock.c: */ -/* Print message because another tdb owns a lock we want. */ -enum TDB_ERROR owner_conflict(struct tdb_context *tdb, const char *call); - -/* If we fork, we no longer really own locks. */ -bool check_lock_pid(struct tdb_context *tdb, const char *call, bool log); - -/* Lock/unlock a range of hashes. */ -enum TDB_ERROR tdb_lock_hashes(struct tdb_context *tdb, - tdb_off_t hash_lock, tdb_len_t hash_range, - int ltype, enum tdb_lock_flags waitflag); -enum TDB_ERROR tdb_unlock_hashes(struct tdb_context *tdb, - tdb_off_t hash_lock, - tdb_len_t hash_range, int ltype); - -/* For closing the file. */ -void tdb_lock_cleanup(struct tdb_context *tdb); - -/* Lock/unlock a particular free bucket. */ -enum TDB_ERROR tdb_lock_free_bucket(struct tdb_context *tdb, tdb_off_t b_off, - enum tdb_lock_flags waitflag); -void tdb_unlock_free_bucket(struct tdb_context *tdb, tdb_off_t b_off); - -/* Serialize transaction start. */ -enum TDB_ERROR tdb_transaction_lock(struct tdb_context *tdb, int ltype); -void tdb_transaction_unlock(struct tdb_context *tdb, int ltype); - -/* Do we have any hash locks (ie. via tdb_chainlock) ? */ -bool tdb_has_hash_locks(struct tdb_context *tdb); - -/* Lock entire database. */ -enum TDB_ERROR tdb_allrecord_lock(struct tdb_context *tdb, int ltype, - enum tdb_lock_flags flags, bool upgradable); -void tdb_allrecord_unlock(struct tdb_context *tdb, int ltype); -enum TDB_ERROR tdb_allrecord_upgrade(struct tdb_context *tdb, off_t start); - -/* Serialize db open. */ -enum TDB_ERROR tdb_lock_open(struct tdb_context *tdb, - int ltype, enum tdb_lock_flags flags); -void tdb_unlock_open(struct tdb_context *tdb, int ltype); -bool tdb_has_open_lock(struct tdb_context *tdb); - -/* Serialize db expand. */ -enum TDB_ERROR tdb_lock_expand(struct tdb_context *tdb, int ltype); -void tdb_unlock_expand(struct tdb_context *tdb, int ltype); -bool tdb_has_expansion_lock(struct tdb_context *tdb); - -/* If it needs recovery, grab all the locks and do it. */ -enum TDB_ERROR tdb_lock_and_recover(struct tdb_context *tdb); - -/* Byte-range lock wrappers for TDB1 to access. */ -enum TDB_ERROR tdb_brlock(struct tdb_context *tdb, - int rw_type, tdb_off_t offset, tdb_off_t len, - enum tdb_lock_flags flags); - -enum TDB_ERROR tdb_brunlock(struct tdb_context *tdb, - int rw_type, tdb_off_t offset, size_t len); - -enum TDB_ERROR tdb_nest_lock(struct tdb_context *tdb, - tdb_off_t offset, int ltype, - enum tdb_lock_flags flags); - -enum TDB_ERROR tdb_nest_unlock(struct tdb_context *tdb, - tdb_off_t off, int ltype); - -enum TDB_ERROR tdb_lock_gradual(struct tdb_context *tdb, - int ltype, enum tdb_lock_flags flags, - tdb_off_t off, tdb_off_t len); - -/* Default lock and unlock functions. */ -int tdb_fcntl_lock(int fd, int rw, off_t off, off_t len, bool waitflag, void *); -int tdb_fcntl_unlock(int fd, int rw, off_t off, off_t len, void *); - -/* transaction.c: */ -enum TDB_ERROR tdb_transaction_recover(struct tdb_context *tdb); -tdb_bool_err tdb_needs_recovery(struct tdb_context *tdb); - -/* this is stored at the front of every database */ -struct tdb1_header { - char magic_food[32]; /* for /etc/magic */ - uint32_t version; /* version of the code */ - uint32_t hash_size; /* number of hash entries */ - tdb1_off_t rwlocks; /* obsolete - kept to detect old formats */ - tdb1_off_t recovery_start; /* offset of transaction recovery region */ - tdb1_off_t sequence_number; /* used when TDB1_SEQNUM is set */ - uint32_t magic1_hash; /* hash of TDB_MAGIC_FOOD. */ - uint32_t magic2_hash; /* hash of TDB1_MAGIC. */ - tdb1_off_t reserved[27]; -}; - -struct tdb1_traverse_lock { - struct tdb1_traverse_lock *next; - uint32_t off; - uint32_t hash; - int lock_rw; -}; - -struct tdb_context { - /* Single list of all TDBs, to detect multiple opens. */ - struct tdb_context *next; - - /* Filename of the database. */ - const char *name; - - /* Logging function */ - void (*log_fn)(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, - void *data); - void *log_data; - - /* Open flags passed to tdb_open. */ - int open_flags; - - /* low level (fnctl) lock functions. */ - int (*lock_fn)(int fd, int rw, off_t off, off_t len, bool w, void *); - int (*unlock_fn)(int fd, int rw, off_t off, off_t len, void *); - void *lock_data; - - /* the tdb flags passed to tdb_open. */ - uint32_t flags; - - /* Our statistics. */ - struct tdb_attribute_stats stats; - - /* The actual file information */ - struct tdb_file *file; - - /* Hash function. */ - uint64_t (*hash_fn)(const void *key, size_t len, uint64_t seed, void *); - void *hash_data; - uint64_t hash_seed; - - /* Our open hook, if any. */ - enum TDB_ERROR (*openhook)(int fd, void *data); - void *openhook_data; - - /* Last error we returned. */ - enum TDB_ERROR last_error; - - struct { - - /* Are we accessing directly? (debugging check). */ - int direct_access; - - /* Set if we are in a transaction. */ - struct tdb_transaction *transaction; - - /* What free table are we using? */ - tdb_off_t ftable_off; - unsigned int ftable; - - /* IO methods: changes for transactions. */ - const struct tdb_methods *io; - - /* Direct access information */ - struct tdb_access_hdr *access; - } tdb2; - - struct { - int traverse_read; /* read-only traversal */ - int traverse_write; /* read-write traversal */ - - struct tdb1_header header; /* a cached copy of the header */ - struct tdb1_traverse_lock travlocks; /* current traversal locks */ - const struct tdb1_methods *io; - struct tdb1_transaction *transaction; - int page_size; - int max_dead_records; - } tdb1; -}; - -#define TDB1_BYTEREV(x) (((((x)&0xff)<<24)|((x)&0xFF00)<<8)|(((x)>>8)&0xFF00)|((x)>>24)) - -/* tdb1_check.c: */ -int tdb1_check(struct tdb_context *tdb, - enum TDB_ERROR (*check)(TDB_DATA key, TDB_DATA data, void *), - void *private_data); - - -/* tdb1_open.c: */ -enum TDB_ERROR tdb1_new_database(struct tdb_context *tdb, - struct tdb_attribute_tdb1_hashsize *hashsize, - struct tdb_attribute_tdb1_max_dead *max_dead); -enum TDB_ERROR tdb1_open(struct tdb_context *tdb, - struct tdb_attribute_tdb1_max_dead *max_dead); - -/* tdb1_io.c: */ -enum TDB_ERROR tdb1_probe_length(struct tdb_context *tdb); - -/* tdb1_lock.c: */ -int tdb1_allrecord_lock(struct tdb_context *tdb, int ltype, - enum tdb_lock_flags flags, bool upgradable); -int tdb1_allrecord_unlock(struct tdb_context *tdb, int ltype); - -int tdb1_chainlock(struct tdb_context *tdb, TDB_DATA key); -int tdb1_chainunlock(struct tdb_context *tdb, TDB_DATA key); -int tdb1_chainlock_read(struct tdb_context *tdb, TDB_DATA key); -int tdb1_chainunlock_read(struct tdb_context *tdb, TDB_DATA key); - -/* tdb1_transaction.c: */ -int tdb1_transaction_recover(struct tdb_context *tdb); -int tdb1_transaction_cancel(struct tdb_context *tdb); - -/* tdb1_traverse.c: */ -int tdb1_traverse(struct tdb_context *tdb, - int (*)(struct tdb_context *, TDB_DATA, TDB_DATA, void *), - void *private_data); - -/* tdb1_summary.c: */ -char *tdb1_summary(struct tdb_context *tdb); - -/* tdb1_tdb.c: */ -int tdb1_store(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, int flag); -enum TDB_ERROR tdb1_fetch(struct tdb_context *tdb, TDB_DATA key, - TDB_DATA *data); -int tdb1_append(struct tdb_context *tdb, TDB_DATA key, TDB_DATA new_dbuf); -int tdb1_delete(struct tdb_context *tdb, TDB_DATA key); -int tdb1_exists(struct tdb_context *tdb, TDB_DATA key); -enum TDB_ERROR tdb1_parse_record(struct tdb_context *tdb, TDB_DATA key, - enum TDB_ERROR (*parser)(TDB_DATA key, - TDB_DATA data, - void *private_data), - void *private_data); -void tdb1_increment_seqnum_nonblock(struct tdb_context *tdb); -int tdb1_get_seqnum(struct tdb_context *tdb); -int tdb1_wipe_all(struct tdb_context *tdb); - -/* tdb1_transaction.c: */ -int tdb1_transaction_start(struct tdb_context *tdb); -int tdb1_transaction_prepare_commit(struct tdb_context *tdb); -int tdb1_transaction_commit(struct tdb_context *tdb); - -/* tdb1_traverse.c: */ -TDB_DATA tdb1_firstkey(struct tdb_context *tdb); -TDB_DATA tdb1_nextkey(struct tdb_context *tdb, TDB_DATA key); - -/* tdb.c: */ -enum TDB_ERROR COLD tdb_logerr(struct tdb_context *tdb, - enum TDB_ERROR ecode, - enum tdb_log_level level, - const char *fmt, ...); - -#ifdef TDB_TRACE -void tdb_trace(struct tdb_context *tdb, const char *op); -void tdb_trace_seqnum(struct tdb_context *tdb, uint32_t seqnum, const char *op); -void tdb_trace_open(struct tdb_context *tdb, const char *op, - unsigned hash_size, unsigned tdb_flags, unsigned open_flags); -void tdb_trace_ret(struct tdb_context *tdb, const char *op, int ret); -void tdb_trace_retrec(struct tdb_context *tdb, const char *op, TDB_DATA ret); -void tdb_trace_1rec(struct tdb_context *tdb, const char *op, - TDB_DATA rec); -void tdb_trace_1rec_ret(struct tdb_context *tdb, const char *op, - TDB_DATA rec, int ret); -void tdb_trace_1rec_retrec(struct tdb_context *tdb, const char *op, - TDB_DATA rec, TDB_DATA ret); -void tdb_trace_2rec_flag_ret(struct tdb_context *tdb, const char *op, - TDB_DATA rec1, TDB_DATA rec2, unsigned flag, - int ret); -void tdb_trace_2rec_retrec(struct tdb_context *tdb, const char *op, - TDB_DATA rec1, TDB_DATA rec2, TDB_DATA ret); -#else -#define tdb_trace(tdb, op) -#define tdb_trace_seqnum(tdb, seqnum, op) -#define tdb_trace_open(tdb, op, hash_size, tdb_flags, open_flags) -#define tdb_trace_ret(tdb, op, ret) -#define tdb_trace_retrec(tdb, op, ret) -#define tdb_trace_1rec(tdb, op, rec) -#define tdb_trace_1rec_ret(tdb, op, rec, ret) -#define tdb_trace_1rec_retrec(tdb, op, rec, ret) -#define tdb_trace_2rec_flag_ret(tdb, op, rec1, rec2, flag, ret) -#define tdb_trace_2rec_retrec(tdb, op, rec1, rec2, ret) -#endif /* !TDB_TRACE */ - -#endif diff --git a/ccan/tdb2/summary.c b/ccan/tdb2/summary.c deleted file mode 100644 index f3a3a085..00000000 --- a/ccan/tdb2/summary.c +++ /dev/null @@ -1,356 +0,0 @@ - /* - Trivial Database 2: human-readable summary code - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "private.h" -#include -#include - -#define SUMMARY_FORMAT \ - "Size of file/data: %zu/%zu\n" \ - "Number of records: %zu\n" \ - "Smallest/average/largest keys: %zu/%zu/%zu\n%s" \ - "Smallest/average/largest data: %zu/%zu/%zu\n%s" \ - "Smallest/average/largest padding: %zu/%zu/%zu\n%s" \ - "Number of free records: %zu\n" \ - "Smallest/average/largest free records: %zu/%zu/%zu\n%s" \ - "Number of uncoalesced records: %zu\n" \ - "Smallest/average/largest uncoalesced runs: %zu/%zu/%zu\n%s" \ - "Toplevel hash used: %u of %u\n" \ - "Number of chains: %zu\n" \ - "Number of subhashes: %zu\n" \ - "Smallest/average/largest subhash entries: %zu/%zu/%zu\n%s" \ - "Percentage keys/data/padding/free/rechdrs/freehdrs/hashes: %.0f/%.0f/%.0f/%.0f/%.0f/%.0f/%.0f\n" - -#define BUCKET_SUMMARY_FORMAT_A \ - "Free bucket %zu: total entries %zu.\n" \ - "Smallest/average/largest length: %zu/%zu/%zu\n%s" -#define BUCKET_SUMMARY_FORMAT_B \ - "Free bucket %zu-%zu: total entries %zu.\n" \ - "Smallest/average/largest length: %zu/%zu/%zu\n%s" -#define CAPABILITY_FORMAT \ - "Capability %llu%s\n" - -#define HISTO_WIDTH 70 -#define HISTO_HEIGHT 20 - -static tdb_off_t count_hash(struct tdb_context *tdb, - tdb_off_t hash_off, unsigned bits) -{ - const tdb_off_t *h; - tdb_off_t count = 0; - unsigned int i; - - h = tdb_access_read(tdb, hash_off, sizeof(*h) << bits, true); - if (TDB_PTR_IS_ERR(h)) { - return TDB_ERR_TO_OFF(TDB_PTR_ERR(h)); - } - for (i = 0; i < (1 << bits); i++) - count += (h[i] != 0); - - tdb_access_release(tdb, h); - return count; -} - -static enum TDB_ERROR summarize(struct tdb_context *tdb, - struct tally *hashes, - struct tally *ftables, - struct tally *fr, - struct tally *keys, - struct tally *data, - struct tally *extra, - struct tally *uncoal, - struct tally *chains) -{ - tdb_off_t off; - tdb_len_t len; - tdb_len_t unc = 0; - - for (off = sizeof(struct tdb_header); - off < tdb->file->map_size; - off += len) { - const union { - struct tdb_used_record u; - struct tdb_free_record f; - struct tdb_recovery_record r; - } *p; - /* We might not be able to get the whole thing. */ - p = tdb_access_read(tdb, off, sizeof(p->f), true); - if (TDB_PTR_IS_ERR(p)) { - return TDB_PTR_ERR(p); - } - if (frec_magic(&p->f) != TDB_FREE_MAGIC) { - if (unc > 1) { - tally_add(uncoal, unc); - unc = 0; - } - } - - if (p->r.magic == TDB_RECOVERY_INVALID_MAGIC - || p->r.magic == TDB_RECOVERY_MAGIC) { - len = sizeof(p->r) + p->r.max_len; - } else if (frec_magic(&p->f) == TDB_FREE_MAGIC) { - len = frec_len(&p->f); - tally_add(fr, len); - len += sizeof(p->u); - unc++; - } else if (rec_magic(&p->u) == TDB_USED_MAGIC) { - len = sizeof(p->u) - + rec_key_length(&p->u) - + rec_data_length(&p->u) - + rec_extra_padding(&p->u); - - tally_add(keys, rec_key_length(&p->u)); - tally_add(data, rec_data_length(&p->u)); - tally_add(extra, rec_extra_padding(&p->u)); - } else if (rec_magic(&p->u) == TDB_HTABLE_MAGIC) { - tdb_off_t count = count_hash(tdb, - off + sizeof(p->u), - TDB_SUBLEVEL_HASH_BITS); - if (TDB_OFF_IS_ERR(count)) { - return TDB_OFF_TO_ERR(count); - } - tally_add(hashes, count); - tally_add(extra, rec_extra_padding(&p->u)); - len = sizeof(p->u) - + rec_data_length(&p->u) - + rec_extra_padding(&p->u); - } else if (rec_magic(&p->u) == TDB_FTABLE_MAGIC) { - len = sizeof(p->u) - + rec_data_length(&p->u) - + rec_extra_padding(&p->u); - tally_add(ftables, rec_data_length(&p->u)); - tally_add(extra, rec_extra_padding(&p->u)); - } else if (rec_magic(&p->u) == TDB_CHAIN_MAGIC) { - len = sizeof(p->u) - + rec_data_length(&p->u) - + rec_extra_padding(&p->u); - tally_add(chains, 1); - tally_add(extra, rec_extra_padding(&p->u)); - } else { - len = dead_space(tdb, off); - if (TDB_OFF_IS_ERR(len)) { - return TDB_OFF_TO_ERR(len); - } - } - tdb_access_release(tdb, p); - } - if (unc) - tally_add(uncoal, unc); - return TDB_SUCCESS; -} - -static size_t num_capabilities(struct tdb_context *tdb) -{ - tdb_off_t off, next; - const struct tdb_capability *cap; - size_t count = 0; - - off = tdb_read_off(tdb, offsetof(struct tdb_header, capabilities)); - if (TDB_OFF_IS_ERR(off)) - return count; - - /* Count capability list. */ - for (; off; off = next) { - cap = tdb_access_read(tdb, off, sizeof(*cap), true); - if (TDB_PTR_IS_ERR(cap)) { - break; - } - count++; - next = cap->next; - tdb_access_release(tdb, cap); - } - return count; -} - -static void add_capabilities(struct tdb_context *tdb, size_t num, char *summary) -{ - tdb_off_t off, next; - const struct tdb_capability *cap; - size_t count = 0; - - /* Append to summary. */ - summary += strlen(summary); - - off = tdb_read_off(tdb, offsetof(struct tdb_header, capabilities)); - if (TDB_OFF_IS_ERR(off)) - return; - - /* Walk capability list. */ - for (; off; off = next) { - cap = tdb_access_read(tdb, off, sizeof(*cap), true); - if (TDB_PTR_IS_ERR(cap)) { - break; - } - count++; - sprintf(summary, CAPABILITY_FORMAT, - cap->type & TDB_CAP_TYPE_MASK, - /* Noopen? How did we get here? */ - (cap->type & TDB_CAP_NOOPEN) ? " (unopenable)" - : ((cap->type & TDB_CAP_NOWRITE) - && (cap->type & TDB_CAP_NOCHECK)) ? " (uncheckable,read-only)" - : (cap->type & TDB_CAP_NOWRITE) ? " (read-only)" - : (cap->type & TDB_CAP_NOCHECK) ? " (uncheckable)" - : ""); - summary += strlen(summary); - next = cap->next; - tdb_access_release(tdb, cap); - } -} - -enum TDB_ERROR tdb_summary(struct tdb_context *tdb, - enum tdb_summary_flags flags, - char **summary) -{ - tdb_len_t len; - size_t num_caps; - struct tally *ftables, *hashes, *freet, *keys, *data, *extra, *uncoal, - *chains; - char *hashesg, *freeg, *keysg, *datag, *extrag, *uncoalg; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_VERSION1) { - /* tdb1 doesn't do graphs. */ - *summary = tdb1_summary(tdb); - if (!*summary) - return tdb->last_error; - return TDB_SUCCESS; - } - - hashesg = freeg = keysg = datag = extrag = uncoalg = NULL; - - ecode = tdb_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_WAIT, false); - if (ecode != TDB_SUCCESS) { - return tdb->last_error = ecode; - } - - ecode = tdb_lock_expand(tdb, F_RDLCK); - if (ecode != TDB_SUCCESS) { - tdb_allrecord_unlock(tdb, F_RDLCK); - return tdb->last_error = ecode; - } - - /* Start stats off empty. */ - ftables = tally_new(HISTO_HEIGHT); - hashes = tally_new(HISTO_HEIGHT); - freet = tally_new(HISTO_HEIGHT); - keys = tally_new(HISTO_HEIGHT); - data = tally_new(HISTO_HEIGHT); - extra = tally_new(HISTO_HEIGHT); - uncoal = tally_new(HISTO_HEIGHT); - chains = tally_new(HISTO_HEIGHT); - if (!ftables || !hashes || !freet || !keys || !data || !extra - || !uncoal || !chains) { - ecode = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_summary: failed to allocate" - " tally structures"); - goto unlock; - } - - ecode = summarize(tdb, hashes, ftables, freet, keys, data, extra, - uncoal, chains); - if (ecode != TDB_SUCCESS) { - goto unlock; - } - - if (flags & TDB_SUMMARY_HISTOGRAMS) { - hashesg = tally_histogram(hashes, HISTO_WIDTH, HISTO_HEIGHT); - freeg = tally_histogram(freet, HISTO_WIDTH, HISTO_HEIGHT); - keysg = tally_histogram(keys, HISTO_WIDTH, HISTO_HEIGHT); - datag = tally_histogram(data, HISTO_WIDTH, HISTO_HEIGHT); - extrag = tally_histogram(extra, HISTO_WIDTH, HISTO_HEIGHT); - uncoalg = tally_histogram(uncoal, HISTO_WIDTH, HISTO_HEIGHT); - } - - num_caps = num_capabilities(tdb); - - /* 20 is max length of a %llu. */ - len = strlen(SUMMARY_FORMAT) + 33*20 + 1 - + (hashesg ? strlen(hashesg) : 0) - + (freeg ? strlen(freeg) : 0) - + (keysg ? strlen(keysg) : 0) - + (datag ? strlen(datag) : 0) - + (extrag ? strlen(extrag) : 0) - + (uncoalg ? strlen(uncoalg) : 0) - + num_caps * (strlen(CAPABILITY_FORMAT) + 20*4); - - *summary = malloc(len); - if (!*summary) { - ecode = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_summary: failed to allocate string"); - goto unlock; - } - - sprintf(*summary, SUMMARY_FORMAT, - (size_t)tdb->file->map_size, - tally_total(keys, NULL) + tally_total(data, NULL), - tally_num(keys), - tally_min(keys), tally_mean(keys), tally_max(keys), - keysg ? keysg : "", - tally_min(data), tally_mean(data), tally_max(data), - datag ? datag : "", - tally_min(extra), tally_mean(extra), tally_max(extra), - extrag ? extrag : "", - tally_num(freet), - tally_min(freet), tally_mean(freet), tally_max(freet), - freeg ? freeg : "", - tally_total(uncoal, NULL), - tally_min(uncoal), tally_mean(uncoal), tally_max(uncoal), - uncoalg ? uncoalg : "", - (unsigned)count_hash(tdb, offsetof(struct tdb_header, - hashtable), - TDB_TOPLEVEL_HASH_BITS), - 1 << TDB_TOPLEVEL_HASH_BITS, - tally_num(chains), - tally_num(hashes), - tally_min(hashes), tally_mean(hashes), tally_max(hashes), - hashesg ? hashesg : "", - tally_total(keys, NULL) * 100.0 / tdb->file->map_size, - tally_total(data, NULL) * 100.0 / tdb->file->map_size, - tally_total(extra, NULL) * 100.0 / tdb->file->map_size, - tally_total(freet, NULL) * 100.0 / tdb->file->map_size, - (tally_num(keys) + tally_num(freet) + tally_num(hashes)) - * sizeof(struct tdb_used_record) * 100.0 / tdb->file->map_size, - tally_num(ftables) * sizeof(struct tdb_freetable) - * 100.0 / tdb->file->map_size, - (tally_num(hashes) - * (sizeof(tdb_off_t) << TDB_SUBLEVEL_HASH_BITS) - + (sizeof(tdb_off_t) << TDB_TOPLEVEL_HASH_BITS) - + sizeof(struct tdb_chain) * tally_num(chains)) - * 100.0 / tdb->file->map_size); - - add_capabilities(tdb, num_caps, *summary); - -unlock: - free(hashesg); - free(freeg); - free(keysg); - free(datag); - free(extrag); - free(uncoalg); - free(hashes); - free(freet); - free(keys); - free(data); - free(extra); - free(uncoal); - free(ftables); - free(chains); - - tdb_allrecord_unlock(tdb, F_RDLCK); - tdb_unlock_expand(tdb, F_RDLCK); - return tdb->last_error = ecode; -} diff --git a/ccan/tdb2/tdb.c b/ccan/tdb2/tdb.c deleted file mode 100644 index 62607bf1..00000000 --- a/ccan/tdb2/tdb.c +++ /dev/null @@ -1,642 +0,0 @@ - /* - Trivial Database 2: fetch, store and misc routines. - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "private.h" -#include -#include - -static enum TDB_ERROR update_rec_hdr(struct tdb_context *tdb, - tdb_off_t off, - tdb_len_t keylen, - tdb_len_t datalen, - struct tdb_used_record *rec, - uint64_t h) -{ - uint64_t dataroom = rec_data_length(rec) + rec_extra_padding(rec); - enum TDB_ERROR ecode; - - ecode = set_header(tdb, rec, TDB_USED_MAGIC, keylen, datalen, - keylen + dataroom, h); - if (ecode == TDB_SUCCESS) { - ecode = tdb_write_convert(tdb, off, rec, sizeof(*rec)); - } - return ecode; -} - -static enum TDB_ERROR replace_data(struct tdb_context *tdb, - struct hash_info *h, - struct tdb_data key, struct tdb_data dbuf, - tdb_off_t old_off, tdb_len_t old_room, - bool growing) -{ - tdb_off_t new_off; - enum TDB_ERROR ecode; - - /* Allocate a new record. */ - new_off = alloc(tdb, key.dsize, dbuf.dsize, h->h, TDB_USED_MAGIC, - growing); - if (TDB_OFF_IS_ERR(new_off)) { - return TDB_OFF_TO_ERR(new_off); - } - - /* We didn't like the existing one: remove it. */ - if (old_off) { - tdb->stats.frees++; - ecode = add_free_record(tdb, old_off, - sizeof(struct tdb_used_record) - + key.dsize + old_room, - TDB_LOCK_WAIT, true); - if (ecode == TDB_SUCCESS) - ecode = replace_in_hash(tdb, h, new_off); - } else { - ecode = add_to_hash(tdb, h, new_off); - } - if (ecode != TDB_SUCCESS) { - return ecode; - } - - new_off += sizeof(struct tdb_used_record); - ecode = tdb->tdb2.io->twrite(tdb, new_off, key.dptr, key.dsize); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - new_off += key.dsize; - ecode = tdb->tdb2.io->twrite(tdb, new_off, dbuf.dptr, dbuf.dsize); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - if (tdb->flags & TDB_SEQNUM) - tdb_inc_seqnum(tdb); - - return TDB_SUCCESS; -} - -static enum TDB_ERROR update_data(struct tdb_context *tdb, - tdb_off_t off, - struct tdb_data dbuf, - tdb_len_t extra) -{ - enum TDB_ERROR ecode; - - ecode = tdb->tdb2.io->twrite(tdb, off, dbuf.dptr, dbuf.dsize); - if (ecode == TDB_SUCCESS && extra) { - /* Put a zero in; future versions may append other data. */ - ecode = tdb->tdb2.io->twrite(tdb, off + dbuf.dsize, "", 1); - } - if (tdb->flags & TDB_SEQNUM) - tdb_inc_seqnum(tdb); - - return ecode; -} - -enum TDB_ERROR tdb_store(struct tdb_context *tdb, - struct tdb_data key, struct tdb_data dbuf, int flag) -{ - struct hash_info h; - tdb_off_t off; - tdb_len_t old_room = 0; - struct tdb_used_record rec; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_VERSION1) { - if (tdb1_store(tdb, key, dbuf, flag) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - - off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL); - if (TDB_OFF_IS_ERR(off)) { - return tdb->last_error = TDB_OFF_TO_ERR(off); - } - - /* Now we have lock on this hash bucket. */ - if (flag == TDB_INSERT) { - if (off) { - ecode = TDB_ERR_EXISTS; - goto out; - } - } else { - if (off) { - old_room = rec_data_length(&rec) - + rec_extra_padding(&rec); - if (old_room >= dbuf.dsize) { - /* Can modify in-place. Easy! */ - ecode = update_rec_hdr(tdb, off, - key.dsize, dbuf.dsize, - &rec, h.h); - if (ecode != TDB_SUCCESS) { - goto out; - } - ecode = update_data(tdb, - off + sizeof(rec) - + key.dsize, dbuf, - old_room - dbuf.dsize); - if (ecode != TDB_SUCCESS) { - goto out; - } - tdb_unlock_hashes(tdb, h.hlock_start, - h.hlock_range, F_WRLCK); - return tdb->last_error = TDB_SUCCESS; - } - } else { - if (flag == TDB_MODIFY) { - /* if the record doesn't exist and we - are in TDB_MODIFY mode then we should fail - the store */ - ecode = TDB_ERR_NOEXIST; - goto out; - } - } - } - - /* If we didn't use the old record, this implies we're growing. */ - ecode = replace_data(tdb, &h, key, dbuf, off, old_room, off); -out: - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK); - return tdb->last_error = ecode; -} - -enum TDB_ERROR tdb_append(struct tdb_context *tdb, - struct tdb_data key, struct tdb_data dbuf) -{ - struct hash_info h; - tdb_off_t off; - struct tdb_used_record rec; - tdb_len_t old_room = 0, old_dlen; - unsigned char *newdata; - struct tdb_data new_dbuf; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_VERSION1) { - if (tdb1_append(tdb, key, dbuf) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - - off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL); - if (TDB_OFF_IS_ERR(off)) { - return tdb->last_error = TDB_OFF_TO_ERR(off); - } - - if (off) { - old_dlen = rec_data_length(&rec); - old_room = old_dlen + rec_extra_padding(&rec); - - /* Fast path: can append in place. */ - if (rec_extra_padding(&rec) >= dbuf.dsize) { - ecode = update_rec_hdr(tdb, off, key.dsize, - old_dlen + dbuf.dsize, &rec, - h.h); - if (ecode != TDB_SUCCESS) { - goto out; - } - - off += sizeof(rec) + key.dsize + old_dlen; - ecode = update_data(tdb, off, dbuf, - rec_extra_padding(&rec)); - goto out; - } - - /* Slow path. */ - newdata = malloc(key.dsize + old_dlen + dbuf.dsize); - if (!newdata) { - ecode = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_append:" - " failed to allocate %zu bytes", - (size_t)(key.dsize + old_dlen - + dbuf.dsize)); - goto out; - } - ecode = tdb->tdb2.io->tread(tdb, off + sizeof(rec) + key.dsize, - newdata, old_dlen); - if (ecode != TDB_SUCCESS) { - goto out_free_newdata; - } - memcpy(newdata + old_dlen, dbuf.dptr, dbuf.dsize); - new_dbuf.dptr = newdata; - new_dbuf.dsize = old_dlen + dbuf.dsize; - } else { - newdata = NULL; - new_dbuf = dbuf; - } - - /* If they're using tdb_append(), it implies they're growing record. */ - ecode = replace_data(tdb, &h, key, new_dbuf, off, old_room, true); - -out_free_newdata: - free(newdata); -out: - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK); - return tdb->last_error = ecode; -} - -enum TDB_ERROR tdb_fetch(struct tdb_context *tdb, struct tdb_data key, - struct tdb_data *data) -{ - tdb_off_t off; - struct tdb_used_record rec; - struct hash_info h; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_VERSION1) - return tdb1_fetch(tdb, key, data); - - off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL); - if (TDB_OFF_IS_ERR(off)) { - return tdb->last_error = TDB_OFF_TO_ERR(off); - } - - if (!off) { - ecode = TDB_ERR_NOEXIST; - } else { - data->dsize = rec_data_length(&rec); - data->dptr = tdb_alloc_read(tdb, off + sizeof(rec) + key.dsize, - data->dsize); - if (TDB_PTR_IS_ERR(data->dptr)) { - ecode = TDB_PTR_ERR(data->dptr); - } else - ecode = TDB_SUCCESS; - } - - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK); - return tdb->last_error = ecode; -} - -bool tdb_exists(struct tdb_context *tdb, TDB_DATA key) -{ - tdb_off_t off; - struct tdb_used_record rec; - struct hash_info h; - - if (tdb->flags & TDB_VERSION1) { - return tdb1_exists(tdb, key); - } - - off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL); - if (TDB_OFF_IS_ERR(off)) { - tdb->last_error = TDB_OFF_TO_ERR(off); - return false; - } - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK); - - tdb->last_error = TDB_SUCCESS; - return off ? true : false; -} - -enum TDB_ERROR tdb_delete(struct tdb_context *tdb, struct tdb_data key) -{ - tdb_off_t off; - struct tdb_used_record rec; - struct hash_info h; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_VERSION1) { - if (tdb1_delete(tdb, key) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - - off = find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL); - if (TDB_OFF_IS_ERR(off)) { - return tdb->last_error = TDB_OFF_TO_ERR(off); - } - - if (!off) { - ecode = TDB_ERR_NOEXIST; - goto unlock; - } - - ecode = delete_from_hash(tdb, &h); - if (ecode != TDB_SUCCESS) { - goto unlock; - } - - /* Free the deleted entry. */ - tdb->stats.frees++; - ecode = add_free_record(tdb, off, - sizeof(struct tdb_used_record) - + rec_key_length(&rec) - + rec_data_length(&rec) - + rec_extra_padding(&rec), - TDB_LOCK_WAIT, true); - - if (tdb->flags & TDB_SEQNUM) - tdb_inc_seqnum(tdb); - -unlock: - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_WRLCK); - return tdb->last_error = ecode; -} - -unsigned int tdb_get_flags(struct tdb_context *tdb) -{ - return tdb->flags; -} - -static bool inside_transaction(const struct tdb_context *tdb) -{ - if (tdb->flags & TDB_VERSION1) - return tdb->tdb1.transaction != NULL; - else - return tdb->tdb2.transaction != NULL; -} - -static bool readonly_changable(struct tdb_context *tdb, const char *caller) -{ - if (inside_transaction(tdb)) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "%s: can't change" - " TDB_RDONLY inside transaction", - caller); - return false; - } - return true; -} - -void tdb_add_flag(struct tdb_context *tdb, unsigned flag) -{ - if (tdb->flags & TDB_INTERNAL) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_add_flag: internal db"); - return; - } - switch (flag) { - case TDB_NOLOCK: - tdb->flags |= TDB_NOLOCK; - break; - case TDB_NOMMAP: - tdb->flags |= TDB_NOMMAP; - tdb_munmap(tdb->file); - break; - case TDB_NOSYNC: - tdb->flags |= TDB_NOSYNC; - break; - case TDB_SEQNUM: - tdb->flags |= TDB_SEQNUM; - break; - case TDB_ALLOW_NESTING: - tdb->flags |= TDB_ALLOW_NESTING; - break; - case TDB_RDONLY: - if (readonly_changable(tdb, "tdb_add_flag")) - tdb->flags |= TDB_RDONLY; - break; - default: - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_add_flag: Unknown flag %u", - flag); - } -} - -void tdb_remove_flag(struct tdb_context *tdb, unsigned flag) -{ - if (tdb->flags & TDB_INTERNAL) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_remove_flag: internal db"); - return; - } - switch (flag) { - case TDB_NOLOCK: - tdb->flags &= ~TDB_NOLOCK; - break; - case TDB_NOMMAP: - tdb->flags &= ~TDB_NOMMAP; - tdb_mmap(tdb); - break; - case TDB_NOSYNC: - tdb->flags &= ~TDB_NOSYNC; - break; - case TDB_SEQNUM: - tdb->flags &= ~TDB_SEQNUM; - break; - case TDB_ALLOW_NESTING: - tdb->flags &= ~TDB_ALLOW_NESTING; - break; - case TDB_RDONLY: - if ((tdb->open_flags & O_ACCMODE) == O_RDONLY) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_remove_flag: can't" - " remove TDB_RDONLY on tdb" - " opened with O_RDONLY"); - break; - } - if (readonly_changable(tdb, "tdb_remove_flag")) - tdb->flags &= ~TDB_RDONLY; - break; - default: - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_remove_flag: Unknown flag %u", - flag); - } -} - -const char *tdb_errorstr(enum TDB_ERROR ecode) -{ - /* Gcc warns if you miss a case in the switch, so use that. */ - switch (TDB_ERR_TO_OFF(ecode)) { - case TDB_ERR_TO_OFF(TDB_SUCCESS): return "Success"; - case TDB_ERR_TO_OFF(TDB_ERR_CORRUPT): return "Corrupt database"; - case TDB_ERR_TO_OFF(TDB_ERR_IO): return "IO Error"; - case TDB_ERR_TO_OFF(TDB_ERR_LOCK): return "Locking error"; - case TDB_ERR_TO_OFF(TDB_ERR_OOM): return "Out of memory"; - case TDB_ERR_TO_OFF(TDB_ERR_EXISTS): return "Record exists"; - case TDB_ERR_TO_OFF(TDB_ERR_EINVAL): return "Invalid parameter"; - case TDB_ERR_TO_OFF(TDB_ERR_NOEXIST): return "Record does not exist"; - case TDB_ERR_TO_OFF(TDB_ERR_RDONLY): return "write not permitted"; - } - return "Invalid error code"; -} - -enum TDB_ERROR tdb_error(struct tdb_context *tdb) -{ - return tdb->last_error; -} - -enum TDB_ERROR COLD tdb_logerr(struct tdb_context *tdb, - enum TDB_ERROR ecode, - enum tdb_log_level level, - const char *fmt, ...) -{ - char *message; - va_list ap; - size_t len; - /* tdb_open paths care about errno, so save it. */ - int saved_errno = errno; - - if (!tdb->log_fn) - return ecode; - - va_start(ap, fmt); - len = vasprintf(&message, fmt, ap); - va_end(ap); - - if (len < 0) { - tdb->log_fn(tdb, TDB_LOG_ERROR, TDB_ERR_OOM, - "out of memory formatting message:", tdb->log_data); - tdb->log_fn(tdb, level, ecode, fmt, tdb->log_data); - } else { - tdb->log_fn(tdb, level, ecode, message, tdb->log_data); - free(message); - } - errno = saved_errno; - return ecode; -} - -enum TDB_ERROR tdb_parse_record_(struct tdb_context *tdb, - TDB_DATA key, - enum TDB_ERROR (*parse)(TDB_DATA k, - TDB_DATA d, - void *data), - void *data) -{ - tdb_off_t off; - struct tdb_used_record rec; - struct hash_info h; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_VERSION1) { - return tdb->last_error = tdb1_parse_record(tdb, key, parse, - data); - } - - off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL); - if (TDB_OFF_IS_ERR(off)) { - return tdb->last_error = TDB_OFF_TO_ERR(off); - } - - if (!off) { - ecode = TDB_ERR_NOEXIST; - } else { - const void *dptr; - dptr = tdb_access_read(tdb, off + sizeof(rec) + key.dsize, - rec_data_length(&rec), false); - if (TDB_PTR_IS_ERR(dptr)) { - ecode = TDB_PTR_ERR(dptr); - } else { - TDB_DATA d = tdb_mkdata(dptr, rec_data_length(&rec)); - - ecode = parse(key, d, data); - tdb_access_release(tdb, dptr); - } - } - - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK); - return tdb->last_error = ecode; -} - -const char *tdb_name(const struct tdb_context *tdb) -{ - return tdb->name; -} - -int64_t tdb_get_seqnum(struct tdb_context *tdb) -{ - tdb_off_t off; - - if (tdb->flags & TDB_VERSION1) { - tdb1_off_t val; - tdb->last_error = TDB_SUCCESS; - val = tdb1_get_seqnum(tdb); - - if (tdb->last_error != TDB_SUCCESS) - return TDB_ERR_TO_OFF(tdb->last_error); - else - return val; - } - - off = tdb_read_off(tdb, offsetof(struct tdb_header, seqnum)); - if (TDB_OFF_IS_ERR(off)) - tdb->last_error = TDB_OFF_TO_ERR(off); - else - tdb->last_error = TDB_SUCCESS; - return off; -} - - -int tdb_fd(const struct tdb_context *tdb) -{ - return tdb->file->fd; -} - -struct traverse_state { - enum TDB_ERROR error; - struct tdb_context *dest_db; -}; - -/* - traverse function for repacking - */ -static int repack_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, - struct traverse_state *state) -{ - state->error = tdb_store(state->dest_db, key, data, TDB_INSERT); - if (state->error != TDB_SUCCESS) { - return -1; - } - return 0; -} - -enum TDB_ERROR tdb_repack(struct tdb_context *tdb) -{ - struct tdb_context *tmp_db; - struct traverse_state state; - - state.error = tdb_transaction_start(tdb); - if (state.error != TDB_SUCCESS) { - return state.error; - } - - tmp_db = tdb_open("tmpdb", TDB_INTERNAL, O_RDWR|O_CREAT, 0, NULL); - if (tmp_db == NULL) { - state.error = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - __location__ - " Failed to create tmp_db"); - tdb_transaction_cancel(tdb); - return tdb->last_error = state.error; - } - - state.dest_db = tmp_db; - if (tdb_traverse(tdb, repack_traverse, &state) < 0) { - goto fail; - } - - state.error = tdb_wipe_all(tdb); - if (state.error != TDB_SUCCESS) { - goto fail; - } - - state.dest_db = tdb; - if (tdb_traverse(tmp_db, repack_traverse, &state) < 0) { - goto fail; - } - - tdb_close(tmp_db); - return tdb_transaction_commit(tdb); - -fail: - tdb_transaction_cancel(tdb); - tdb_close(tmp_db); - return state.error; -} diff --git a/ccan/tdb2/tdb1_check.c b/ccan/tdb2/tdb1_check.c deleted file mode 100644 index 07ee0755..00000000 --- a/ccan/tdb2/tdb1_check.c +++ /dev/null @@ -1,478 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Rusty Russell 2009 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "tdb1_private.h" - -/* Since we opened it, these shouldn't fail unless it's recent corruption. */ -static bool tdb1_check_header(struct tdb_context *tdb, tdb1_off_t *recovery) -{ - struct tdb1_header hdr; - uint32_t h1, h2; - - if (tdb->tdb1.io->tdb1_read(tdb, 0, &hdr, sizeof(hdr), 0) == -1) - return false; - if (strcmp(hdr.magic_food, TDB_MAGIC_FOOD) != 0) - goto corrupt; - - TDB1_CONV(hdr); - if (hdr.version != TDB1_VERSION) - goto corrupt; - - if (hdr.rwlocks != 0 && hdr.rwlocks != TDB1_HASH_RWLOCK_MAGIC) - goto corrupt; - - tdb1_header_hash(tdb, &h1, &h2); - if (hdr.magic1_hash && hdr.magic2_hash && - (hdr.magic1_hash != h1 || hdr.magic2_hash != h2)) - goto corrupt; - - if (hdr.hash_size == 0) - goto corrupt; - - if (hdr.hash_size != tdb->tdb1.header.hash_size) - goto corrupt; - - if (hdr.recovery_start != 0 && - hdr.recovery_start < TDB1_DATA_START(tdb->tdb1.header.hash_size)) - goto corrupt; - - *recovery = hdr.recovery_start; - return true; - -corrupt: - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Header is corrupt\n"); - return false; -} - -/* Generic record header check. */ -static bool tdb1_check_record(struct tdb_context *tdb, - tdb1_off_t off, - const struct tdb1_record *rec) -{ - tdb1_off_t tailer; - - /* Check rec->next: 0 or points to record offset, aligned. */ - if (rec->next > 0 && rec->next < TDB1_DATA_START(tdb->tdb1.header.hash_size)){ - tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Record offset %d too small next %d\n", - off, rec->next); - goto corrupt; - } - if (rec->next + sizeof(*rec) < rec->next) { - tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Record offset %d too large next %d\n", - off, rec->next); - goto corrupt; - } - if ((rec->next % TDB1_ALIGNMENT) != 0) { - tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Record offset %d misaligned next %d\n", - off, rec->next); - goto corrupt; - } - if (tdb->tdb1.io->tdb1_oob(tdb, rec->next, sizeof(*rec), 0)) - goto corrupt; - - /* Check rec_len: similar to rec->next, implies next record. */ - if ((rec->rec_len % TDB1_ALIGNMENT) != 0) { - tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Record offset %d misaligned length %d\n", - off, rec->rec_len); - goto corrupt; - } - /* Must fit tailer. */ - if (rec->rec_len < sizeof(tailer)) { - tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Record offset %d too short length %d\n", - off, rec->rec_len); - goto corrupt; - } - /* OOB allows "right at the end" access, so this works for last rec. */ - if (tdb->tdb1.io->tdb1_oob(tdb, off, sizeof(*rec)+rec->rec_len, 0)) - goto corrupt; - - /* Check tailer. */ - if (tdb1_ofs_read(tdb, off+sizeof(*rec)+rec->rec_len-sizeof(tailer), - &tailer) == -1) - goto corrupt; - if (tailer != sizeof(*rec) + rec->rec_len) { - tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Record offset %d invalid tailer\n", off); - goto corrupt; - } - - return true; - -corrupt: - tdb->last_error = TDB_ERR_CORRUPT; - return false; -} - -/* Grab some bytes: may copy if can't use mmap. - Caller has already done bounds check. */ -static TDB_DATA get_bytes(struct tdb_context *tdb, - tdb1_off_t off, tdb1_len_t len) -{ - TDB_DATA d; - - d.dsize = len; - - if (tdb->tdb1.transaction == NULL && tdb->file->map_ptr != NULL) - d.dptr = (unsigned char *)tdb->file->map_ptr + off; - else - d.dptr = tdb1_alloc_read(tdb, off, d.dsize); - return d; -} - -/* Frees data if we're not able to simply use mmap. */ -static void put_bytes(struct tdb_context *tdb, TDB_DATA d) -{ - if (tdb->tdb1.transaction == NULL && tdb->file->map_ptr != NULL) - return; - free(d.dptr); -} - -/* We use the excellent Jenkins lookup3 hash; this is based on hash_word2. - * See: http://burtleburtle.net/bob/c/lookup3.c - */ -#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) -static void jhash(uint32_t key, uint32_t *pc, uint32_t *pb) -{ - uint32_t a,b,c; - - /* Set up the internal state */ - a = b = c = 0xdeadbeef + *pc; - c += *pb; - a += key; - c ^= b; c -= rot(b,14); - a ^= c; a -= rot(c,11); - b ^= a; b -= rot(a,25); - c ^= b; c -= rot(b,16); - a ^= c; a -= rot(c,4); - b ^= a; b -= rot(a,14); - c ^= b; c -= rot(b,24); - *pc=c; *pb=b; -} - -/* - We want to check that all free records are in the free list - (only once), and all free list entries are free records. Similarly - for each hash chain of used records. - - Doing that naively (without walking hash chains, since we want to be - linear) means keeping a list of records which have been seen in each - hash chain, and another of records pointed to (ie. next pointers - from records and the initial hash chain heads). These two lists - should be equal. This will take 8 bytes per record, and require - sorting at the end. - - So instead, we record each offset in a bitmap such a way that - recording it twice will cancel out. Since each offset should appear - exactly twice, the bitmap should be zero at the end. - - The approach was inspired by Bloom Filters (see Wikipedia). For - each value, we flip K bits in a bitmap of size N. The number of - distinct arrangements is: - - N! / (K! * (N-K)!) - - Of course, not all arrangements are actually distinct, but testing - shows this formula to be close enough. - - So, if K == 8 and N == 256, the probability of two things flipping the same - bits is 1 in 409,663,695,276,000. - - Given that ldb uses a hash size of 10000, using 32 bytes per hash chain - (320k) seems reasonable. -*/ -#define NUM_HASHES 8 -#define BITMAP_BITS 256 - -static void bit_flip(unsigned char bits[], unsigned int idx) -{ - bits[idx / CHAR_BIT] ^= (1 << (idx % CHAR_BIT)); -} - -/* We record offsets in a bitmap for the particular chain it should be in. */ -static void record_offset(unsigned char bits[], tdb1_off_t off) -{ - uint32_t h1 = off, h2 = 0; - unsigned int i; - - /* We get two good hash values out of jhash2, so we use both. Then - * we keep going to produce further hash values. */ - for (i = 0; i < NUM_HASHES / 2; i++) { - jhash(off, &h1, &h2); - bit_flip(bits, h1 % BITMAP_BITS); - bit_flip(bits, h2 % BITMAP_BITS); - h2++; - } -} - -/* Check that an in-use record is valid. */ -static bool tdb1_check_used_record(struct tdb_context *tdb, - tdb1_off_t off, - const struct tdb1_record *rec, - unsigned char **hashes, - enum TDB_ERROR (*check)(TDB_DATA, TDB_DATA, - void *), - void *private_data) -{ - TDB_DATA key, data; - - if (!tdb1_check_record(tdb, off, rec)) - return false; - - /* key + data + tailer must fit in record */ - if (rec->key_len + rec->data_len + sizeof(tdb1_off_t) > rec->rec_len) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Record offset %d too short for contents\n", off); - return false; - } - - key = get_bytes(tdb, off + sizeof(*rec), rec->key_len); - if (!key.dptr) - return false; - - if ((uint32_t)tdb_hash(tdb, key.dptr, key.dsize) != rec->full_hash) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Record offset %d has incorrect hash\n", off); - goto fail_put_key; - } - - /* Mark this offset as a known value for this hash bucket. */ - record_offset(hashes[TDB1_BUCKET(rec->full_hash)+1], off); - /* And similarly if the next pointer is valid. */ - if (rec->next) - record_offset(hashes[TDB1_BUCKET(rec->full_hash)+1], rec->next); - - /* If they supply a check function and this record isn't dead, - get data and feed it. */ - if (check && rec->magic != TDB1_DEAD_MAGIC) { - enum TDB_ERROR ecode; - - data = get_bytes(tdb, off + sizeof(*rec) + rec->key_len, - rec->data_len); - if (!data.dptr) - goto fail_put_key; - - ecode = check(key, data, private_data); - if (ecode != TDB_SUCCESS) { - tdb->last_error = ecode; - goto fail_put_data; - } - put_bytes(tdb, data); - } - - put_bytes(tdb, key); - return true; - -fail_put_data: - put_bytes(tdb, data); -fail_put_key: - put_bytes(tdb, key); - return false; -} - -/* Check that an unused record is valid. */ -static bool tdb1_check_free_record(struct tdb_context *tdb, - tdb1_off_t off, - const struct tdb1_record *rec, - unsigned char **hashes) -{ - if (!tdb1_check_record(tdb, off, rec)) - return false; - - /* Mark this offset as a known value for the free list. */ - record_offset(hashes[0], off); - /* And similarly if the next pointer is valid. */ - if (rec->next) - record_offset(hashes[0], rec->next); - return true; -} - -/* Slow, but should be very rare. */ -size_t tdb1_dead_space(struct tdb_context *tdb, tdb1_off_t off) -{ - size_t len; - - for (len = 0; off + len < tdb->file->map_size; len++) { - char c; - if (tdb->tdb1.io->tdb1_read(tdb, off, &c, 1, 0)) - return 0; - if (c != 0 && c != 0x42) - break; - } - return len; -} - -int tdb1_check(struct tdb_context *tdb, - enum TDB_ERROR (*check)(TDB_DATA key, TDB_DATA data, void *), - void *private_data) -{ - unsigned int h; - unsigned char **hashes; - tdb1_off_t off, recovery_start; - struct tdb1_record rec; - bool found_recovery = false; - tdb1_len_t dead; - bool locked; - size_t alloc_len; - - /* We may have a write lock already, so don't re-lock. */ - if (tdb->file->allrecord_lock.count != 0) { - locked = false; - } else { - if (tdb_lockall_read(tdb) != TDB_SUCCESS) - return -1; - locked = true; - } - - /* Make sure we know true size of the underlying file. */ - tdb->tdb1.io->tdb1_oob(tdb, tdb->file->map_size, 1, 1); - - /* Header must be OK: also gets us the recovery ptr, if any. */ - if (!tdb1_check_header(tdb, &recovery_start)) - goto unlock; - - /* We should have the whole header, too. */ - if (tdb->file->map_size < TDB1_DATA_START(tdb->tdb1.header.hash_size)) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "File too short for hashes\n"); - goto unlock; - } - - /* One big malloc: pointers then bit arrays. */ - alloc_len = sizeof(hashes[0]) * (1+tdb->tdb1.header.hash_size) - + BITMAP_BITS / CHAR_BIT * (1+tdb->tdb1.header.hash_size); - hashes = (unsigned char **)calloc(1, alloc_len); - if (!hashes) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_check: could not allocate %zu", - alloc_len); - goto unlock; - } - - /* Initialize pointers */ - hashes[0] = (unsigned char *)(&hashes[1+tdb->tdb1.header.hash_size]); - for (h = 1; h < 1+tdb->tdb1.header.hash_size; h++) - hashes[h] = hashes[h-1] + BITMAP_BITS / CHAR_BIT; - - /* Freelist and hash headers are all in a row: read them. */ - for (h = 0; h < 1+tdb->tdb1.header.hash_size; h++) { - if (tdb1_ofs_read(tdb, TDB1_FREELIST_TOP + h*sizeof(tdb1_off_t), - &off) == -1) - goto free; - if (off) - record_offset(hashes[h], off); - } - - /* For each record, read it in and check it's ok. */ - for (off = TDB1_DATA_START(tdb->tdb1.header.hash_size); - off < tdb->file->map_size; - off += sizeof(rec) + rec.rec_len) { - if (tdb->tdb1.io->tdb1_read(tdb, off, &rec, sizeof(rec), - TDB1_DOCONV()) == -1) - goto free; - switch (rec.magic) { - case TDB1_MAGIC: - case TDB1_DEAD_MAGIC: - if (!tdb1_check_used_record(tdb, off, &rec, hashes, - check, private_data)) - goto free; - break; - case TDB1_FREE_MAGIC: - if (!tdb1_check_free_record(tdb, off, &rec, hashes)) - goto free; - break; - /* If we crash after ftruncate, we can get zeroes or fill. */ - case TDB1_RECOVERY_INVALID_MAGIC: - case 0x42424242: - if (recovery_start == off) { - found_recovery = true; - break; - } - dead = tdb1_dead_space(tdb, off); - if (dead < sizeof(rec)) - goto corrupt; - - tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING, - "Dead space at %d-%d (of %u)\n", - off, off + dead, tdb->file->map_size); - rec.rec_len = dead - sizeof(rec); - break; - case TDB1_RECOVERY_MAGIC: - if (recovery_start != off) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Unexpected recovery record at offset %d\n", - off); - goto free; - } - found_recovery = true; - break; - default: ; - corrupt: - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Bad magic 0x%x at offset %d\n", - rec.magic, off); - goto free; - } - } - - /* Now, hashes should all be empty: each record exists and is referred - * to by one other. */ - for (h = 0; h < 1+tdb->tdb1.header.hash_size; h++) { - unsigned int i; - for (i = 0; i < BITMAP_BITS / CHAR_BIT; i++) { - if (hashes[h][i] != 0) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Hashes do not match records\n"); - goto free; - } - } - } - - /* We must have found recovery area if there was one. */ - if (recovery_start != 0 && !found_recovery) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "Expected a recovery area at %u\n", - recovery_start); - goto free; - } - - free(hashes); - if (locked) { - tdb_unlockall_read(tdb); - } - return 0; - -free: - free(hashes); -unlock: - if (locked) { - tdb_unlockall_read(tdb); - } - return -1; -} diff --git a/ccan/tdb2/tdb1_freelist.c b/ccan/tdb2/tdb1_freelist.c deleted file mode 100644 index ea368ec4..00000000 --- a/ccan/tdb2/tdb1_freelist.c +++ /dev/null @@ -1,322 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 1999-2005 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000-2003 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#include "tdb1_private.h" - -/* read a freelist record and check for simple errors */ -int tdb1_rec_free_read(struct tdb_context *tdb, tdb1_off_t off, struct tdb1_record *rec) -{ - if (tdb->tdb1.io->tdb1_read(tdb, off, rec, sizeof(*rec),TDB1_DOCONV()) == -1) - return -1; - - if (rec->magic == TDB1_MAGIC) { - /* this happens when a app is showdown while deleting a record - we should - not completely fail when this happens */ - tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_WARNING, - "tdb1_rec_free_read non-free magic 0x%x at offset=%d - fixing\n", - rec->magic, off); - rec->magic = TDB1_FREE_MAGIC; - if (tdb->tdb1.io->tdb1_write(tdb, off, rec, sizeof(*rec)) == -1) - return -1; - } - - if (rec->magic != TDB1_FREE_MAGIC) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb1_rec_free_read bad magic 0x%x at offset=%d\n", - rec->magic, off); - return -1; - } - if (tdb->tdb1.io->tdb1_oob(tdb, rec->next, sizeof(*rec), 0) != 0) - return -1; - return 0; -} - - -/* update a record tailer (must hold allocation lock) */ -static int update_tailer(struct tdb_context *tdb, tdb1_off_t offset, - const struct tdb1_record *rec) -{ - tdb1_off_t totalsize; - - /* Offset of tailer from record header */ - totalsize = sizeof(*rec) + rec->rec_len; - return tdb1_ofs_write(tdb, offset + totalsize - sizeof(tdb1_off_t), - &totalsize); -} - -/* Add an element into the freelist. Merge adjacent records if - necessary. */ -int tdb1_free(struct tdb_context *tdb, tdb1_off_t offset, struct tdb1_record *rec) -{ - /* Allocation and tailer lock */ - if (tdb1_lock(tdb, -1, F_WRLCK) != 0) - return -1; - - /* set an initial tailer, so if we fail we don't leave a bogus record */ - if (update_tailer(tdb, offset, rec) != 0) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb_free: update_tailer failed!\n"); - goto fail; - } - - tdb->stats.alloc_coalesce_tried++; - /* Look left */ - if (offset - sizeof(tdb1_off_t) > TDB1_DATA_START(tdb->tdb1.header.hash_size)) { - tdb1_off_t left = offset - sizeof(tdb1_off_t); - struct tdb1_record l; - tdb1_off_t leftsize; - - /* Read in tailer and jump back to header */ - if (tdb1_ofs_read(tdb, left, &leftsize) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_free: left offset read failed at %u", left); - goto update; - } - - /* it could be uninitialised data */ - if (leftsize == 0 || leftsize == TDB1_PAD_U32) { - goto update; - } - - left = offset - leftsize; - - if (leftsize > offset || - left < TDB1_DATA_START(tdb->tdb1.header.hash_size)) { - goto update; - } - - /* Now read in the left record */ - if (tdb->tdb1.io->tdb1_read(tdb, left, &l, sizeof(l), TDB1_DOCONV()) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_free: left read failed at %u (%u)", left, leftsize); - goto update; - } - - /* If it's free, expand to include it. */ - if (l.magic == TDB1_FREE_MAGIC) { - /* we now merge the new record into the left record, rather than the other - way around. This makes the operation O(1) instead of O(n). This change - prevents traverse from being O(n^2) after a lot of deletes */ - l.rec_len += sizeof(*rec) + rec->rec_len; - if (tdb1_rec_write(tdb, left, &l) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_free: update_left failed at %u", left); - goto fail; - } - if (update_tailer(tdb, left, &l) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_free: update_tailer failed at %u", offset); - goto fail; - } - tdb->stats.alloc_coalesce_succeeded++; - tdb->stats.alloc_coalesce_num_merged++; - tdb->stats.frees++; - tdb1_unlock(tdb, -1, F_WRLCK); - return 0; - } - } - -update: - - /* Now, prepend to free list */ - rec->magic = TDB1_FREE_MAGIC; - - if (tdb1_ofs_read(tdb, TDB1_FREELIST_TOP, &rec->next) == -1 || - tdb1_rec_write(tdb, offset, rec) == -1 || - tdb1_ofs_write(tdb, TDB1_FREELIST_TOP, &offset) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_free record write failed at offset=%d", - offset); - goto fail; - } - - /* And we're done. */ - tdb->stats.frees++; - tdb1_unlock(tdb, -1, F_WRLCK); - return 0; - - fail: - tdb1_unlock(tdb, -1, F_WRLCK); - return -1; -} - - - -/* - the core of tdb1_allocate - called when we have decided which - free list entry to use - - Note that we try to allocate by grabbing data from the end of an existing record, - not the beginning. This is so the left merge in a free is more likely to be - able to free up the record without fragmentation - */ -static tdb1_off_t tdb1_allocate_ofs(struct tdb_context *tdb, - tdb1_len_t length, tdb1_off_t rec_ptr, - struct tdb1_record *rec, tdb1_off_t last_ptr) -{ -#define MIN_REC_SIZE (sizeof(struct tdb1_record) + sizeof(tdb1_off_t) + 8) - - if (rec->rec_len < length + MIN_REC_SIZE) { - /* we have to grab the whole record */ - - /* unlink it from the previous record */ - if (tdb1_ofs_write(tdb, last_ptr, &rec->next) == -1) { - return 0; - } - - /* mark it not free */ - rec->magic = TDB1_MAGIC; - if (tdb1_rec_write(tdb, rec_ptr, rec) == -1) { - return 0; - } - tdb->stats.allocs++; - return rec_ptr; - } - - /* we're going to just shorten the existing record */ - rec->rec_len -= (length + sizeof(*rec)); - if (tdb1_rec_write(tdb, rec_ptr, rec) == -1) { - return 0; - } - if (update_tailer(tdb, rec_ptr, rec) == -1) { - return 0; - } - - /* and setup the new record */ - rec_ptr += sizeof(*rec) + rec->rec_len; - - memset(rec, '\0', sizeof(*rec)); - rec->rec_len = length; - rec->magic = TDB1_MAGIC; - - if (tdb1_rec_write(tdb, rec_ptr, rec) == -1) { - return 0; - } - - if (update_tailer(tdb, rec_ptr, rec) == -1) { - return 0; - } - - tdb->stats.allocs++; - tdb->stats.alloc_leftover++; - return rec_ptr; -} - -/* allocate some space from the free list. The offset returned points - to a unconnected tdb1_record within the database with room for at - least length bytes of total data - - 0 is returned if the space could not be allocated - */ -tdb1_off_t tdb1_allocate(struct tdb_context *tdb, tdb1_len_t length, struct tdb1_record *rec) -{ - tdb1_off_t rec_ptr, last_ptr, newrec_ptr; - struct { - tdb1_off_t rec_ptr, last_ptr; - tdb1_len_t rec_len; - } bestfit; - float multiplier = 1.0; - - if (tdb1_lock(tdb, -1, F_WRLCK) == -1) - return 0; - - /* over-allocate to reduce fragmentation */ - length *= 1.25; - - /* Extra bytes required for tailer */ - length += sizeof(tdb1_off_t); - length = TDB1_ALIGN(length, TDB1_ALIGNMENT); - - again: - last_ptr = TDB1_FREELIST_TOP; - - /* read in the freelist top */ - if (tdb1_ofs_read(tdb, TDB1_FREELIST_TOP, &rec_ptr) == -1) - goto fail; - - bestfit.rec_ptr = 0; - bestfit.last_ptr = 0; - bestfit.rec_len = 0; - - /* - this is a best fit allocation strategy. Originally we used - a first fit strategy, but it suffered from massive fragmentation - issues when faced with a slowly increasing record size. - */ - while (rec_ptr) { - if (tdb1_rec_free_read(tdb, rec_ptr, rec) == -1) { - goto fail; - } - - if (rec->rec_len >= length) { - if (bestfit.rec_ptr == 0 || - rec->rec_len < bestfit.rec_len) { - bestfit.rec_len = rec->rec_len; - bestfit.rec_ptr = rec_ptr; - bestfit.last_ptr = last_ptr; - } - } - - /* move to the next record */ - last_ptr = rec_ptr; - rec_ptr = rec->next; - - /* if we've found a record that is big enough, then - stop searching if its also not too big. The - definition of 'too big' changes as we scan - through */ - if (bestfit.rec_len > 0 && - bestfit.rec_len < length * multiplier) { - break; - } - - /* this multiplier means we only extremely rarely - search more than 50 or so records. At 50 records we - accept records up to 11 times larger than what we - want */ - multiplier *= 1.05; - } - - if (bestfit.rec_ptr != 0) { - if (tdb1_rec_free_read(tdb, bestfit.rec_ptr, rec) == -1) { - goto fail; - } - - newrec_ptr = tdb1_allocate_ofs(tdb, length, bestfit.rec_ptr, - rec, bestfit.last_ptr); - tdb1_unlock(tdb, -1, F_WRLCK); - return newrec_ptr; - } - - /* we didn't find enough space. See if we can expand the - database and if we can then try again */ - if (tdb1_expand(tdb, length + sizeof(*rec)) == 0) - goto again; - fail: - tdb1_unlock(tdb, -1, F_WRLCK); - return 0; -} diff --git a/ccan/tdb2/tdb1_hash.c b/ccan/tdb2/tdb1_hash.c deleted file mode 100644 index 2d5e4961..00000000 --- a/ccan/tdb2/tdb1_hash.c +++ /dev/null @@ -1,347 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Rusty Russell 2010 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "tdb1_private.h" - -/* This is based on the hash algorithm from gdbm */ -uint64_t tdb1_old_hash(const void *key, size_t len, uint64_t seed, void *unused) -{ - uint32_t value; /* Used to compute the hash value. */ - uint32_t i; /* Used to cycle through random values. */ - const unsigned char *dptr = key; - - /* Set the initial value from the key size. */ - for (value = 0x238F13AF * len, i=0; i < len; i++) - value = (value + (dptr[i] << (i*5 % 24))); - - return (1103515243 * value + 12345); -} - -#ifndef WORDS_BIGENDIAN -# define HASH_LITTLE_ENDIAN 1 -# define HASH_BIG_ENDIAN 0 -#else -# define HASH_LITTLE_ENDIAN 0 -# define HASH_BIG_ENDIAN 1 -#endif - -/* -------------------------------------------------------------------------------- -lookup3.c, by Bob Jenkins, May 2006, Public Domain. - -These are functions for producing 32-bit hashes for hash table lookup. -hash_word(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() -are externally useful functions. Routines to test the hash are included -if SELF_TEST is defined. You can use this free for any purpose. It's in -the public domain. It has no warranty. - -You probably want to use hashlittle(). hashlittle() and hashbig() -hash byte arrays. hashlittle() is is faster than hashbig() on -little-endian machines. Intel and AMD are little-endian machines. -On second thought, you probably want hashlittle2(), which is identical to -hashlittle() except it returns two 32-bit hashes for the price of one. -You could implement hashbig2() if you wanted but I haven't bothered here. - -If you want to find a hash of, say, exactly 7 integers, do - a = i1; b = i2; c = i3; - mix(a,b,c); - a += i4; b += i5; c += i6; - mix(a,b,c); - a += i7; - final(a,b,c); -then use c as the hash value. If you have a variable length array of -4-byte integers to hash, use hash_word(). If you have a byte array (like -a character string), use hashlittle(). If you have several byte arrays, or -a mix of things, see the comments above hashlittle(). - -Why is this so big? I read 12 bytes at a time into 3 4-byte integers, -then mix those integers. This is fast (you can do a lot more thorough -mixing with 12*3 instructions on 3 integers than you can with 3 instructions -on 1 byte), but shoehorning those bytes into integers efficiently is messy. -*/ - -#define hashsize(n) ((uint32_t)1<<(n)) -#define hashmask(n) (hashsize(n)-1) -#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) - -/* -------------------------------------------------------------------------------- -mix -- mix 3 32-bit values reversibly. - -This is reversible, so any information in (a,b,c) before mix() is -still in (a,b,c) after mix(). - -If four pairs of (a,b,c) inputs are run through mix(), or through -mix() in reverse, there are at least 32 bits of the output that -are sometimes the same for one pair and different for another pair. -This was tested for: -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. - -Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that -satisfy this are - 4 6 8 16 19 4 - 9 15 3 18 27 15 - 14 9 3 7 17 3 -Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing -for "differ" defined as + with a one-bit base and a two-bit delta. I -used http://burtleburtle.net/bob/hash/avalanche.html to choose -the operations, constants, and arrangements of the variables. - -This does not achieve avalanche. There are input bits of (a,b,c) -that fail to affect some output bits of (a,b,c), especially of a. The -most thoroughly mixed value is c, but it doesn't really even achieve -avalanche in c. - -This allows some parallelism. Read-after-writes are good at doubling -the number of bits affected, so the goal of mixing pulls in the opposite -direction as the goal of parallelism. I did what I could. Rotates -seem to cost as much as shifts on every machine I could lay my hands -on, and rotates are much kinder to the top and bottom bits, so I used -rotates. -------------------------------------------------------------------------------- -*/ -#define mix(a,b,c) \ -{ \ - a -= c; a ^= rot(c, 4); c += b; \ - b -= a; b ^= rot(a, 6); a += c; \ - c -= b; c ^= rot(b, 8); b += a; \ - a -= c; a ^= rot(c,16); c += b; \ - b -= a; b ^= rot(a,19); a += c; \ - c -= b; c ^= rot(b, 4); b += a; \ -} - -/* -------------------------------------------------------------------------------- -final -- final mixing of 3 32-bit values (a,b,c) into c - -Pairs of (a,b,c) values differing in only a few bits will usually -produce values of c that look totally different. This was tested for -* pairs that differed by one bit, by two bits, in any combination - of top bits of (a,b,c), or in any combination of bottom bits of - (a,b,c). -* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed - the output delta to a Gray code (a^(a>>1)) so a string of 1's (as - is commonly produced by subtraction) look like a single 1-bit - difference. -* the base values were pseudorandom, all zero but one bit set, or - all zero plus a counter that starts at zero. - -These constants passed: - 14 11 25 16 4 14 24 - 12 14 25 16 4 14 24 -and these came close: - 4 8 15 26 3 22 24 - 10 8 15 26 3 22 24 - 11 8 15 26 3 22 24 -------------------------------------------------------------------------------- -*/ -#define final(a,b,c) \ -{ \ - c ^= b; c -= rot(b,14); \ - a ^= c; a -= rot(c,11); \ - b ^= a; b -= rot(a,25); \ - c ^= b; c -= rot(b,16); \ - a ^= c; a -= rot(c,4); \ - b ^= a; b -= rot(a,14); \ - c ^= b; c -= rot(b,24); \ -} - - -/* -------------------------------------------------------------------------------- -hashlittle() -- hash a variable-length key into a 32-bit value - k : the key (the unaligned variable-length array of bytes) - length : the length of the key, counting by bytes - val2 : IN: can be any 4-byte value OUT: second 32 bit hash. -Returns a 32-bit value. Every bit of the key affects every bit of -the return value. Two keys differing by one or two bits will have -totally different hash values. Note that the return value is better -mixed than val2, so use that first. - -The best hash table sizes are powers of 2. There is no need to do -mod a prime (mod is sooo slow!). If you need less than 32 bits, -use a bitmask. For example, if you need only 10 bits, do - h = (h & hashmask(10)); -In which case, the hash table should have hashsize(10) elements. - -If you are hashing n strings (uint8_t **)k, do it like this: - for (i=0, h=0; i 12) - { - a += k[0]; - b += k[1]; - c += k[2]; - mix(a,b,c); - length -= 12; - k += 3; - } - - /*----------------------------- handle the last (probably partial) block */ - k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[1]; a+=k[0]; break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]; break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ - case 1 : a+=k8[0]; break; - case 0 : return c; - } - } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { - const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ - const uint8_t *k8; - - /*--------------- all but last block: aligned reads and different mixing */ - while (length > 12) - { - a += k[0] + (((uint32_t)k[1])<<16); - b += k[2] + (((uint32_t)k[3])<<16); - c += k[4] + (((uint32_t)k[5])<<16); - mix(a,b,c); - length -= 12; - k += 6; - } - - /*----------------------------- handle the last (probably partial) block */ - k8 = (const uint8_t *)k; - switch(length) - { - case 12: c+=k[4]+(((uint32_t)k[5])<<16); - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ - case 10: c+=k[4]; - b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 9 : c+=k8[8]; /* fall through */ - case 8 : b+=k[2]+(((uint32_t)k[3])<<16); - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ - case 6 : b+=k[2]; - a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 5 : b+=k8[4]; /* fall through */ - case 4 : a+=k[0]+(((uint32_t)k[1])<<16); - break; - case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ - case 2 : a+=k[0]; - break; - case 1 : a+=k8[0]; - break; - case 0 : return c; /* zero length requires no mixing */ - } - - } else { /* need to read the key one byte at a time */ - const uint8_t *k = (const uint8_t *)key; - - /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ - while (length > 12) - { - a += k[0]; - a += ((uint32_t)k[1])<<8; - a += ((uint32_t)k[2])<<16; - a += ((uint32_t)k[3])<<24; - b += k[4]; - b += ((uint32_t)k[5])<<8; - b += ((uint32_t)k[6])<<16; - b += ((uint32_t)k[7])<<24; - c += k[8]; - c += ((uint32_t)k[9])<<8; - c += ((uint32_t)k[10])<<16; - c += ((uint32_t)k[11])<<24; - mix(a,b,c); - length -= 12; - k += 12; - } - - /*-------------------------------- last block: affect all 32 bits of (c) */ - switch(length) /* all the case statements fall through */ - { - case 12: c+=((uint32_t)k[11])<<24; - case 11: c+=((uint32_t)k[10])<<16; - case 10: c+=((uint32_t)k[9])<<8; - case 9 : c+=k[8]; - case 8 : b+=((uint32_t)k[7])<<24; - case 7 : b+=((uint32_t)k[6])<<16; - case 6 : b+=((uint32_t)k[5])<<8; - case 5 : b+=k[4]; - case 4 : a+=((uint32_t)k[3])<<24; - case 3 : a+=((uint32_t)k[2])<<16; - case 2 : a+=((uint32_t)k[1])<<8; - case 1 : a+=k[0]; - break; - case 0 : return c; - } - } - - final(a,b,c); - return c; -} - -uint64_t tdb1_incompatible_hash(const void *key, size_t len, uint64_t seed, - void *unused) -{ - return hashlittle(key, len); -} diff --git a/ccan/tdb2/tdb1_io.c b/ccan/tdb2/tdb1_io.c deleted file mode 100644 index 488f3d84..00000000 --- a/ccan/tdb2/tdb1_io.c +++ /dev/null @@ -1,543 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 1999-2005 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000-2003 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - - -#include "tdb1_private.h" -#ifndef MAX -#define MAX(a,b) ((a) > (b) ? (a) : (b)) -#endif - -/* check for an out of bounds access - if it is out of bounds then - see if the database has been expanded by someone else and expand - if necessary - note that "len" is the minimum length needed for the db -*/ -static int tdb1_oob(struct tdb_context *tdb, tdb1_off_t off, tdb1_len_t len, - int probe) -{ - struct stat st; - if (len + off < len) { - if (!probe) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_oob off %d len %d wrap\n", - (int)off, (int)len); - } - return -1; - } - - if (off + len <= tdb->file->map_size) - return 0; - if (tdb->flags & TDB_INTERNAL) { - if (!probe) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_oob len %d beyond internal malloc size %u", - (int)(off + len), (int)tdb->file->map_size); - } - return -1; - } - - if (fstat(tdb->file->fd, &st) == -1) { - tdb->last_error = TDB_ERR_IO; - return -1; - } - - if (st.st_size < (size_t)off + len) { - if (!probe) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_oob len %u beyond eof at %u", - (int)(off + len), (int)st.st_size); - } - return -1; - } - - /* Beware >4G files! */ - if ((tdb1_off_t)st.st_size != st.st_size) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_oob len %llu too large!\n", - (long long)st.st_size); - return -1; - } - - /* Unmap, update size, remap */ - if (tdb1_munmap(tdb) == -1) { - tdb->last_error = TDB_ERR_IO; - return -1; - } - tdb->file->map_size = st.st_size; - tdb1_mmap(tdb); - return 0; -} - -/* write a lump of data at a specified offset */ -static int tdb1_write(struct tdb_context *tdb, tdb1_off_t off, - const void *buf, tdb1_len_t len) -{ - if (len == 0) { - return 0; - } - - if ((tdb->flags & TDB_RDONLY) || tdb->tdb1.traverse_read) { - tdb->last_error = TDB_ERR_RDONLY; - return -1; - } - - if (tdb->tdb1.io->tdb1_oob(tdb, off, len, 0) != 0) - return -1; - - if (tdb->file->map_ptr) { - memcpy(off + (char *)tdb->file->map_ptr, buf, len); - } else { - ssize_t written = pwrite(tdb->file->fd, buf, len, off); - if ((written != (ssize_t)len) && (written != -1)) { - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_WARNING, - "tdb1_write: wrote only " - "%d of %d bytes at %d, trying once more", - (int)written, len, off); - written = pwrite(tdb->file->fd, - (const char *)buf+written, - len-written, - off+written); - } - if (written == -1) { - /* Ensure ecode is set for log fn. */ - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_write failed at %d " - "len=%d (%s)", - off, len, strerror(errno)); - return -1; - } else if (written != (ssize_t)len) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_write: failed to " - "write %d bytes at %d in two attempts", - len, off); - return -1; - } - } - return 0; -} - -/* Endian conversion: we only ever deal with 4 byte quantities */ -void *tdb1_convert(void *buf, uint32_t size) -{ - uint32_t i, *p = (uint32_t *)buf; - for (i = 0; i < size / 4; i++) - p[i] = TDB1_BYTEREV(p[i]); - return buf; -} - - -/* read a lump of data at a specified offset, maybe convert */ -static int tdb1_read(struct tdb_context *tdb, tdb1_off_t off, void *buf, - tdb1_len_t len, int cv) -{ - if (tdb->tdb1.io->tdb1_oob(tdb, off, len, 0) != 0) { - return -1; - } - - if (tdb->file->map_ptr) { - memcpy(buf, off + (char *)tdb->file->map_ptr, len); - } else { - ssize_t ret = pread(tdb->file->fd, buf, len, off); - if (ret != (ssize_t)len) { - /* Ensure ecode is set for log fn. */ - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_read failed at %d " - "len=%d ret=%d (%s) map_size=%d", - (int)off, (int)len, (int)ret, - strerror(errno), - (int)tdb->file->map_size); - return -1; - } - } - if (cv) { - tdb1_convert(buf, len); - } - return 0; -} - - - -/* - do an unlocked scan of the hash table heads to find the next non-zero head. The value - will then be confirmed with the lock held -*/ -static void tdb1_next_hash_chain(struct tdb_context *tdb, uint32_t *chain) -{ - uint32_t h = *chain; - if (tdb->file->map_ptr) { - for (;h < tdb->tdb1.header.hash_size;h++) { - if (0 != *(uint32_t *)(TDB1_HASH_TOP(h) + (unsigned char *)tdb->file->map_ptr)) { - break; - } - } - } else { - uint32_t off=0; - for (;h < tdb->tdb1.header.hash_size;h++) { - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(h), &off) != 0 || off != 0) { - break; - } - } - } - (*chain) = h; -} - - -int tdb1_munmap(struct tdb_context *tdb) -{ - if (tdb->flags & TDB_INTERNAL) - return 0; - -#if HAVE_MMAP - if (tdb->file->map_ptr) { - int ret; - - ret = munmap(tdb->file->map_ptr, tdb->file->map_size); - if (ret != 0) - return ret; - } -#endif - tdb->file->map_ptr = NULL; - return 0; -} - -void tdb1_mmap(struct tdb_context *tdb) -{ - if (tdb->flags & TDB_INTERNAL) - return; - -#if HAVE_MMAP - if (!(tdb->flags & TDB_NOMMAP)) { - int mmap_flags; - if ((tdb->open_flags & O_ACCMODE) == O_RDONLY) - mmap_flags = PROT_READ; - else - mmap_flags = PROT_READ | PROT_WRITE; - - tdb->file->map_ptr = mmap(NULL, tdb->file->map_size, - mmap_flags, - MAP_SHARED|MAP_FILE, tdb->file->fd, 0); - - /* - * NB. When mmap fails it returns MAP_FAILED *NOT* NULL !!!! - */ - - if (tdb->file->map_ptr == MAP_FAILED) { - tdb->file->map_ptr = NULL; - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_WARNING, - "tdb1_mmap failed for size %d (%s)", - tdb->file->map_size, strerror(errno)); - } - } else { - tdb->file->map_ptr = NULL; - } -#else - tdb->file->map_ptr = NULL; -#endif -} - -/* expand a file. we prefer to use ftruncate, as that is what posix - says to use for mmap expansion */ -static int tdb1_expand_file(struct tdb_context *tdb, tdb1_off_t size, tdb1_off_t addition) -{ - char buf[8192]; - - if ((tdb->flags & TDB_RDONLY) || tdb->tdb1.traverse_read) { - tdb->last_error = TDB_ERR_RDONLY; - return -1; - } - - if (ftruncate(tdb->file->fd, size+addition) == -1) { - char b = 0; - ssize_t written = pwrite(tdb->file->fd, &b, 1, - (size+addition) - 1); - if (written == 0) { - /* try once more, potentially revealing errno */ - written = pwrite(tdb->file->fd, &b, 1, - (size+addition) - 1); - } - if (written == 0) { - /* again - give up, guessing errno */ - errno = ENOSPC; - } - if (written != 1) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "expand_file to %d failed (%s)", - size+addition, - strerror(errno)); - return -1; - } - } - - /* now fill the file with something. This ensures that the - file isn't sparse, which would be very bad if we ran out of - disk. This must be done with write, not via mmap */ - memset(buf, TDB1_PAD_BYTE, sizeof(buf)); - while (addition) { - size_t n = addition>sizeof(buf)?sizeof(buf):addition; - ssize_t written = pwrite(tdb->file->fd, buf, n, size); - if (written == 0) { - /* prevent infinite loops: try _once_ more */ - written = pwrite(tdb->file->fd, buf, n, size); - } - if (written == 0) { - /* give up, trying to provide a useful errno */ - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "expand_file write " - "returned 0 twice: giving up!"); - errno = ENOSPC; - return -1; - } else if (written == -1) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "expand_file write of " - "%d bytes failed (%s)", (int)n, - strerror(errno)); - return -1; - } else if (written != n) { - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_WARNING, - "expand_file: wrote " - "only %d of %d bytes - retrying", - (int)written, (int)n); - } - addition -= written; - size += written; - } - tdb->stats.expands++; - return 0; -} - - -/* You need 'size', this tells you how much you should expand by. */ -tdb1_off_t tdb1_expand_adjust(tdb1_off_t map_size, tdb1_off_t size, int page_size) -{ - tdb1_off_t new_size, top_size; - - /* limit size in order to avoid using up huge amounts of memory for - * in memory tdbs if an oddball huge record creeps in */ - if (size > 100 * 1024) { - top_size = map_size + size * 2; - } else { - top_size = map_size + size * 100; - } - - /* always make room for at least top_size more records, and at - least 25% more space. if the DB is smaller than 100MiB, - otherwise grow it by 10% only. */ - if (map_size > 100 * 1024 * 1024) { - new_size = map_size * 1.10; - } else { - new_size = map_size * 1.25; - } - - /* Round the database up to a multiple of the page size */ - new_size = MAX(top_size, new_size); - return TDB1_ALIGN(new_size, page_size) - map_size; -} - -/* expand the database at least size bytes by expanding the underlying - file and doing the mmap again if necessary */ -int tdb1_expand(struct tdb_context *tdb, tdb1_off_t size) -{ - struct tdb1_record rec; - tdb1_off_t offset; - - if (tdb1_lock(tdb, -1, F_WRLCK) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "lock failed in tdb1_expand"); - return -1; - } - - /* must know about any previous expansions by another process */ - tdb->tdb1.io->tdb1_oob(tdb, tdb->file->map_size, 1, 1); - - size = tdb1_expand_adjust(tdb->file->map_size, size, - tdb->tdb1.page_size); - - if (!(tdb->flags & TDB_INTERNAL)) - tdb1_munmap(tdb); - - /* - * We must ensure the file is unmapped before doing this - * to ensure consistency with systems like OpenBSD where - * writes and mmaps are not consistent. - */ - - /* expand the file itself */ - if (!(tdb->flags & TDB_INTERNAL)) { - if (tdb->tdb1.io->tdb1_expand_file(tdb, tdb->file->map_size, size) != 0) - goto fail; - } - - tdb->file->map_size += size; - - if (tdb->flags & TDB_INTERNAL) { - char *new_map_ptr = (char *)realloc(tdb->file->map_ptr, - tdb->file->map_size); - if (!new_map_ptr) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_OOM, - TDB_LOG_ERROR, - "tdb1_expand: no memory"); - tdb->file->map_size -= size; - goto fail; - } - tdb->file->map_ptr = new_map_ptr; - } else { - /* - * We must ensure the file is remapped before adding the space - * to ensure consistency with systems like OpenBSD where - * writes and mmaps are not consistent. - */ - - /* We're ok if the mmap fails as we'll fallback to read/write */ - tdb1_mmap(tdb); - } - - /* form a new freelist record */ - memset(&rec,'\0',sizeof(rec)); - rec.rec_len = size - sizeof(rec); - - /* link it into the free list */ - offset = tdb->file->map_size - size; - if (tdb1_free(tdb, offset, &rec) == -1) - goto fail; - - tdb1_unlock(tdb, -1, F_WRLCK); - return 0; - fail: - tdb1_unlock(tdb, -1, F_WRLCK); - return -1; -} - -/* read/write a tdb1_off_t */ -int tdb1_ofs_read(struct tdb_context *tdb, tdb1_off_t offset, tdb1_off_t *d) -{ - return tdb->tdb1.io->tdb1_read(tdb, offset, (char*)d, sizeof(*d), TDB1_DOCONV()); -} - -int tdb1_ofs_write(struct tdb_context *tdb, tdb1_off_t offset, tdb1_off_t *d) -{ - tdb1_off_t off = *d; - return tdb->tdb1.io->tdb1_write(tdb, offset, TDB1_CONV(off), sizeof(*d)); -} - - -/* read a lump of data, allocating the space for it */ -unsigned char *tdb1_alloc_read(struct tdb_context *tdb, tdb1_off_t offset, tdb1_len_t len) -{ - unsigned char *buf; - - /* some systems don't like zero length malloc */ - - if (!(buf = (unsigned char *)malloc(len ? len : 1))) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb1_alloc_read malloc failed" - " len=%d (%s)", - len, strerror(errno)); - return NULL; - } - if (tdb->tdb1.io->tdb1_read(tdb, offset, buf, len, 0) == -1) { - SAFE_FREE(buf); - return NULL; - } - return buf; -} - -/* Give a piece of tdb data to a parser */ -enum TDB_ERROR tdb1_parse_data(struct tdb_context *tdb, TDB_DATA key, - tdb1_off_t offset, tdb1_len_t len, - enum TDB_ERROR (*parser)(TDB_DATA key, - TDB_DATA data, - void *private_data), - void *private_data) -{ - TDB_DATA data; - enum TDB_ERROR result; - - data.dsize = len; - - if ((tdb->tdb1.transaction == NULL) && (tdb->file->map_ptr != NULL)) { - /* - * Optimize by avoiding the malloc/memcpy/free, point the - * parser directly at the mmap area. - */ - if (tdb->tdb1.io->tdb1_oob(tdb, offset, len, 0) != 0) { - return tdb->last_error; - } - data.dptr = offset + (unsigned char *)tdb->file->map_ptr; - return parser(key, data, private_data); - } - - if (!(data.dptr = tdb1_alloc_read(tdb, offset, len))) { - return tdb->last_error; - } - - result = parser(key, data, private_data); - free(data.dptr); - return result; -} - -/* read/write a record */ -int tdb1_rec_read(struct tdb_context *tdb, tdb1_off_t offset, struct tdb1_record *rec) -{ - if (tdb->tdb1.io->tdb1_read(tdb, offset, rec, sizeof(*rec),TDB1_DOCONV()) == -1) - return -1; - if (TDB1_BAD_MAGIC(rec)) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb1_rec_read bad magic 0x%x at offset=%d", - rec->magic, offset); - return -1; - } - return tdb->tdb1.io->tdb1_oob(tdb, rec->next, sizeof(*rec), 0); -} - -int tdb1_rec_write(struct tdb_context *tdb, tdb1_off_t offset, struct tdb1_record *rec) -{ - struct tdb1_record r = *rec; - return tdb->tdb1.io->tdb1_write(tdb, offset, TDB1_CONV(r), sizeof(r)); -} - -static const struct tdb1_methods io1_methods = { - tdb1_read, - tdb1_write, - tdb1_next_hash_chain, - tdb1_oob, - tdb1_expand_file, -}; - -/* - initialise the default methods table -*/ -void tdb1_io_init(struct tdb_context *tdb) -{ - tdb->tdb1.io = &io1_methods; -} - -enum TDB_ERROR tdb1_probe_length(struct tdb_context *tdb) -{ - tdb->last_error = TDB_SUCCESS; - tdb->tdb1.io->tdb1_oob(tdb, tdb->file->map_size, 1, true); - return tdb->last_error; -} diff --git a/ccan/tdb2/tdb1_lock.c b/ccan/tdb2/tdb1_lock.c deleted file mode 100644 index 5cc0ad65..00000000 --- a/ccan/tdb2/tdb1_lock.c +++ /dev/null @@ -1,560 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 1999-2005 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000-2003 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#include "tdb1_private.h" - -/* list -1 is the alloc list, otherwise a hash chain. */ -static tdb1_off_t lock_offset(int list) -{ - return TDB1_FREELIST_TOP + 4*list; -} - -/* a byte range locking function - return 0 on success - this functions locks/unlocks 1 byte at the specified offset. - - On error, errno is also set so that errors are passed back properly - through tdb1_open(). - - note that a len of zero means lock to end of file -*/ -int tdb1_brlock(struct tdb_context *tdb, - int rw_type, tdb1_off_t offset, size_t len, - enum tdb_lock_flags flags) -{ - enum TDB_ERROR ecode = tdb_brlock(tdb, rw_type, offset, len, flags - | TDB_LOCK_NOCHECK); - if (ecode == TDB_SUCCESS) - return 0; - tdb->last_error = ecode; - return -1; -} - -int tdb1_brunlock(struct tdb_context *tdb, - int rw_type, tdb1_off_t offset, size_t len) -{ - enum TDB_ERROR ecode = tdb_brunlock(tdb, rw_type, offset, len); - if (ecode == TDB_SUCCESS) - return 0; - tdb->last_error = ecode; - return -1; -} - -int tdb1_allrecord_upgrade(struct tdb_context *tdb) -{ - enum TDB_ERROR ecode = tdb_allrecord_upgrade(tdb, TDB1_FREELIST_TOP); - if (ecode == TDB_SUCCESS) - return 0; - tdb->last_error = ecode; - return -1; -} - -static struct tdb_lock *tdb1_find_nestlock(struct tdb_context *tdb, - tdb1_off_t offset) -{ - unsigned int i; - - for (i=0; ifile->num_lockrecs; i++) { - if (tdb->file->lockrecs[i].off == offset) { - return &tdb->file->lockrecs[i]; - } - } - return NULL; -} - -/* lock an offset in the database. */ -int tdb1_nest_lock(struct tdb_context *tdb, uint32_t offset, int ltype, - enum tdb_lock_flags flags) -{ - enum TDB_ERROR ecode; - - if (offset >= lock_offset(tdb->tdb1.header.hash_size)) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb1_lock: invalid offset %u for" - " ltype=%d", - offset, ltype); - return -1; - } - - ecode = tdb_nest_lock(tdb, offset, ltype, flags | TDB_LOCK_NOCHECK); - if (unlikely(ecode != TDB_SUCCESS)) { - tdb->last_error = ecode; - return -1; - } - return 0; -} - -static int tdb1_lock_and_recover(struct tdb_context *tdb) -{ - int ret; - - /* We need to match locking order in transaction commit. */ - if (tdb1_brlock(tdb, F_WRLCK, TDB1_FREELIST_TOP, 0, - TDB_LOCK_WAIT|TDB_LOCK_NOCHECK)) { - return -1; - } - - if (tdb1_brlock(tdb, F_WRLCK, TDB1_OPEN_LOCK, 1, - TDB_LOCK_WAIT|TDB_LOCK_NOCHECK)) { - tdb1_brunlock(tdb, F_WRLCK, TDB1_FREELIST_TOP, 0); - return -1; - } - - ret = tdb1_transaction_recover(tdb); - - tdb1_brunlock(tdb, F_WRLCK, TDB1_OPEN_LOCK, 1); - tdb1_brunlock(tdb, F_WRLCK, TDB1_FREELIST_TOP, 0); - - return ret; -} - -static bool have_data_locks(const struct tdb_context *tdb) -{ - unsigned int i; - - for (i = 0; i < tdb->file->num_lockrecs; i++) { - if (tdb->file->lockrecs[i].off >= lock_offset(-1)) - return true; - } - return false; -} - -static int tdb1_lock_list(struct tdb_context *tdb, int list, int ltype, - enum tdb_lock_flags waitflag) -{ - int ret; - bool check = false; - - /* a allrecord lock allows us to avoid per chain locks */ - if (tdb->file->allrecord_lock.count) { - if (!check_lock_pid(tdb, "tdb1_lock_list", true)) { - tdb->last_error = TDB_ERR_LOCK; - return -1; - } - if (tdb->file->allrecord_lock.owner != tdb) { - tdb->last_error = owner_conflict(tdb, "tdb1_lock_list"); - return -1; - } - if (ltype == tdb->file->allrecord_lock.ltype - || ltype == F_RDLCK) { - return 0; - } - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, - TDB_LOG_USE_ERROR, - "tdb1_lock_list:" - " already have read lock"); - return -1; - } - - /* Only check when we grab first data lock. */ - check = !have_data_locks(tdb); - ret = tdb1_nest_lock(tdb, lock_offset(list), ltype, waitflag); - - if (ret == 0 && check) { - tdb_bool_err berr = tdb1_needs_recovery(tdb); - - if (berr < 0) { - return -1; - } - if (berr == true) { - tdb1_nest_unlock(tdb, lock_offset(list), ltype); - - if (tdb1_lock_and_recover(tdb) == -1) { - return -1; - } - return tdb1_lock_list(tdb, list, ltype, waitflag); - } - } - return ret; -} - -/* lock a list in the database. list -1 is the alloc list */ -int tdb1_lock(struct tdb_context *tdb, int list, int ltype) -{ - int ret; - - ret = tdb1_lock_list(tdb, list, ltype, TDB_LOCK_WAIT); - /* Don't log for EAGAIN and EINTR: they could have overridden lock fns */ - if (ret && errno != EAGAIN && errno != EINTR) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_lock failed on list %d " - "ltype=%d (%s)", list, ltype, strerror(errno)); - } - return ret; -} - -int tdb1_nest_unlock(struct tdb_context *tdb, uint32_t offset, int ltype) -{ - enum TDB_ERROR ecode; - - /* Sanity checks */ - if (offset >= lock_offset(tdb->tdb1.header.hash_size)) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_ERROR, - "tdb1_unlock: offset %u invalid (%d)", - offset, tdb->tdb1.header.hash_size); - return -1; - } - - ecode = tdb_nest_unlock(tdb, offset, ltype); - if (unlikely(ecode != TDB_SUCCESS)) { - tdb->last_error = ecode; - return -1; - } - return 0; -} - -int tdb1_unlock(struct tdb_context *tdb, int list, int ltype) -{ - /* a global lock allows us to avoid per chain locks */ - if (tdb->file->allrecord_lock.count && - (ltype == tdb->file->allrecord_lock.ltype || ltype == F_RDLCK)) { - if (tdb->file->allrecord_lock.owner != tdb) { - tdb->last_error = owner_conflict(tdb, "tdb1_unlock"); - return -1; - } - return 0; - } - - if (tdb->file->allrecord_lock.count) { - tdb->last_error = TDB_ERR_LOCK; - return -1; - } - - return tdb1_nest_unlock(tdb, lock_offset(list), ltype); -} - -/* - get the transaction lock - */ -int tdb1_transaction_lock(struct tdb_context *tdb, int ltype, - enum tdb_lock_flags lockflags) -{ - return tdb1_nest_lock(tdb, TDB1_TRANSACTION_LOCK, ltype, lockflags); -} - -/* - release the transaction lock - */ -int tdb1_transaction_unlock(struct tdb_context *tdb, int ltype) -{ - return tdb1_nest_unlock(tdb, TDB1_TRANSACTION_LOCK, ltype); -} - -/* lock/unlock entire database. It can only be upgradable if you have some - * other way of guaranteeing exclusivity (ie. transaction write lock). - * We do the locking gradually to avoid being starved by smaller locks. */ -int tdb1_allrecord_lock(struct tdb_context *tdb, int ltype, - enum tdb_lock_flags flags, bool upgradable) -{ - enum TDB_ERROR ecode; - tdb_bool_err berr; - - /* tdb_lock_gradual() doesn't know about tdb->tdb1.traverse_read. */ - if (tdb->tdb1.traverse_read && !(tdb->flags & TDB_NOLOCK)) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, - TDB_LOG_USE_ERROR, - "tdb1_allrecord_lock during" - " tdb1_read_traverse"); - return -1; - } - - if (tdb->file->allrecord_lock.count - && tdb->file->allrecord_lock.ltype == ltype) { - tdb->file->allrecord_lock.count++; - return 0; - } - - if (tdb1_have_extra_locks(tdb)) { - /* can't combine global and chain locks */ - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, - TDB_LOG_USE_ERROR, - "tdb1_allrecord_lock holding" - " other locks"); - return -1; - } - - if (upgradable && ltype != F_RDLCK) { - /* tdb error: you can't upgrade a write lock! */ - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, - TDB_LOG_ERROR, - "tdb1_allrecord_lock cannot" - " have upgradable write lock"); - return -1; - } - - /* We cover two kinds of locks: - * 1) Normal chain locks. Taken for almost all operations. - * 3) Individual records locks. Taken after normal or free - * chain locks. - * - * It is (1) which cause the starvation problem, so we're only - * gradual for that. */ - ecode = tdb_lock_gradual(tdb, ltype, flags | TDB_LOCK_NOCHECK, - TDB1_FREELIST_TOP, tdb->tdb1.header.hash_size * 4); - if (ecode != TDB_SUCCESS) { - tdb->last_error = ecode; - return -1; - } - - /* Grab individual record locks. */ - if (tdb1_brlock(tdb, ltype, lock_offset(tdb->tdb1.header.hash_size), 0, - flags) == -1) { - tdb1_brunlock(tdb, ltype, TDB1_FREELIST_TOP, - tdb->tdb1.header.hash_size * 4); - return -1; - } - - tdb->file->allrecord_lock.owner = tdb; - tdb->file->allrecord_lock.count = 1; - tdb->file->locker = getpid(); - /* If it's upgradable, it's actually exclusive so we can treat - * it as a write lock. */ - tdb->file->allrecord_lock.ltype = upgradable ? F_WRLCK : ltype; - tdb->file->allrecord_lock.off = upgradable; - - berr = tdb1_needs_recovery(tdb); - if (berr < 0) { - return -1; - } - - if (berr == true) { - tdb1_allrecord_unlock(tdb, ltype); - if (tdb1_lock_and_recover(tdb) == -1) { - return -1; - } - return tdb1_allrecord_lock(tdb, ltype, flags, upgradable); - } - - return 0; -} - - - -/* unlock entire db */ -int tdb1_allrecord_unlock(struct tdb_context *tdb, int ltype) -{ - /* Don't try this during r/o traversal! */ - if (tdb->tdb1.traverse_read) { - tdb->last_error = TDB_ERR_LOCK; - return -1; - } - - if (tdb->file->allrecord_lock.count == 0) { - tdb->last_error = TDB_ERR_LOCK; - return -1; - } - - /* Upgradable locks are marked as write locks. */ - if (tdb->file->allrecord_lock.ltype != ltype - && (!tdb->file->allrecord_lock.off || ltype != F_RDLCK)) { - tdb->last_error = TDB_ERR_LOCK; - return -1; - } - - if (tdb->file->allrecord_lock.count > 1) { - if (tdb->file->allrecord_lock.owner != tdb) { - tdb->last_error - = owner_conflict(tdb, "tdb1_allrecord_unlock"); - return -1; - } - tdb->file->allrecord_lock.count--; - return 0; - } - - tdb->file->allrecord_lock.count = 0; - tdb->file->allrecord_lock.ltype = 0; - - if (tdb1_brunlock(tdb, ltype, TDB1_FREELIST_TOP, 0)) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_unlockall failed (%s)", strerror(errno)); - return -1; - } - - return 0; -} - -/* lock/unlock one hash chain. This is meant to be used to reduce - contention - it cannot guarantee how many records will be locked */ -int tdb1_chainlock(struct tdb_context *tdb, TDB_DATA key) -{ - int ret = tdb1_lock(tdb, - TDB1_BUCKET(tdb_hash(tdb, key.dptr, key.dsize)), - F_WRLCK); - return ret; -} - -int tdb1_chainunlock(struct tdb_context *tdb, TDB_DATA key) -{ - return tdb1_unlock(tdb, TDB1_BUCKET(tdb_hash(tdb, key.dptr, key.dsize)), - F_WRLCK); -} - -int tdb1_chainlock_read(struct tdb_context *tdb, TDB_DATA key) -{ - int ret; - ret = tdb1_lock(tdb, TDB1_BUCKET(tdb_hash(tdb, key.dptr, key.dsize)), - F_RDLCK); - return ret; -} - -int tdb1_chainunlock_read(struct tdb_context *tdb, TDB_DATA key) -{ - return tdb1_unlock(tdb, TDB1_BUCKET(tdb_hash(tdb, key.dptr, key.dsize)), - F_RDLCK); -} - -/* record lock stops delete underneath */ -int tdb1_lock_record(struct tdb_context *tdb, tdb1_off_t off) -{ - if (tdb->file->allrecord_lock.count) { - if (!check_lock_pid(tdb, "tdb1_lock_record", true)) { - tdb->last_error = TDB_ERR_LOCK; - return -1; - } - if (tdb->file->allrecord_lock.owner != tdb) { - tdb->last_error = owner_conflict(tdb, - "tdb1_lock_record"); - return -1; - } - return 0; - } - return off ? tdb1_brlock(tdb, F_RDLCK, off, 1, TDB_LOCK_WAIT) : 0; -} - -/* - Write locks override our own fcntl readlocks, so check it here. - Note this is meant to be F_SETLK, *not* F_SETLKW, as it's not - an error to fail to get the lock here. -*/ -int tdb1_write_lock_record(struct tdb_context *tdb, tdb1_off_t off) -{ - struct tdb1_traverse_lock *i; - for (i = &tdb->tdb1.travlocks; i; i = i->next) - if (i->off == off) - return -1; - if (tdb->file->allrecord_lock.count) { - if (!check_lock_pid(tdb, "tdb1_write_lock_record", true)) { - tdb->last_error = TDB_ERR_LOCK; - return -1; - } - if (tdb->file->allrecord_lock.owner != tdb) { - tdb->last_error - = owner_conflict(tdb, "tdb1_write_lock_record"); - return -1; - } - if (tdb->file->allrecord_lock.ltype == F_WRLCK) { - return 0; - } - return -1; - } - return tdb1_brlock(tdb, F_WRLCK, off, 1, TDB_LOCK_NOWAIT|TDB_LOCK_PROBE); -} - -int tdb1_write_unlock_record(struct tdb_context *tdb, tdb1_off_t off) -{ - if (tdb->file->allrecord_lock.count) { - if (tdb->file->allrecord_lock.owner != tdb) { - tdb->last_error - = owner_conflict(tdb, - "tdb1_write_unlock_record"); - return -1; - } - return 0; - } - return tdb1_brunlock(tdb, F_WRLCK, off, 1); -} - -/* fcntl locks don't stack: avoid unlocking someone else's */ -int tdb1_unlock_record(struct tdb_context *tdb, tdb1_off_t off) -{ - struct tdb1_traverse_lock *i; - uint32_t count = 0; - - if (tdb->file->allrecord_lock.count) { - if (tdb->file->allrecord_lock.owner != tdb) { - tdb->last_error = owner_conflict(tdb, - "tdb1_unlock_record"); - return -1; - } - return 0; - } - - if (off == 0) - return 0; - for (i = &tdb->tdb1.travlocks; i; i = i->next) - if (i->off == off) - count++; - return (count == 1 ? tdb1_brunlock(tdb, F_RDLCK, off, 1) : 0); -} - -bool tdb1_have_extra_locks(struct tdb_context *tdb) -{ - unsigned int extra = tdb->file->num_lockrecs; - - /* A transaction holds the lock for all records. */ - if (!tdb->tdb1.transaction && tdb->file->allrecord_lock.count) { - return true; - } - - /* We always hold the active lock if CLEAR_IF_FIRST. */ - if (tdb1_find_nestlock(tdb, TDB1_ACTIVE_LOCK)) { - extra--; - } - - /* In a transaction, we expect to hold the transaction lock */ - if (tdb->tdb1.transaction - && tdb1_find_nestlock(tdb, TDB1_TRANSACTION_LOCK)) { - extra--; - } - - return extra; -} - -/* The transaction code uses this to remove all locks. */ -void tdb1_release_transaction_locks(struct tdb_context *tdb) -{ - unsigned int i, active = 0; - - if (tdb->file->allrecord_lock.count != 0) { - tdb1_brunlock(tdb, tdb->file->allrecord_lock.ltype, TDB1_FREELIST_TOP, 0); - tdb->file->allrecord_lock.count = 0; - } - - for (i=0;ifile->num_lockrecs;i++) { - struct tdb_lock *lck = &tdb->file->lockrecs[i]; - - /* Don't release the active lock! Copy it to first entry. */ - if (lck->off == TDB1_ACTIVE_LOCK) { - tdb->file->lockrecs[active++] = *lck; - } else { - tdb1_brunlock(tdb, lck->ltype, lck->off, 1); - } - } - tdb->file->num_lockrecs = active; - if (tdb->file->num_lockrecs == 0) { - SAFE_FREE(tdb->file->lockrecs); - } -} diff --git a/ccan/tdb2/tdb1_open.c b/ccan/tdb2/tdb1_open.c deleted file mode 100644 index e668616a..00000000 --- a/ccan/tdb2/tdb1_open.c +++ /dev/null @@ -1,234 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 1999-2005 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000-2003 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include -#include "tdb1_private.h" -#include - -/* We use two hashes to double-check they're using the right hash function. */ -void tdb1_header_hash(struct tdb_context *tdb, - uint32_t *magic1_hash, uint32_t *magic2_hash) -{ - uint32_t tdb1_magic = TDB1_MAGIC; - - *magic1_hash = tdb_hash(tdb, TDB_MAGIC_FOOD, sizeof(TDB_MAGIC_FOOD)); - *magic2_hash = tdb_hash(tdb, TDB1_CONV(tdb1_magic), sizeof(tdb1_magic)); - - /* Make sure at least one hash is non-zero! */ - if (*magic1_hash == 0 && *magic2_hash == 0) - *magic1_hash = 1; -} - -static void tdb_context_init(struct tdb_context *tdb, - struct tdb_attribute_tdb1_max_dead *max_dead) -{ - assert(tdb->flags & TDB_VERSION1); - - tdb1_io_init(tdb); - - tdb->tdb1.traverse_read = tdb->tdb1.traverse_write = 0; - memset(&tdb->tdb1.travlocks, 0, sizeof(tdb->tdb1.travlocks)); - tdb->tdb1.transaction = NULL; - - /* cache the page size */ - tdb->tdb1.page_size = getpagesize(); - if (tdb->tdb1.page_size <= 0) { - tdb->tdb1.page_size = 0x2000; - } - - if (max_dead) { - tdb->tdb1.max_dead_records = max_dead->max_dead; - } else { - tdb->tdb1.max_dead_records = 0; - } -} - -/* initialise a new database */ -enum TDB_ERROR tdb1_new_database(struct tdb_context *tdb, - struct tdb_attribute_tdb1_hashsize *hashsize, - struct tdb_attribute_tdb1_max_dead *max_dead) -{ - struct tdb1_header *newdb; - size_t size; - int hash_size = TDB1_DEFAULT_HASH_SIZE; - enum TDB_ERROR ret; - - tdb_context_init(tdb, max_dead); - - /* Default TDB2 hash becomes default TDB1 hash. */ - if (tdb->hash_fn == tdb_jenkins_hash) - tdb->hash_fn = tdb1_old_hash; - - if (hashsize) - hash_size = hashsize->hsize; - - /* We make it up in memory, then write it out if not internal */ - size = sizeof(struct tdb1_header) + (hash_size+1)*sizeof(tdb1_off_t); - if (!(newdb = (struct tdb1_header *)calloc(size, 1))) { - return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "Could not allocate new database header"); - } - - /* Fill in the header */ - newdb->version = TDB1_VERSION; - newdb->hash_size = hash_size; - - tdb1_header_hash(tdb, &newdb->magic1_hash, &newdb->magic2_hash); - - /* Make sure older tdbs (which don't check the magic hash fields) - * will refuse to open this TDB. */ - if (tdb->hash_fn == tdb1_incompatible_hash) - newdb->rwlocks = TDB1_HASH_RWLOCK_MAGIC; - - memcpy(&tdb->tdb1.header, newdb, sizeof(tdb->tdb1.header)); - /* This creates an endian-converted db. */ - TDB1_CONV(*newdb); - /* Don't endian-convert the magic food! */ - memcpy(newdb->magic_food, TDB_MAGIC_FOOD, strlen(TDB_MAGIC_FOOD)+1); - - if (tdb->flags & TDB_INTERNAL) { - tdb->file->map_size = size; - tdb->file->map_ptr = (char *)newdb; - return TDB_SUCCESS; - } - if (lseek(tdb->file->fd, 0, SEEK_SET) == -1) { - ret = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_new_database: lseek failed"); - goto fail; - } - - if (ftruncate(tdb->file->fd, 0) == -1) { - ret = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_new_database: ftruncate failed"); - goto fail; - } - - if (!tdb1_write_all(tdb->file->fd, newdb, size)) { - ret = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_new_database: write failed"); - goto fail; - } - ret = TDB_SUCCESS; - - fail: - SAFE_FREE(newdb); - return ret; -} - -typedef void (*tdb1_log_func)(struct tdb_context *, enum tdb_log_level, enum TDB_ERROR, - const char *, void *); -typedef uint64_t (*tdb1_hash_func)(const void *key, size_t len, uint64_t seed, - void *data); - -struct tdb1_logging_context { - tdb1_log_func log_fn; - void *log_private; -}; - -static bool hash_correct(struct tdb_context *tdb, - uint32_t *m1, uint32_t *m2) -{ - /* older TDB without magic hash references */ - if (tdb->tdb1.header.magic1_hash == 0 - && tdb->tdb1.header.magic2_hash == 0) { - return true; - } - - tdb1_header_hash(tdb, m1, m2); - return (tdb->tdb1.header.magic1_hash == *m1 && - tdb->tdb1.header.magic2_hash == *m2); -} - -static bool check_header_hash(struct tdb_context *tdb, - uint32_t *m1, uint32_t *m2) -{ - if (hash_correct(tdb, m1, m2)) - return true; - - /* If they use one inbuilt, try the other inbuilt hash. */ - if (tdb->hash_fn == tdb1_old_hash) - tdb->hash_fn = tdb1_incompatible_hash; - else if (tdb->hash_fn == tdb1_incompatible_hash) - tdb->hash_fn = tdb1_old_hash; - else - return false; - return hash_correct(tdb, m1, m2); -} - -/* We are hold the TDB open lock on tdb->fd. */ -enum TDB_ERROR tdb1_open(struct tdb_context *tdb, - struct tdb_attribute_tdb1_max_dead *max_dead) -{ - const char *hash_alg; - uint32_t magic1, magic2; - - tdb->flags |= TDB_VERSION1; - - tdb_context_init(tdb, max_dead); - - /* Default TDB2 hash becomes default TDB1 hash. */ - if (tdb->hash_fn == tdb_jenkins_hash) { - tdb->hash_fn = tdb1_old_hash; - hash_alg = "default"; - } else if (tdb->hash_fn == tdb1_incompatible_hash) - hash_alg = "tdb1_incompatible_hash"; - else - hash_alg = "the user defined"; - - if (tdb->tdb1.header.version != TDB1_BYTEREV(TDB1_VERSION)) { - if (tdb->flags & TDB_CONVERT) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_open:" - " %s does not need TDB_CONVERT", - tdb->name); - } - } else { - tdb->flags |= TDB_CONVERT; - tdb1_convert(&tdb->tdb1.header, sizeof(tdb->tdb1.header)); - } - - if (tdb->tdb1.header.rwlocks != 0 && - tdb->tdb1.header.rwlocks != TDB1_HASH_RWLOCK_MAGIC) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb1_open: spinlocks no longer supported"); - } - - if (!check_header_hash(tdb, &magic1, &magic2)) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_USE_ERROR, - "tdb1_open: " - "%s was not created with %s hash function we are using\n" - "magic1_hash[0x%08X %s 0x%08X] " - "magic2_hash[0x%08X %s 0x%08X]", - tdb->name, hash_alg, - tdb->tdb1.header.magic1_hash, - (tdb->tdb1.header.magic1_hash == magic1) ? "==" : "!=", - magic1, - tdb->tdb1.header.magic2_hash, - (tdb->tdb1.header.magic2_hash == magic2) ? "==" : "!=", - magic2); - } - return TDB_SUCCESS; -} diff --git a/ccan/tdb2/tdb1_private.h b/ccan/tdb2/tdb1_private.h deleted file mode 100644 index cb22b9f3..00000000 --- a/ccan/tdb2/tdb1_private.h +++ /dev/null @@ -1,179 +0,0 @@ -#ifndef CCAN_TDB2_TDB1_PRIVATE_H -#define CCAN_TDB2_TDB1_PRIVATE_H - /* - Unix SMB/CIFS implementation. - - trivial database library - private includes - - Copyright (C) Andrew Tridgell 2005 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#include "private.h" - -#include - -/* #define TDB_TRACE 1 */ -#ifndef HAVE_GETPAGESIZE -#define getpagesize() 0x2000 -#endif - -#ifndef __STRING -#define __STRING(x) #x -#endif - -#ifndef __STRINGSTRING -#define __STRINGSTRING(x) __STRING(x) -#endif - -#ifndef __location__ -#define __location__ __FILE__ ":" __STRINGSTRING(__LINE__) -#endif - -#ifndef offsetof -#define offsetof(t,f) ((unsigned int)&((t *)0)->f) -#endif - -#define TDB1_VERSION (0x26011967 + 6) -#define TDB1_MAGIC (0x26011999U) -#define TDB1_FREE_MAGIC (~TDB1_MAGIC) -#define TDB1_DEAD_MAGIC (0xFEE1DEAD) -#define TDB1_RECOVERY_MAGIC (0xf53bc0e7U) -#define TDB1_RECOVERY_INVALID_MAGIC (0x0) -#define TDB1_HASH_RWLOCK_MAGIC (0xbad1a51U) -#define TDB1_ALIGNMENT 4 -#define TDB1_DEFAULT_HASH_SIZE 131 -#define TDB1_FREELIST_TOP (sizeof(struct tdb1_header)) -#define TDB1_ALIGN(x,a) (((x) + (a)-1) & ~((a)-1)) -#define TDB1_DEAD(r) ((r)->magic == TDB1_DEAD_MAGIC) -#define TDB1_BAD_MAGIC(r) ((r)->magic != TDB1_MAGIC && !TDB1_DEAD(r)) -#define TDB1_HASH_TOP(hash) (TDB1_FREELIST_TOP + (TDB1_BUCKET(hash)+1)*sizeof(tdb1_off_t)) -#define TDB1_HASHTABLE_SIZE(tdb) ((tdb->tdb1.header.hash_size+1)*sizeof(tdb1_off_t)) -#define TDB1_DATA_START(hash_size) (TDB1_HASH_TOP(hash_size-1) + sizeof(tdb1_off_t)) -#define TDB1_RECOVERY_HEAD offsetof(struct tdb1_header, recovery_start) -#define TDB1_SEQNUM_OFS offsetof(struct tdb1_header, sequence_number) -#define TDB1_PAD_BYTE 0x42 -#define TDB1_PAD_U32 0x42424242 - -/* lock offsets */ -#define TDB1_OPEN_LOCK 0 -#define TDB1_ACTIVE_LOCK 4 -#define TDB1_TRANSACTION_LOCK 8 - -/* free memory if the pointer is valid and zero the pointer */ -#ifndef SAFE_FREE -#define SAFE_FREE(x) do { if ((x) != NULL) {free((void *)x); (x)=NULL;} } while(0) -#endif - -#define TDB1_BUCKET(hash) ((hash) % tdb->tdb1.header.hash_size) - -#define TDB1_DOCONV() (tdb->flags & TDB_CONVERT) -#define TDB1_CONV(x) (TDB1_DOCONV() ? tdb1_convert(&x, sizeof(x)) : &x) - -/* the body of the database is made of one tdb1_record for the free space - plus a separate data list for each hash value */ -struct tdb1_record { - tdb1_off_t next; /* offset of the next record in the list */ - tdb1_len_t rec_len; /* total byte length of record */ - tdb1_len_t key_len; /* byte length of key */ - tdb1_len_t data_len; /* byte length of data */ - uint32_t full_hash; /* the full 32 bit hash of the key */ - uint32_t magic; /* try to catch errors */ - /* the following union is implied: - union { - char record[rec_len]; - struct { - char key[key_len]; - char data[data_len]; - } - uint32_t totalsize; (tailer) - } - */ -}; - - -struct tdb1_methods { - int (*tdb1_read)(struct tdb_context *, tdb1_off_t , void *, tdb1_len_t , int ); - int (*tdb1_write)(struct tdb_context *, tdb1_off_t, const void *, tdb1_len_t); - void (*next_hash_chain)(struct tdb_context *, uint32_t *); - int (*tdb1_oob)(struct tdb_context *, tdb1_off_t, tdb1_len_t, int ); - int (*tdb1_expand_file)(struct tdb_context *, tdb1_off_t , tdb1_off_t ); -}; - - -/* - internal prototypes -*/ -int tdb1_munmap(struct tdb_context *tdb); -void tdb1_mmap(struct tdb_context *tdb); -int tdb1_lock(struct tdb_context *tdb, int list, int ltype); -int tdb1_nest_lock(struct tdb_context *tdb, uint32_t offset, int ltype, - enum tdb_lock_flags flags); -int tdb1_nest_unlock(struct tdb_context *tdb, uint32_t offset, int ltype); -int tdb1_unlock(struct tdb_context *tdb, int list, int ltype); -int tdb1_brlock(struct tdb_context *tdb, - int rw_type, tdb1_off_t offset, size_t len, - enum tdb_lock_flags flags); -int tdb1_brunlock(struct tdb_context *tdb, - int rw_type, tdb1_off_t offset, size_t len); -bool tdb1_have_extra_locks(struct tdb_context *tdb); -void tdb1_release_transaction_locks(struct tdb_context *tdb); -int tdb1_transaction_lock(struct tdb_context *tdb, int ltype, - enum tdb_lock_flags lockflags); -int tdb1_transaction_unlock(struct tdb_context *tdb, int ltype); -int tdb1_recovery_area(struct tdb_context *tdb, - const struct tdb1_methods *methods, - tdb1_off_t *recovery_offset, - struct tdb1_record *rec); -int tdb1_allrecord_upgrade(struct tdb_context *tdb); -int tdb1_write_lock_record(struct tdb_context *tdb, tdb1_off_t off); -int tdb1_write_unlock_record(struct tdb_context *tdb, tdb1_off_t off); -int tdb1_ofs_read(struct tdb_context *tdb, tdb1_off_t offset, tdb1_off_t *d); -int tdb1_ofs_write(struct tdb_context *tdb, tdb1_off_t offset, tdb1_off_t *d); -void *tdb1_convert(void *buf, uint32_t size); -int tdb1_free(struct tdb_context *tdb, tdb1_off_t offset, struct tdb1_record *rec); -tdb1_off_t tdb1_allocate(struct tdb_context *tdb, tdb1_len_t length, struct tdb1_record *rec); -int tdb1_ofs_read(struct tdb_context *tdb, tdb1_off_t offset, tdb1_off_t *d); -int tdb1_ofs_write(struct tdb_context *tdb, tdb1_off_t offset, tdb1_off_t *d); -int tdb1_lock_record(struct tdb_context *tdb, tdb1_off_t off); -int tdb1_unlock_record(struct tdb_context *tdb, tdb1_off_t off); -tdb_bool_err tdb1_needs_recovery(struct tdb_context *tdb); -int tdb1_rec_read(struct tdb_context *tdb, tdb1_off_t offset, struct tdb1_record *rec); -int tdb1_rec_write(struct tdb_context *tdb, tdb1_off_t offset, struct tdb1_record *rec); -int tdb1_do_delete(struct tdb_context *tdb, tdb1_off_t rec_ptr, struct tdb1_record *rec); -unsigned char *tdb1_alloc_read(struct tdb_context *tdb, tdb1_off_t offset, tdb1_len_t len); -enum TDB_ERROR tdb1_parse_data(struct tdb_context *tdb, TDB_DATA key, - tdb1_off_t offset, tdb1_len_t len, - enum TDB_ERROR (*parser)(TDB_DATA key, - TDB_DATA data, - void *private_data), - void *private_data); -tdb1_off_t tdb1_find_lock_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash, int locktype, - struct tdb1_record *rec); -void tdb1_io_init(struct tdb_context *tdb); -int tdb1_expand(struct tdb_context *tdb, tdb1_off_t size); -tdb1_off_t tdb1_expand_adjust(tdb1_off_t map_size, tdb1_off_t size, int page_size); -int tdb1_rec_free_read(struct tdb_context *tdb, tdb1_off_t off, - struct tdb1_record *rec); -bool tdb1_write_all(int fd, const void *buf, size_t count); -void tdb1_header_hash(struct tdb_context *tdb, - uint32_t *magic1_hash, uint32_t *magic2_hash); -uint64_t tdb1_old_hash(const void *key, size_t len, uint64_t seed, void *); -size_t tdb1_dead_space(struct tdb_context *tdb, tdb1_off_t off); -#endif /* CCAN_TDB2_TDB1_PRIVATE_H */ diff --git a/ccan/tdb2/tdb1_summary.c b/ccan/tdb2/tdb1_summary.c deleted file mode 100644 index b74b8f44..00000000 --- a/ccan/tdb2/tdb1_summary.c +++ /dev/null @@ -1,202 +0,0 @@ - /* - Trivial Database: human-readable summary code - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "tdb1_private.h" - -#define SUMMARY_FORMAT1 \ - "Size of file/data: %u/%zu\n" \ - "Number of records: %zu\n" \ - "Smallest/average/largest keys: %zu/%zu/%zu\n" \ - "Smallest/average/largest data: %zu/%zu/%zu\n" \ - "Smallest/average/largest padding: %zu/%zu/%zu\n" \ - "Number of dead records: %zu\n" \ - "Smallest/average/largest dead records: %zu/%zu/%zu\n" \ - "Number of free records: %zu\n" \ - "Smallest/average/largest free records: %zu/%zu/%zu\n" \ - "Number of hash chains: %zu\n" \ - "Smallest/average/largest hash chains: %zu/%zu/%zu\n" \ - "Number of uncoalesced records: %zu\n" \ - "Smallest/average/largest uncoalesced runs: %zu/%zu/%zu\n" \ - "Percentage keys/data/padding/free/dead/rechdrs&tailers/hashes: %.0f/%.0f/%.0f/%.0f/%.0f/%.0f/%.0f\n" - -/* We don't use tally module, to keep upstream happy. */ -struct tally { - size_t min, max, total; - size_t num; -}; - -static void tally1_init(struct tally *tally) -{ - tally->total = 0; - tally->num = 0; - tally->min = tally->max = 0; -} - -static void tally1_add(struct tally *tally, size_t len) -{ - if (tally->num == 0) - tally->max = tally->min = len; - else if (len > tally->max) - tally->max = len; - else if (len < tally->min) - tally->min = len; - tally->num++; - tally->total += len; -} - -static size_t tally1_mean(const struct tally *tally) -{ - if (!tally->num) - return 0; - return tally->total / tally->num; -} - -static size_t get_hash_length(struct tdb_context *tdb, unsigned int i) -{ - tdb1_off_t rec_ptr; - size_t count = 0; - - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(i), &rec_ptr) == -1) - return 0; - - /* keep looking until we find the right record */ - while (rec_ptr) { - struct tdb1_record r; - ++count; - if (tdb1_rec_read(tdb, rec_ptr, &r) == -1) - return 0; - rec_ptr = r.next; - } - return count; -} - -char *tdb1_summary(struct tdb_context *tdb) -{ - tdb1_off_t off, rec_off; - struct tally freet, keys, data, dead, extra, hash, uncoal; - struct tdb1_record rec; - char *ret = NULL; - bool locked; - size_t len, unc = 0; - struct tdb1_record recovery; - - /* We may have a write lock already, so don't lock. */ - if (tdb->file->allrecord_lock.count != 0) { - locked = false; - } else { - if (tdb_lockall_read(tdb) != TDB_SUCCESS) - return NULL; - locked = true; - } - - if (tdb1_recovery_area(tdb, tdb->tdb1.io, &rec_off, &recovery) != 0) { - goto unlock; - } - - tally1_init(&freet); - tally1_init(&keys); - tally1_init(&data); - tally1_init(&dead); - tally1_init(&extra); - tally1_init(&hash); - tally1_init(&uncoal); - - for (off = TDB1_DATA_START(tdb->tdb1.header.hash_size); - off < tdb->file->map_size - 1; - off += sizeof(rec) + rec.rec_len) { - if (tdb->tdb1.io->tdb1_read(tdb, off, &rec, sizeof(rec), - TDB1_DOCONV()) == -1) - goto unlock; - switch (rec.magic) { - case TDB1_MAGIC: - tally1_add(&keys, rec.key_len); - tally1_add(&data, rec.data_len); - tally1_add(&extra, rec.rec_len - (rec.key_len - + rec.data_len)); - if (unc > 1) - tally1_add(&uncoal, unc - 1); - unc = 0; - break; - case TDB1_FREE_MAGIC: - tally1_add(&freet, rec.rec_len); - unc++; - break; - /* If we crash after ftruncate, we can get zeroes or fill. */ - case TDB1_RECOVERY_INVALID_MAGIC: - case 0x42424242: - unc++; - /* If it's a valid recovery, we can trust rec_len. */ - if (off != rec_off) { - rec.rec_len = tdb1_dead_space(tdb, off) - - sizeof(rec); - } - /* Fall through */ - case TDB1_DEAD_MAGIC: - tally1_add(&dead, rec.rec_len); - break; - default: - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "Unexpected record magic 0x%x" - " at offset %d", - rec.magic, off); - goto unlock; - } - } - if (unc > 1) - tally1_add(&uncoal, unc - 1); - - for (off = 0; off < tdb->tdb1.header.hash_size; off++) - tally1_add(&hash, get_hash_length(tdb, off)); - - /* 20 is max length of a %zu. */ - len = strlen(SUMMARY_FORMAT1) + 35*20 + 1; - ret = (char *)malloc(len); - if (!ret) - goto unlock; - - snprintf(ret, len, SUMMARY_FORMAT1, - (tdb1_len_t)tdb->file->map_size, keys.total+data.total, - keys.num, - keys.min, tally1_mean(&keys), keys.max, - data.min, tally1_mean(&data), data.max, - extra.min, tally1_mean(&extra), extra.max, - dead.num, - dead.min, tally1_mean(&dead), dead.max, - freet.num, - freet.min, tally1_mean(&freet), freet.max, - hash.num, - hash.min, tally1_mean(&hash), hash.max, - uncoal.total, - uncoal.min, tally1_mean(&uncoal), uncoal.max, - keys.total * 100.0 / tdb->file->map_size, - data.total * 100.0 / tdb->file->map_size, - extra.total * 100.0 / tdb->file->map_size, - freet.total * 100.0 / tdb->file->map_size, - dead.total * 100.0 / tdb->file->map_size, - (keys.num + freet.num + dead.num) - * (sizeof(struct tdb1_record) + sizeof(uint32_t)) - * 100.0 / tdb->file->map_size, - tdb->tdb1.header.hash_size * sizeof(tdb1_off_t) - * 100.0 / (tdb1_len_t)tdb->file->map_size); - -unlock: - if (locked) { - tdb_unlockall_read(tdb); - } - return ret; -} diff --git a/ccan/tdb2/tdb1_tdb.c b/ccan/tdb2/tdb1_tdb.c deleted file mode 100644 index a220f471..00000000 --- a/ccan/tdb2/tdb1_tdb.c +++ /dev/null @@ -1,829 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 1999-2005 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000-2003 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#include "tdb1_private.h" -#include - -/* - non-blocking increment of the tdb sequence number if the tdb has been opened using - the TDB_SEQNUM flag -*/ -void tdb1_increment_seqnum_nonblock(struct tdb_context *tdb) -{ - tdb1_off_t seqnum=0; - - if (!(tdb->flags & TDB_SEQNUM)) { - return; - } - - /* we ignore errors from this, as we have no sane way of - dealing with them. - */ - tdb1_ofs_read(tdb, TDB1_SEQNUM_OFS, &seqnum); - seqnum++; - tdb1_ofs_write(tdb, TDB1_SEQNUM_OFS, &seqnum); -} - -/* - increment the tdb sequence number if the tdb has been opened using - the TDB_SEQNUM flag -*/ -static void tdb1_increment_seqnum(struct tdb_context *tdb) -{ - if (!(tdb->flags & TDB_SEQNUM)) { - return; - } - - if (tdb1_nest_lock(tdb, TDB1_SEQNUM_OFS, F_WRLCK, - TDB_LOCK_WAIT|TDB_LOCK_PROBE) != 0) { - return; - } - - tdb1_increment_seqnum_nonblock(tdb); - - tdb1_nest_unlock(tdb, TDB1_SEQNUM_OFS, F_WRLCK); -} - -static enum TDB_ERROR tdb1_key_compare(TDB_DATA key, TDB_DATA data, - void *matches_) -{ - bool *matches = matches_; - *matches = (memcmp(data.dptr, key.dptr, data.dsize) == 0); - return TDB_SUCCESS; -} - -/* Returns 0 on fail; last_error will be TDB_ERR_NOEXIST if it simply - * wasn't there, otherwise a real error. - * On success, return offset of record, and fills in rec */ -static tdb1_off_t tdb1_find(struct tdb_context *tdb, TDB_DATA key, uint32_t hash, - struct tdb1_record *r) -{ - tdb1_off_t rec_ptr; - - /* read in the hash top */ - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1) - return 0; - - /* keep looking until we find the right record */ - while (rec_ptr) { - if (tdb1_rec_read(tdb, rec_ptr, r) == -1) - return 0; - - tdb->stats.compares++; - if (TDB1_DEAD(r)) { - tdb->stats.compare_wrong_bucket++; - } else if (key.dsize != r->key_len) { - tdb->stats.compare_wrong_keylen++; - } else if (hash != r->full_hash) { - tdb->stats.compare_wrong_rechash++; - } else { - enum TDB_ERROR ecode; - bool matches; - ecode = tdb1_parse_data(tdb, key, rec_ptr + sizeof(*r), - r->key_len, tdb1_key_compare, - &matches); - - if (ecode != TDB_SUCCESS) { - tdb->last_error = ecode; - return 0; - } - - if (!matches) { - tdb->stats.compare_wrong_keycmp++; - } else { - return rec_ptr; - } - } - /* detect tight infinite loop */ - if (rec_ptr == r->next) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb1_find: loop detected."); - return 0; - } - rec_ptr = r->next; - } - tdb->last_error = TDB_ERR_NOEXIST; - return 0; -} - -/* As tdb1_find, but if you succeed, keep the lock */ -tdb1_off_t tdb1_find_lock_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash, int locktype, - struct tdb1_record *rec) -{ - uint32_t rec_ptr; - - if (tdb1_lock(tdb, TDB1_BUCKET(hash), locktype) == -1) - return 0; - if (!(rec_ptr = tdb1_find(tdb, key, hash, rec))) - tdb1_unlock(tdb, TDB1_BUCKET(hash), locktype); - return rec_ptr; -} - -static TDB_DATA _tdb1_fetch(struct tdb_context *tdb, TDB_DATA key); - -/* update an entry in place - this only works if the new data size - is <= the old data size and the key exists. - on failure return -1. -*/ -static int tdb1_update_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash, TDB_DATA dbuf) -{ - struct tdb1_record rec; - tdb1_off_t rec_ptr; - - /* find entry */ - if (!(rec_ptr = tdb1_find(tdb, key, hash, &rec))) - return -1; - - /* it could be an exact duplicate of what is there - this is - * surprisingly common (eg. with a ldb re-index). */ - if (rec.key_len == key.dsize && - rec.data_len == dbuf.dsize && - rec.full_hash == hash) { - TDB_DATA data = _tdb1_fetch(tdb, key); - if (data.dsize == dbuf.dsize && - memcmp(data.dptr, dbuf.dptr, data.dsize) == 0) { - if (data.dptr) { - free(data.dptr); - } - return 0; - } - if (data.dptr) { - free(data.dptr); - } - } - - /* must be long enough key, data and tailer */ - if (rec.rec_len < key.dsize + dbuf.dsize + sizeof(tdb1_off_t)) { - tdb->last_error = TDB_SUCCESS; /* Not really an error */ - return -1; - } - - if (tdb->tdb1.io->tdb1_write(tdb, rec_ptr + sizeof(rec) + rec.key_len, - dbuf.dptr, dbuf.dsize) == -1) - return -1; - - if (dbuf.dsize != rec.data_len) { - /* update size */ - rec.data_len = dbuf.dsize; - return tdb1_rec_write(tdb, rec_ptr, &rec); - } - - return 0; -} - -/* find an entry in the database given a key */ -/* If an entry doesn't exist tdb1_err will be set to - * TDB_ERR_NOEXIST. If a key has no data attached - * then the TDB_DATA will have zero length but - * a non-zero pointer - */ -static TDB_DATA _tdb1_fetch(struct tdb_context *tdb, TDB_DATA key) -{ - tdb1_off_t rec_ptr; - struct tdb1_record rec; - TDB_DATA ret; - uint32_t hash; - - /* find which hash bucket it is in */ - hash = tdb_hash(tdb, key.dptr, key.dsize); - if (!(rec_ptr = tdb1_find_lock_hash(tdb,key,hash,F_RDLCK,&rec))) { - ret.dptr = NULL; - ret.dsize = 0; - return ret; - } - - ret.dptr = tdb1_alloc_read(tdb, rec_ptr + sizeof(rec) + rec.key_len, - rec.data_len); - ret.dsize = rec.data_len; - tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_RDLCK); - return ret; -} - -enum TDB_ERROR tdb1_fetch(struct tdb_context *tdb, TDB_DATA key, TDB_DATA *data) -{ - *data = _tdb1_fetch(tdb, key); - if (data->dptr == NULL) - return tdb->last_error; - return TDB_SUCCESS; -} - -enum TDB_ERROR tdb1_parse_record(struct tdb_context *tdb, TDB_DATA key, - enum TDB_ERROR (*parser)(TDB_DATA key, - TDB_DATA data, - void *private_data), - void *private_data) -{ - tdb1_off_t rec_ptr; - struct tdb1_record rec; - enum TDB_ERROR ret; - uint32_t hash; - - /* find which hash bucket it is in */ - hash = tdb_hash(tdb, key.dptr, key.dsize); - - if (!(rec_ptr = tdb1_find_lock_hash(tdb,key,hash,F_RDLCK,&rec))) { - return tdb->last_error; - } - - ret = tdb1_parse_data(tdb, key, rec_ptr + sizeof(rec) + rec.key_len, - rec.data_len, parser, private_data); - - tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_RDLCK); - - return ret; -} - -/* check if an entry in the database exists - - note that 1 is returned if the key is found and 0 is returned if not found - this doesn't match the conventions in the rest of this module, but is - compatible with gdbm -*/ -static int tdb1_exists_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash) -{ - struct tdb1_record rec; - - if (tdb1_find_lock_hash(tdb, key, hash, F_RDLCK, &rec) == 0) - return 0; - tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_RDLCK); - return 1; -} - -int tdb1_exists(struct tdb_context *tdb, TDB_DATA key) -{ - uint32_t hash = tdb_hash(tdb, key.dptr, key.dsize); - int ret; - - assert(tdb->flags & TDB_VERSION1); - ret = tdb1_exists_hash(tdb, key, hash); - return ret; -} - -/* actually delete an entry in the database given the offset */ -int tdb1_do_delete(struct tdb_context *tdb, tdb1_off_t rec_ptr, struct tdb1_record *rec) -{ - tdb1_off_t last_ptr, i; - struct tdb1_record lastrec; - - if ((tdb->flags & TDB_RDONLY) || tdb->tdb1.traverse_read) return -1; - - if (((tdb->tdb1.traverse_write != 0) && (!TDB1_DEAD(rec))) || - tdb1_write_lock_record(tdb, rec_ptr) == -1) { - /* Someone traversing here: mark it as dead */ - rec->magic = TDB1_DEAD_MAGIC; - return tdb1_rec_write(tdb, rec_ptr, rec); - } - if (tdb1_write_unlock_record(tdb, rec_ptr) != 0) - return -1; - - /* find previous record in hash chain */ - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(rec->full_hash), &i) == -1) - return -1; - for (last_ptr = 0; i != rec_ptr; last_ptr = i, i = lastrec.next) - if (tdb1_rec_read(tdb, i, &lastrec) == -1) - return -1; - - /* unlink it: next ptr is at start of record. */ - if (last_ptr == 0) - last_ptr = TDB1_HASH_TOP(rec->full_hash); - if (tdb1_ofs_write(tdb, last_ptr, &rec->next) == -1) - return -1; - - /* recover the space */ - if (tdb1_free(tdb, rec_ptr, rec) == -1) - return -1; - return 0; -} - -static int tdb1_count_dead(struct tdb_context *tdb, uint32_t hash) -{ - int res = 0; - tdb1_off_t rec_ptr; - struct tdb1_record rec; - - /* read in the hash top */ - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1) - return 0; - - while (rec_ptr) { - if (tdb1_rec_read(tdb, rec_ptr, &rec) == -1) - return 0; - - if (rec.magic == TDB1_DEAD_MAGIC) { - res += 1; - } - rec_ptr = rec.next; - } - return res; -} - -/* - * Purge all DEAD records from a hash chain - */ -static int tdb1_purge_dead(struct tdb_context *tdb, uint32_t hash) -{ - int res = -1; - struct tdb1_record rec; - tdb1_off_t rec_ptr; - - if (tdb1_lock(tdb, -1, F_WRLCK) == -1) { - return -1; - } - - /* read in the hash top */ - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1) - goto fail; - - while (rec_ptr) { - tdb1_off_t next; - - if (tdb1_rec_read(tdb, rec_ptr, &rec) == -1) { - goto fail; - } - - next = rec.next; - - if (rec.magic == TDB1_DEAD_MAGIC - && tdb1_do_delete(tdb, rec_ptr, &rec) == -1) { - goto fail; - } - rec_ptr = next; - } - res = 0; - fail: - tdb1_unlock(tdb, -1, F_WRLCK); - return res; -} - -/* delete an entry in the database given a key */ -static int tdb1_delete_hash(struct tdb_context *tdb, TDB_DATA key, uint32_t hash) -{ - tdb1_off_t rec_ptr; - struct tdb1_record rec; - int ret; - - if (tdb->tdb1.max_dead_records != 0) { - - /* - * Allow for some dead records per hash chain, mainly for - * tdb's with a very high create/delete rate like locking.tdb. - */ - - if (tdb1_lock(tdb, TDB1_BUCKET(hash), F_WRLCK) == -1) - return -1; - - if (tdb1_count_dead(tdb, hash) >= tdb->tdb1.max_dead_records) { - /* - * Don't let the per-chain freelist grow too large, - * delete all existing dead records - */ - tdb1_purge_dead(tdb, hash); - } - - if (!(rec_ptr = tdb1_find(tdb, key, hash, &rec))) { - tdb1_unlock(tdb, TDB1_BUCKET(hash), F_WRLCK); - return -1; - } - - /* - * Just mark the record as dead. - */ - rec.magic = TDB1_DEAD_MAGIC; - ret = tdb1_rec_write(tdb, rec_ptr, &rec); - } - else { - if (!(rec_ptr = tdb1_find_lock_hash(tdb, key, hash, F_WRLCK, - &rec))) - return -1; - - ret = tdb1_do_delete(tdb, rec_ptr, &rec); - } - - if (ret == 0) { - tdb1_increment_seqnum(tdb); - } - - if (tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_WRLCK) != 0) - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_delete: WARNING tdb1_unlock failed!"); - return ret; -} - -int tdb1_delete(struct tdb_context *tdb, TDB_DATA key) -{ - uint32_t hash = tdb_hash(tdb, key.dptr, key.dsize); - int ret; - - assert(tdb->flags & TDB_VERSION1); - ret = tdb1_delete_hash(tdb, key, hash); - return ret; -} - -/* - * See if we have a dead record around with enough space - */ -static tdb1_off_t tdb1_find_dead(struct tdb_context *tdb, uint32_t hash, - struct tdb1_record *r, tdb1_len_t length) -{ - tdb1_off_t rec_ptr; - - /* read in the hash top */ - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1) - return 0; - - /* keep looking until we find the right record */ - while (rec_ptr) { - if (tdb1_rec_read(tdb, rec_ptr, r) == -1) - return 0; - - if (TDB1_DEAD(r) && r->rec_len >= length) { - /* - * First fit for simple coding, TODO: change to best - * fit - */ - return rec_ptr; - } - rec_ptr = r->next; - } - return 0; -} - -static int _tdb1_store(struct tdb_context *tdb, TDB_DATA key, - TDB_DATA dbuf, int flag, uint32_t hash) -{ - struct tdb1_record rec; - tdb1_off_t rec_ptr; - int ret = -1; - - /* check for it existing, on insert. */ - if (flag == TDB_INSERT) { - if (tdb1_exists_hash(tdb, key, hash)) { - tdb->last_error = TDB_ERR_EXISTS; - goto fail; - } - if (tdb->last_error != TDB_ERR_NOEXIST) { - goto fail; - } - } else { - /* first try in-place update, on modify or replace. */ - if (tdb1_update_hash(tdb, key, hash, dbuf) == 0) { - goto done; - } - if (tdb->last_error != TDB_SUCCESS) { - if (tdb->last_error != TDB_ERR_NOEXIST) { - goto fail; - } - if (flag == TDB_MODIFY) { - /* if the record doesn't exist and we are in TDB1_MODIFY mode then - we should fail the store */ - goto fail; - } - } - } - /* reset the error code potentially set by the tdb1_update() */ - tdb->last_error = TDB_SUCCESS; - - /* delete any existing record - if it doesn't exist we don't - care. Doing this first reduces fragmentation, and avoids - coalescing with `allocated' block before it's updated. */ - if (flag != TDB_INSERT) - tdb1_delete_hash(tdb, key, hash); - - if (tdb->tdb1.max_dead_records != 0) { - /* - * Allow for some dead records per hash chain, look if we can - * find one that can hold the new record. We need enough space - * for key, data and tailer. If we find one, we don't have to - * consult the central freelist. - */ - rec_ptr = tdb1_find_dead( - tdb, hash, &rec, - key.dsize + dbuf.dsize + sizeof(tdb1_off_t)); - - if (rec_ptr != 0) { - rec.key_len = key.dsize; - rec.data_len = dbuf.dsize; - rec.full_hash = hash; - rec.magic = TDB1_MAGIC; - if (tdb1_rec_write(tdb, rec_ptr, &rec) == -1 - || tdb->tdb1.io->tdb1_write( - tdb, rec_ptr + sizeof(rec), - key.dptr, key.dsize) == -1 - || tdb->tdb1.io->tdb1_write( - tdb, rec_ptr + sizeof(rec) + key.dsize, - dbuf.dptr, dbuf.dsize) == -1) { - goto fail; - } - goto done; - } - } - - /* - * We have to allocate some space from the freelist, so this means we - * have to lock it. Use the chance to purge all the DEAD records from - * the hash chain under the freelist lock. - */ - - if (tdb1_lock(tdb, -1, F_WRLCK) == -1) { - goto fail; - } - - if ((tdb->tdb1.max_dead_records != 0) - && (tdb1_purge_dead(tdb, hash) == -1)) { - tdb1_unlock(tdb, -1, F_WRLCK); - goto fail; - } - - /* we have to allocate some space */ - rec_ptr = tdb1_allocate(tdb, key.dsize + dbuf.dsize, &rec); - - tdb1_unlock(tdb, -1, F_WRLCK); - - if (rec_ptr == 0) { - goto fail; - } - - /* Read hash top into next ptr */ - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(hash), &rec.next) == -1) - goto fail; - - rec.key_len = key.dsize; - rec.data_len = dbuf.dsize; - rec.full_hash = hash; - rec.magic = TDB1_MAGIC; - - /* write out and point the top of the hash chain at it */ - if (tdb1_rec_write(tdb, rec_ptr, &rec) == -1 - || tdb->tdb1.io->tdb1_write(tdb, rec_ptr + sizeof(rec), - key.dptr, key.dsize) == -1 - || tdb->tdb1.io->tdb1_write(tdb, rec_ptr + sizeof(rec) + key.dsize, - dbuf.dptr, dbuf.dsize) == -1 - || tdb1_ofs_write(tdb, TDB1_HASH_TOP(hash), &rec_ptr) == -1) { - /* Need to tdb1_unallocate() here */ - goto fail; - } - - done: - ret = 0; - fail: - if (ret == 0) { - tdb1_increment_seqnum(tdb); - } - return ret; -} - -/* store an element in the database, replacing any existing element - with the same key - - return 0 on success, -1 on failure -*/ -int tdb1_store(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, int flag) -{ - uint32_t hash; - int ret; - - assert(tdb->flags & TDB_VERSION1); - - if ((tdb->flags & TDB_RDONLY) || tdb->tdb1.traverse_read) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_RDONLY, - TDB_LOG_USE_ERROR, - "tdb_store: read-only tdb"); - return -1; - } - - /* find which hash bucket it is in */ - hash = tdb_hash(tdb, key.dptr, key.dsize); - if (tdb1_lock(tdb, TDB1_BUCKET(hash), F_WRLCK) == -1) - return -1; - - ret = _tdb1_store(tdb, key, dbuf, flag, hash); - tdb1_unlock(tdb, TDB1_BUCKET(hash), F_WRLCK); - return ret; -} - -/* Append to an entry. Create if not exist. */ -int tdb1_append(struct tdb_context *tdb, TDB_DATA key, TDB_DATA new_dbuf) -{ - uint32_t hash; - TDB_DATA dbuf; - int ret = -1; - - assert(tdb->flags & TDB_VERSION1); - - /* find which hash bucket it is in */ - hash = tdb_hash(tdb, key.dptr, key.dsize); - if (tdb1_lock(tdb, TDB1_BUCKET(hash), F_WRLCK) == -1) - return -1; - - dbuf = _tdb1_fetch(tdb, key); - - if (dbuf.dptr == NULL) { - dbuf.dptr = (unsigned char *)malloc(new_dbuf.dsize); - } else { - unsigned int new_len = dbuf.dsize + new_dbuf.dsize; - unsigned char *new_dptr; - - /* realloc '0' is special: don't do that. */ - if (new_len == 0) - new_len = 1; - new_dptr = (unsigned char *)realloc(dbuf.dptr, new_len); - if (new_dptr == NULL) { - free(dbuf.dptr); - } - dbuf.dptr = new_dptr; - } - - if (dbuf.dptr == NULL) { - tdb->last_error = TDB_ERR_OOM; - goto failed; - } - - memcpy(dbuf.dptr + dbuf.dsize, new_dbuf.dptr, new_dbuf.dsize); - dbuf.dsize += new_dbuf.dsize; - - ret = _tdb1_store(tdb, key, dbuf, 0, hash); - -failed: - tdb1_unlock(tdb, TDB1_BUCKET(hash), F_WRLCK); - SAFE_FREE(dbuf.dptr); - return ret; -} - - -/* - get the tdb sequence number. Only makes sense if the writers opened - with TDB1_SEQNUM set. Note that this sequence number will wrap quite - quickly, so it should only be used for a 'has something changed' - test, not for code that relies on the count of the number of changes - made. If you want a counter then use a tdb record. - - The aim of this sequence number is to allow for a very lightweight - test of a possible tdb change. -*/ -int tdb1_get_seqnum(struct tdb_context *tdb) -{ - tdb1_off_t seqnum=0; - - tdb1_ofs_read(tdb, TDB1_SEQNUM_OFS, &seqnum); - return seqnum; -} - - -/* - add a region of the file to the freelist. Length is the size of the region in bytes, - which includes the free list header that needs to be added - */ -static int tdb1_free_region(struct tdb_context *tdb, tdb1_off_t offset, ssize_t length) -{ - struct tdb1_record rec; - if (length <= sizeof(rec)) { - /* the region is not worth adding */ - return 0; - } - if (length + offset > tdb->file->map_size) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb1_free_region: adding region beyond" - " end of file"); - return -1; - } - memset(&rec,'\0',sizeof(rec)); - rec.rec_len = length - sizeof(rec); - if (tdb1_free(tdb, offset, &rec) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_free_region: failed to add free record"); - return -1; - } - return 0; -} - -/* - wipe the entire database, deleting all records. This can be done - very fast by using a allrecord lock. The entire data portion of the - file becomes a single entry in the freelist. - - This code carefully steps around the recovery area, leaving it alone - */ -int tdb1_wipe_all(struct tdb_context *tdb) -{ - int i; - tdb1_off_t offset = 0; - ssize_t data_len; - tdb1_off_t recovery_head; - tdb1_len_t recovery_size = 0; - - if (tdb_lockall(tdb) != TDB_SUCCESS) { - return -1; - } - - - /* see if the tdb has a recovery area, and remember its size - if so. We don't want to lose this as otherwise each - tdb1_wipe_all() in a transaction will increase the size of - the tdb by the size of the recovery area */ - if (tdb1_ofs_read(tdb, TDB1_RECOVERY_HEAD, &recovery_head) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_wipe_all: failed to read recovery head"); - goto failed; - } - - if (recovery_head != 0) { - struct tdb1_record rec; - if (tdb->tdb1.io->tdb1_read(tdb, recovery_head, &rec, sizeof(rec), TDB1_DOCONV()) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_wipe_all: failed to read recovery record"); - return -1; - } - recovery_size = rec.rec_len + sizeof(rec); - } - - /* wipe the hashes */ - for (i=0;itdb1.header.hash_size;i++) { - if (tdb1_ofs_write(tdb, TDB1_HASH_TOP(i), &offset) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_wipe_all: failed to write hash %d", i); - goto failed; - } - } - - /* wipe the freelist */ - if (tdb1_ofs_write(tdb, TDB1_FREELIST_TOP, &offset) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_wipe_all: failed to write freelist"); - goto failed; - } - - /* add all the rest of the file to the freelist, possibly leaving a gap - for the recovery area */ - if (recovery_size == 0) { - /* the simple case - the whole file can be used as a freelist */ - data_len = (tdb->file->map_size - TDB1_DATA_START(tdb->tdb1.header.hash_size)); - if (tdb1_free_region(tdb, TDB1_DATA_START(tdb->tdb1.header.hash_size), data_len) != 0) { - goto failed; - } - } else { - /* we need to add two freelist entries - one on either - side of the recovery area - - Note that we cannot shift the recovery area during - this operation. Only the transaction.c code may - move the recovery area or we risk subtle data - corruption - */ - data_len = (recovery_head - TDB1_DATA_START(tdb->tdb1.header.hash_size)); - if (tdb1_free_region(tdb, TDB1_DATA_START(tdb->tdb1.header.hash_size), data_len) != 0) { - goto failed; - } - /* and the 2nd free list entry after the recovery area - if any */ - data_len = tdb->file->map_size - (recovery_head+recovery_size); - if (tdb1_free_region(tdb, recovery_head+recovery_size, data_len) != 0) { - goto failed; - } - } - - tdb1_increment_seqnum_nonblock(tdb); - tdb_unlockall(tdb); - return 0; - -failed: - tdb_unlockall(tdb); - return -1; -} - -/* Even on files, we can get partial writes due to signals. */ -bool tdb1_write_all(int fd, const void *buf, size_t count) -{ - while (count) { - ssize_t ret; - ret = write(fd, buf, count); - if (ret < 0) - return false; - buf = (const char *)buf + ret; - count -= ret; - } - return true; -} diff --git a/ccan/tdb2/tdb1_transaction.c b/ccan/tdb2/tdb1_transaction.c deleted file mode 100644 index 9cb95235..00000000 --- a/ccan/tdb2/tdb1_transaction.c +++ /dev/null @@ -1,1339 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 2005 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#include "tdb1_private.h" - -/* - transaction design: - - - only allow a single transaction at a time per database. This makes - using the transaction API simpler, as otherwise the caller would - have to cope with temporary failures in transactions that conflict - with other current transactions - - - keep the transaction recovery information in the same file as the - database, using a special 'transaction recovery' record pointed at - by the header. This removes the need for extra journal files as - used by some other databases - - - dynamically allocated the transaction recover record, re-using it - for subsequent transactions. If a larger record is needed then - tdb1_free() the old record to place it on the normal tdb freelist - before allocating the new record - - - during transactions, keep a linked list of writes all that have - been performed by intercepting all tdb1_write() calls. The hooked - transaction versions of tdb1_read() and tdb1_write() check this - linked list and try to use the elements of the list in preference - to the real database. - - - don't allow any locks to be held when a transaction starts, - otherwise we can end up with deadlock (plus lack of lock nesting - in posix locks would mean the lock is lost) - - - if the caller gains a lock during the transaction but doesn't - release it then fail the commit - - - allow for nested calls to tdb1_transaction_start(), re-using the - existing transaction record. If the inner transaction is cancelled - then a subsequent commit will fail - - - keep a mirrored copy of the tdb hash chain heads to allow for the - fast hash heads scan on traverse, updating the mirrored copy in - the transaction version of tdb1_write - - - allow callers to mix transaction and non-transaction use of tdb, - although once a transaction is started then an exclusive lock is - gained until the transaction is committed or cancelled - - - the commit stategy involves first saving away all modified data - into a linearised buffer in the transaction recovery area, then - marking the transaction recovery area with a magic value to - indicate a valid recovery record. In total 4 fsync/msync calls are - needed per commit to prevent race conditions. It might be possible - to reduce this to 3 or even 2 with some more work. - - - check for a valid recovery record on open of the tdb, while the - open lock is held. Automatically recover from the transaction - recovery area if needed, then continue with the open as - usual. This allows for smooth crash recovery with no administrator - intervention. - - - if TDB_NOSYNC is passed to flags in tdb1_open then transactions are - still available, but no transaction recovery area is used and no - fsync/msync calls are made. - - - if TDB_ALLOW_NESTING is passed to flags in tdb open, or added using - tdb1_add_flags() transaction nesting is enabled. - The default is that transaction nesting is NOT allowed. - - Beware. when transactions are nested a transaction successfully - completed with tdb1_transaction_commit() can be silently unrolled later. -*/ - - -/* - hold the context of any current transaction -*/ -struct tdb1_transaction { - /* we keep a mirrored copy of the tdb hash heads here so - tdb1_next_hash_chain() can operate efficiently */ - uint32_t *hash_heads; - - /* the original io methods - used to do IOs to the real db */ - const struct tdb1_methods *io_methods; - - /* the list of transaction blocks. When a block is first - written to, it gets created in this list */ - uint8_t **blocks; - uint32_t num_blocks; - uint32_t block_size; /* bytes in each block */ - uint32_t last_block_size; /* number of valid bytes in the last block */ - - /* non-zero when an internal transaction error has - occurred. All write operations will then fail until the - transaction is ended */ - int transaction_error; - - /* when inside a transaction we need to keep track of any - nested tdb1_transaction_start() calls, as these are allowed, - but don't create a new transaction */ - int nesting; - - /* set when a prepare has already occurred */ - bool prepared; - tdb1_off_t magic_offset; - - /* old file size before transaction */ - tdb1_len_t old_map_size; - - /* did we expand in this transaction */ - bool expanded; -}; - - -/* - read while in a transaction. We need to check first if the data is in our list - of transaction elements, then if not do a real read -*/ -static int transaction1_read(struct tdb_context *tdb, tdb1_off_t off, void *buf, - tdb1_len_t len, int cv) -{ - uint32_t blk; - - /* break it down into block sized ops */ - while (len + (off % tdb->tdb1.transaction->block_size) > tdb->tdb1.transaction->block_size) { - tdb1_len_t len2 = tdb->tdb1.transaction->block_size - (off % tdb->tdb1.transaction->block_size); - if (transaction1_read(tdb, off, buf, len2, cv) != 0) { - return -1; - } - len -= len2; - off += len2; - buf = (void *)(len2 + (char *)buf); - } - - if (len == 0) { - return 0; - } - - blk = off / tdb->tdb1.transaction->block_size; - - /* see if we have it in the block list */ - if (tdb->tdb1.transaction->num_blocks <= blk || - tdb->tdb1.transaction->blocks[blk] == NULL) { - /* nope, do a real read */ - if (tdb->tdb1.transaction->io_methods->tdb1_read(tdb, off, buf, len, cv) != 0) { - goto fail; - } - return 0; - } - - /* it is in the block list. Now check for the last block */ - if (blk == tdb->tdb1.transaction->num_blocks-1) { - if (len > tdb->tdb1.transaction->last_block_size) { - goto fail; - } - } - - /* now copy it out of this block */ - memcpy(buf, tdb->tdb1.transaction->blocks[blk] + (off % tdb->tdb1.transaction->block_size), len); - if (cv) { - tdb1_convert(buf, len); - } - return 0; - -fail: - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "transaction_read: failed at off=%d len=%d", - off, len); - tdb->tdb1.transaction->transaction_error = 1; - return -1; -} - - -/* - write while in a transaction -*/ -static int transaction1_write(struct tdb_context *tdb, tdb1_off_t off, - const void *buf, tdb1_len_t len) -{ - uint32_t blk; - - /* Only a commit is allowed on a prepared transaction */ - if (tdb->tdb1.transaction->prepared) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "transaction_write: transaction already" - " prepared, write not allowed"); - tdb->tdb1.transaction->transaction_error = 1; - return -1; - } - - /* if the write is to a hash head, then update the transaction - hash heads */ - if (len == sizeof(tdb1_off_t) && off >= TDB1_FREELIST_TOP && - off < TDB1_FREELIST_TOP+TDB1_HASHTABLE_SIZE(tdb)) { - uint32_t chain = (off-TDB1_FREELIST_TOP) / sizeof(tdb1_off_t); - memcpy(&tdb->tdb1.transaction->hash_heads[chain], buf, len); - } - - /* break it up into block sized chunks */ - while (len + (off % tdb->tdb1.transaction->block_size) > tdb->tdb1.transaction->block_size) { - tdb1_len_t len2 = tdb->tdb1.transaction->block_size - (off % tdb->tdb1.transaction->block_size); - if (transaction1_write(tdb, off, buf, len2) != 0) { - return -1; - } - len -= len2; - off += len2; - if (buf != NULL) { - buf = (const void *)(len2 + (const char *)buf); - } - } - - if (len == 0) { - return 0; - } - - blk = off / tdb->tdb1.transaction->block_size; - off = off % tdb->tdb1.transaction->block_size; - - if (tdb->tdb1.transaction->num_blocks <= blk) { - uint8_t **new_blocks; - /* expand the blocks array */ - if (tdb->tdb1.transaction->blocks == NULL) { - new_blocks = (uint8_t **)malloc( - (blk+1)*sizeof(uint8_t *)); - } else { - new_blocks = (uint8_t **)realloc( - tdb->tdb1.transaction->blocks, - (blk+1)*sizeof(uint8_t *)); - } - if (new_blocks == NULL) { - tdb->last_error = TDB_ERR_OOM; - goto fail; - } - memset(&new_blocks[tdb->tdb1.transaction->num_blocks], 0, - (1+(blk - tdb->tdb1.transaction->num_blocks))*sizeof(uint8_t *)); - tdb->tdb1.transaction->blocks = new_blocks; - tdb->tdb1.transaction->num_blocks = blk+1; - tdb->tdb1.transaction->last_block_size = 0; - } - - /* allocate and fill a block? */ - if (tdb->tdb1.transaction->blocks[blk] == NULL) { - tdb->tdb1.transaction->blocks[blk] = (uint8_t *)calloc(tdb->tdb1.transaction->block_size, 1); - if (tdb->tdb1.transaction->blocks[blk] == NULL) { - tdb->last_error = TDB_ERR_OOM; - tdb->tdb1.transaction->transaction_error = 1; - return -1; - } - if (tdb->tdb1.transaction->old_map_size > blk * tdb->tdb1.transaction->block_size) { - tdb1_len_t len2 = tdb->tdb1.transaction->block_size; - if (len2 + (blk * tdb->tdb1.transaction->block_size) > tdb->tdb1.transaction->old_map_size) { - len2 = tdb->tdb1.transaction->old_map_size - (blk * tdb->tdb1.transaction->block_size); - } - if (tdb->tdb1.transaction->io_methods->tdb1_read(tdb, blk * tdb->tdb1.transaction->block_size, - tdb->tdb1.transaction->blocks[blk], - len2, 0) != 0) { - SAFE_FREE(tdb->tdb1.transaction->blocks[blk]); - tdb->last_error = TDB_ERR_IO; - goto fail; - } - if (blk == tdb->tdb1.transaction->num_blocks-1) { - tdb->tdb1.transaction->last_block_size = len2; - } - } - } - - /* overwrite part of an existing block */ - if (buf == NULL) { - memset(tdb->tdb1.transaction->blocks[blk] + off, 0, len); - } else { - memcpy(tdb->tdb1.transaction->blocks[blk] + off, buf, len); - } - if (blk == tdb->tdb1.transaction->num_blocks-1) { - if (len + off > tdb->tdb1.transaction->last_block_size) { - tdb->tdb1.transaction->last_block_size = len + off; - } - } - - return 0; - -fail: - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "transaction_write: failed at off=%d len=%d", - (blk*tdb->tdb1.transaction->block_size) + off, len); - tdb->tdb1.transaction->transaction_error = 1; - return -1; -} - - -/* - write while in a transaction - this varient never expands the transaction blocks, it only - updates existing blocks. This means it cannot change the recovery size -*/ -static int transaction1_write_existing(struct tdb_context *tdb, tdb1_off_t off, - const void *buf, tdb1_len_t len) -{ - uint32_t blk; - - /* break it up into block sized chunks */ - while (len + (off % tdb->tdb1.transaction->block_size) > tdb->tdb1.transaction->block_size) { - tdb1_len_t len2 = tdb->tdb1.transaction->block_size - (off % tdb->tdb1.transaction->block_size); - if (transaction1_write_existing(tdb, off, buf, len2) != 0) { - return -1; - } - len -= len2; - off += len2; - if (buf != NULL) { - buf = (const void *)(len2 + (const char *)buf); - } - } - - if (len == 0) { - return 0; - } - - blk = off / tdb->tdb1.transaction->block_size; - off = off % tdb->tdb1.transaction->block_size; - - if (tdb->tdb1.transaction->num_blocks <= blk || - tdb->tdb1.transaction->blocks[blk] == NULL) { - return 0; - } - - if (blk == tdb->tdb1.transaction->num_blocks-1 && - off + len > tdb->tdb1.transaction->last_block_size) { - if (off >= tdb->tdb1.transaction->last_block_size) { - return 0; - } - len = tdb->tdb1.transaction->last_block_size - off; - } - - /* overwrite part of an existing block */ - memcpy(tdb->tdb1.transaction->blocks[blk] + off, buf, len); - - return 0; -} - - -/* - accelerated hash chain head search, using the cached hash heads -*/ -static void transaction1_next_hash_chain(struct tdb_context *tdb, uint32_t *chain) -{ - uint32_t h = *chain; - for (;h < tdb->tdb1.header.hash_size;h++) { - /* the +1 takes account of the freelist */ - if (0 != tdb->tdb1.transaction->hash_heads[h+1]) { - break; - } - } - (*chain) = h; -} - -/* - out of bounds check during a transaction -*/ -static int transaction1_oob(struct tdb_context *tdb, tdb1_off_t off, tdb1_off_t len, int probe) -{ - if (off + len >= off && off + len <= tdb->file->map_size) { - return 0; - } - tdb->last_error = TDB_ERR_IO; - return -1; -} - -/* - transaction version of tdb1_expand(). -*/ -static int transaction1_expand_file(struct tdb_context *tdb, tdb1_off_t size, - tdb1_off_t addition) -{ - /* add a write to the transaction elements, so subsequent - reads see the zero data */ - if (transaction1_write(tdb, size, NULL, addition) != 0) { - return -1; - } - - tdb->tdb1.transaction->expanded = true; - - return 0; -} - -static const struct tdb1_methods transaction1_methods = { - transaction1_read, - transaction1_write, - transaction1_next_hash_chain, - transaction1_oob, - transaction1_expand_file, -}; - - -/* - start a tdb transaction. No token is returned, as only a single - transaction is allowed to be pending per tdb_context -*/ -static int _tdb1_transaction_start(struct tdb_context *tdb) -{ - /* some sanity checks */ - if (tdb->flags & TDB_INTERNAL) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb1_transaction_start:" - " cannot start a" - " transaction on an" - " internal tdb"); - return -1; - } - - if ((tdb->flags & TDB_RDONLY) || tdb->tdb1.traverse_read) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_RDONLY, - TDB_LOG_USE_ERROR, - "tdb_transaction_start:" - " cannot start a" - " transaction on a " - " read-only tdb"); - return -1; - } - - /* cope with nested tdb1_transaction_start() calls */ - if (tdb->tdb1.transaction != NULL) { - if (!(tdb->flags & TDB_ALLOW_NESTING)) { - tdb->last_error - = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_transaction_start:" - " already inside transaction"); - return -1; - } - tdb->stats.transaction_nest++; - tdb->tdb1.transaction->nesting++; - return 0; - } - - if (tdb1_have_extra_locks(tdb)) { - /* the caller must not have any locks when starting a - transaction as otherwise we'll be screwed by lack - of nested locks in posix */ - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb1_transaction_start: cannot start a" - " transaction with locks held"); - return -1; - } - - if (tdb->tdb1.travlocks.next != NULL) { - /* you cannot use transactions inside a traverse (although you can use - traverse inside a transaction) as otherwise you can end up with - deadlock */ - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb1_transaction_start: cannot start a" - " transaction within a traverse"); - return -1; - } - - tdb->tdb1.transaction = (struct tdb1_transaction *) - calloc(sizeof(struct tdb1_transaction), 1); - if (tdb->tdb1.transaction == NULL) { - tdb->last_error = TDB_ERR_OOM; - return -1; - } - - /* a page at a time seems like a reasonable compromise between compactness and efficiency */ - tdb->tdb1.transaction->block_size = tdb->tdb1.page_size; - - /* get the transaction write lock. This is a blocking lock. As - discussed with Volker, there are a number of ways we could - make this async, which we will probably do in the future */ - if (tdb1_transaction_lock(tdb, F_WRLCK, TDB_LOCK_WAIT) == -1) { - SAFE_FREE(tdb->tdb1.transaction->blocks); - SAFE_FREE(tdb->tdb1.transaction); - return -1; - } - - /* get a read lock from the freelist to the end of file. This - is upgraded to a write lock during the commit */ - if (tdb1_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_WAIT, true) == -1) { - if (errno != EAGAIN && errno != EINTR) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_start:" - " failed to get hash locks"); - } - goto fail_allrecord_lock; - } - - /* setup a copy of the hash table heads so the hash scan in - traverse can be fast */ - tdb->tdb1.transaction->hash_heads = (uint32_t *) - calloc(tdb->tdb1.header.hash_size+1, sizeof(uint32_t)); - if (tdb->tdb1.transaction->hash_heads == NULL) { - tdb->last_error = TDB_ERR_OOM; - goto fail; - } - if (tdb->tdb1.io->tdb1_read(tdb, TDB1_FREELIST_TOP, tdb->tdb1.transaction->hash_heads, - TDB1_HASHTABLE_SIZE(tdb), 0) != 0) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_start: failed to read hash heads"); - goto fail; - } - - /* make sure we know about any file expansions already done by - anyone else */ - tdb->tdb1.io->tdb1_oob(tdb, tdb->file->map_size, 1, 1); - tdb->tdb1.transaction->old_map_size = tdb->file->map_size; - - /* finally hook the io methods, replacing them with - transaction specific methods */ - tdb->tdb1.transaction->io_methods = tdb->tdb1.io; - tdb->tdb1.io = &transaction1_methods; - - tdb->stats.transactions++; - return 0; - -fail: - tdb1_allrecord_unlock(tdb, F_RDLCK); -fail_allrecord_lock: - tdb1_transaction_unlock(tdb, F_WRLCK); - SAFE_FREE(tdb->tdb1.transaction->blocks); - SAFE_FREE(tdb->tdb1.transaction->hash_heads); - SAFE_FREE(tdb->tdb1.transaction); - return -1; -} - -int tdb1_transaction_start(struct tdb_context *tdb) -{ - return _tdb1_transaction_start(tdb); -} - -/* - sync to disk -*/ -static int transaction1_sync(struct tdb_context *tdb, tdb1_off_t offset, tdb1_len_t length) -{ - if (tdb->flags & TDB_NOSYNC) { - return 0; - } - -#if HAVE_FDATASYNC - if (fdatasync(tdb->file->fd) != 0) { -#else - if (fsync(tdb->file->fd) != 0) { -#endif - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_transaction: fsync failed"); - return -1; - } -#if HAVE_MMAP - if (tdb->file->map_ptr) { - tdb1_off_t moffset = offset & ~(tdb->tdb1.page_size-1); - if (msync(moffset + (char *)tdb->file->map_ptr, - length + (offset - moffset), MS_SYNC) != 0) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_transaction:" - " msync failed - %s", - strerror(errno)); - return -1; - } - } -#endif - return 0; -} - - -static int _tdb1_transaction_cancel(struct tdb_context *tdb) -{ - int i, ret = 0; - - if (tdb->tdb1.transaction == NULL) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb1_transaction_cancel:" - " no transaction"); - return -1; - } - - if (tdb->tdb1.transaction->nesting != 0) { - tdb->tdb1.transaction->transaction_error = 1; - tdb->tdb1.transaction->nesting--; - return 0; - } - - tdb->file->map_size = tdb->tdb1.transaction->old_map_size; - - /* free all the transaction blocks */ - for (i=0;itdb1.transaction->num_blocks;i++) { - if (tdb->tdb1.transaction->blocks[i] != NULL) { - free(tdb->tdb1.transaction->blocks[i]); - } - } - SAFE_FREE(tdb->tdb1.transaction->blocks); - - if (tdb->tdb1.transaction->magic_offset) { - const struct tdb1_methods *methods = tdb->tdb1.transaction->io_methods; - const uint32_t invalid = TDB1_RECOVERY_INVALID_MAGIC; - - /* remove the recovery marker */ - if (methods->tdb1_write(tdb, tdb->tdb1.transaction->magic_offset, &invalid, 4) == -1 || - transaction1_sync(tdb, tdb->tdb1.transaction->magic_offset, 4) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_cancel: failed to" - " remove recovery magic"); - ret = -1; - } - } - - /* This also removes the OPEN_LOCK, if we have it. */ - tdb1_release_transaction_locks(tdb); - - /* restore the normal io methods */ - tdb->tdb1.io = tdb->tdb1.transaction->io_methods; - - SAFE_FREE(tdb->tdb1.transaction->hash_heads); - SAFE_FREE(tdb->tdb1.transaction); - - return ret; -} - -/* - cancel the current transaction -*/ -int tdb1_transaction_cancel(struct tdb_context *tdb) -{ - tdb->stats.transaction_cancel++; - return _tdb1_transaction_cancel(tdb); -} - -/* - work out how much space the linearised recovery data will consume -*/ -static tdb1_len_t tdb1_recovery_size(struct tdb_context *tdb) -{ - tdb1_len_t recovery_size = 0; - int i; - - recovery_size = sizeof(uint32_t); - for (i=0;itdb1.transaction->num_blocks;i++) { - if (i * tdb->tdb1.transaction->block_size >= tdb->tdb1.transaction->old_map_size) { - break; - } - if (tdb->tdb1.transaction->blocks[i] == NULL) { - continue; - } - recovery_size += 2*sizeof(tdb1_off_t); - if (i == tdb->tdb1.transaction->num_blocks-1) { - recovery_size += tdb->tdb1.transaction->last_block_size; - } else { - recovery_size += tdb->tdb1.transaction->block_size; - } - } - - return recovery_size; -} - -int tdb1_recovery_area(struct tdb_context *tdb, - const struct tdb1_methods *methods, - tdb1_off_t *recovery_offset, - struct tdb1_record *rec) -{ - if (tdb1_ofs_read(tdb, TDB1_RECOVERY_HEAD, recovery_offset) == -1) { - return -1; - } - - if (*recovery_offset == 0) { - rec->rec_len = 0; - return 0; - } - - if (methods->tdb1_read(tdb, *recovery_offset, rec, sizeof(*rec), - TDB1_DOCONV()) == -1) { - return -1; - } - - /* ignore invalid recovery regions: can happen in crash */ - if (rec->magic != TDB1_RECOVERY_MAGIC && - rec->magic != TDB1_RECOVERY_INVALID_MAGIC) { - *recovery_offset = 0; - rec->rec_len = 0; - } - return 0; -} - -/* - allocate the recovery area, or use an existing recovery area if it is - large enough -*/ -static int tdb1_recovery_allocate(struct tdb_context *tdb, - tdb1_len_t *recovery_size, - tdb1_off_t *recovery_offset, - tdb1_len_t *recovery_max_size) -{ - struct tdb1_record rec; - const struct tdb1_methods *methods = tdb->tdb1.transaction->io_methods; - tdb1_off_t recovery_head; - - if (tdb1_recovery_area(tdb, methods, &recovery_head, &rec) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_recovery_allocate:" - " failed to read recovery head"); - return -1; - } - - *recovery_size = tdb1_recovery_size(tdb); - - if (recovery_head != 0 && *recovery_size <= rec.rec_len) { - /* it fits in the existing area */ - *recovery_max_size = rec.rec_len; - *recovery_offset = recovery_head; - return 0; - } - - /* we need to free up the old recovery area, then allocate a - new one at the end of the file. Note that we cannot use - tdb1_allocate() to allocate the new one as that might return - us an area that is being currently used (as of the start of - the transaction) */ - if (recovery_head != 0) { - if (tdb1_free(tdb, recovery_head, &rec) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_recovery_allocate: failed to free" - " previous recovery area"); - return -1; - } - } - - /* the tdb1_free() call might have increased the recovery size */ - *recovery_size = tdb1_recovery_size(tdb); - - /* round up to a multiple of page size */ - *recovery_max_size = tdb1_expand_adjust(tdb->file->map_size, - *recovery_size, - tdb->tdb1.page_size) - - sizeof(rec); - - *recovery_offset = tdb->file->map_size; - recovery_head = *recovery_offset; - - if (methods->tdb1_expand_file(tdb, tdb->tdb1.transaction->old_map_size, - (tdb->file->map_size - tdb->tdb1.transaction->old_map_size) + - sizeof(rec) + *recovery_max_size) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_recovery_allocate:" - " failed to create recovery area"); - return -1; - } - tdb->stats.transaction_expand_file++; - - /* remap the file (if using mmap) */ - methods->tdb1_oob(tdb, tdb->file->map_size, 1, 1); - - /* we have to reset the old map size so that we don't try to expand the file - again in the transaction commit, which would destroy the recovery area */ - tdb->tdb1.transaction->old_map_size = tdb->file->map_size; - - /* write the recovery header offset and sync - we can sync without a race here - as the magic ptr in the recovery record has not been set */ - TDB1_CONV(recovery_head); - if (methods->tdb1_write(tdb, TDB1_RECOVERY_HEAD, - &recovery_head, sizeof(tdb1_off_t)) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_recovery_allocate:" - " failed to write recovery head"); - return -1; - } - if (transaction1_write_existing(tdb, TDB1_RECOVERY_HEAD, &recovery_head, sizeof(tdb1_off_t)) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_recovery_allocate:" - " failed to write recovery head"); - return -1; - } - - return 0; -} - - -/* - setup the recovery data that will be used on a crash during commit -*/ -static int transaction1_setup_recovery(struct tdb_context *tdb, - tdb1_off_t *magic_offset) -{ - tdb1_len_t recovery_size; - unsigned char *data, *p; - const struct tdb1_methods *methods = tdb->tdb1.transaction->io_methods; - struct tdb1_record *rec; - tdb1_off_t recovery_offset, recovery_max_size; - tdb1_off_t old_map_size = tdb->tdb1.transaction->old_map_size; - uint32_t magic, tailer; - int i; - - /* - check that the recovery area has enough space - */ - if (tdb1_recovery_allocate(tdb, &recovery_size, - &recovery_offset, &recovery_max_size) == -1) { - return -1; - } - - data = (unsigned char *)malloc(recovery_size + sizeof(*rec)); - if (data == NULL) { - tdb->last_error = TDB_ERR_OOM; - return -1; - } - - rec = (struct tdb1_record *)data; - memset(rec, 0, sizeof(*rec)); - - rec->magic = TDB1_RECOVERY_INVALID_MAGIC; - rec->data_len = recovery_size; - rec->rec_len = recovery_max_size; - rec->key_len = old_map_size; - TDB1_CONV(*rec); - - /* build the recovery data into a single blob to allow us to do a single - large write, which should be more efficient */ - p = data + sizeof(*rec); - for (i=0;itdb1.transaction->num_blocks;i++) { - tdb1_off_t offset; - tdb1_len_t length; - - if (tdb->tdb1.transaction->blocks[i] == NULL) { - continue; - } - - offset = i * tdb->tdb1.transaction->block_size; - length = tdb->tdb1.transaction->block_size; - if (i == tdb->tdb1.transaction->num_blocks-1) { - length = tdb->tdb1.transaction->last_block_size; - } - - if (offset >= old_map_size) { - continue; - } - if (offset + length > tdb->tdb1.transaction->old_map_size) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb1_transaction_setup_recovery: transaction data over new region boundary"); - free(data); - return -1; - } - memcpy(p, &offset, 4); - memcpy(p+4, &length, 4); - if (TDB1_DOCONV()) { - tdb1_convert(p, 8); - } - /* the recovery area contains the old data, not the - new data, so we have to call the original tdb1_read - method to get it */ - if (methods->tdb1_read(tdb, offset, p + 8, length, 0) != 0) { - free(data); - tdb->last_error = TDB_ERR_IO; - return -1; - } - p += 8 + length; - } - - /* and the tailer */ - tailer = sizeof(*rec) + recovery_max_size; - memcpy(p, &tailer, 4); - if (TDB1_DOCONV()) { - tdb1_convert(p, 4); - } - - /* write the recovery data to the recovery area */ - if (methods->tdb1_write(tdb, recovery_offset, data, sizeof(*rec) + recovery_size) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_setup_recovery:" - " failed to write recovery data"); - free(data); - return -1; - } - if (transaction1_write_existing(tdb, recovery_offset, data, sizeof(*rec) + recovery_size) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_setup_recovery: failed to write" - " secondary recovery data"); - free(data); - return -1; - } - - /* as we don't have ordered writes, we have to sync the recovery - data before we update the magic to indicate that the recovery - data is present */ - if (transaction1_sync(tdb, recovery_offset, sizeof(*rec) + recovery_size) == -1) { - free(data); - return -1; - } - - free(data); - - magic = TDB1_RECOVERY_MAGIC; - TDB1_CONV(magic); - - *magic_offset = recovery_offset + offsetof(struct tdb1_record, magic); - - if (methods->tdb1_write(tdb, *magic_offset, &magic, sizeof(magic)) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_setup_recovery:" - " failed to write recovery magic"); - return -1; - } - if (transaction1_write_existing(tdb, *magic_offset, &magic, sizeof(magic)) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_setup_recovery:" - " failed to write secondary recovery magic"); - return -1; - } - - /* ensure the recovery magic marker is on disk */ - if (transaction1_sync(tdb, *magic_offset, sizeof(magic)) == -1) { - return -1; - } - - return 0; -} - -static int _tdb1_transaction_prepare_commit(struct tdb_context *tdb) -{ - const struct tdb1_methods *methods; - - if (tdb->tdb1.transaction == NULL) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb1_transaction_prepare_commit:" - " no transaction"); - return -1; - } - - if (tdb->tdb1.transaction->prepared) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb1_transaction_prepare_commit:" - " transaction already prepared"); - _tdb1_transaction_cancel(tdb); - return -1; - } - - if (tdb->tdb1.transaction->transaction_error) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_transaction_prepare_commit:" - " transaction error pending"); - _tdb1_transaction_cancel(tdb); - return -1; - } - - - if (tdb->tdb1.transaction->nesting != 0) { - return 0; - } - - /* check for a null transaction */ - if (tdb->tdb1.transaction->blocks == NULL) { - return 0; - } - - methods = tdb->tdb1.transaction->io_methods; - - /* if there are any locks pending then the caller has not - nested their locks properly, so fail the transaction */ - if (tdb1_have_extra_locks(tdb)) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, TDB_LOG_USE_ERROR, - "tdb1_transaction_prepare_commit:" - " locks pending on commit"); - _tdb1_transaction_cancel(tdb); - return -1; - } - - /* upgrade the main transaction lock region to a write lock */ - if (tdb1_allrecord_upgrade(tdb) == -1) { - if (errno != EAGAIN && errno != EINTR) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_prepare_commit:" - " failed to upgrade hash locks"); - } - return -1; - } - - /* get the open lock - this prevents new users attaching to the database - during the commit */ - if (tdb1_nest_lock(tdb, TDB1_OPEN_LOCK, F_WRLCK, TDB_LOCK_WAIT) == -1) { - if (errno != EAGAIN && errno != EINTR) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_prepare_commit:" - " failed to get open lock"); - } - return -1; - } - - if (!(tdb->flags & TDB_NOSYNC)) { - /* write the recovery data to the end of the file */ - if (transaction1_setup_recovery(tdb, &tdb->tdb1.transaction->magic_offset) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_prepare_commit:" - " failed to setup recovery data"); - return -1; - } - } - - tdb->tdb1.transaction->prepared = true; - - /* expand the file to the new size if needed */ - if (tdb->file->map_size != tdb->tdb1.transaction->old_map_size) { - if (methods->tdb1_expand_file(tdb, tdb->tdb1.transaction->old_map_size, - tdb->file->map_size - - tdb->tdb1.transaction->old_map_size) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_prepare_commit:" - " expansion failed"); - return -1; - } - tdb->stats.transaction_expand_file++; - tdb->file->map_size = tdb->tdb1.transaction->old_map_size; - methods->tdb1_oob(tdb, tdb->file->map_size, 1, 1); - } - - /* Keep the open lock until the actual commit */ - - return 0; -} - -/* - prepare to commit the current transaction -*/ -int tdb1_transaction_prepare_commit(struct tdb_context *tdb) -{ - return _tdb1_transaction_prepare_commit(tdb); -} - -/* A repack is worthwhile if the largest is less than half total free. */ -static bool repack_worthwhile(struct tdb_context *tdb) -{ - tdb1_off_t ptr; - struct tdb1_record rec; - tdb1_len_t total = 0, largest = 0; - - if (tdb1_ofs_read(tdb, TDB1_FREELIST_TOP, &ptr) == -1) { - return false; - } - - while (ptr != 0 && tdb1_rec_free_read(tdb, ptr, &rec) == 0) { - total += rec.rec_len; - if (rec.rec_len > largest) { - largest = rec.rec_len; - } - ptr = rec.next; - } - - return total > largest * 2; -} - -/* - commit the current transaction -*/ -int tdb1_transaction_commit(struct tdb_context *tdb) -{ - const struct tdb1_methods *methods; - int i; - bool need_repack = false; - - if (tdb->tdb1.transaction == NULL) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb1_transaction_commit:" - " no transaction"); - return -1; - } - - if (tdb->tdb1.transaction->transaction_error) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb1_transaction_commit:" - " transaction error pending"); - _tdb1_transaction_cancel(tdb); - return -1; - } - - - if (tdb->tdb1.transaction->nesting != 0) { - tdb->tdb1.transaction->nesting--; - return 0; - } - - /* check for a null transaction */ - if (tdb->tdb1.transaction->blocks == NULL) { - _tdb1_transaction_cancel(tdb); - return 0; - } - - if (!tdb->tdb1.transaction->prepared) { - int ret = _tdb1_transaction_prepare_commit(tdb); - if (ret) { - _tdb1_transaction_cancel(tdb); - return ret; - } - } - - methods = tdb->tdb1.transaction->io_methods; - - /* perform all the writes */ - for (i=0;itdb1.transaction->num_blocks;i++) { - tdb1_off_t offset; - tdb1_len_t length; - - if (tdb->tdb1.transaction->blocks[i] == NULL) { - continue; - } - - offset = i * tdb->tdb1.transaction->block_size; - length = tdb->tdb1.transaction->block_size; - if (i == tdb->tdb1.transaction->num_blocks-1) { - length = tdb->tdb1.transaction->last_block_size; - } - - if (methods->tdb1_write(tdb, offset, tdb->tdb1.transaction->blocks[i], length) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_commit:" - " write failed during commit"); - - /* we've overwritten part of the data and - possibly expanded the file, so we need to - run the crash recovery code */ - tdb->tdb1.io = methods; - tdb1_transaction_recover(tdb); - - _tdb1_transaction_cancel(tdb); - - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_commit: write failed"); - return -1; - } - SAFE_FREE(tdb->tdb1.transaction->blocks[i]); - } - - /* Do this before we drop lock or blocks. */ - if (tdb->tdb1.transaction->expanded) { - need_repack = repack_worthwhile(tdb); - } - - SAFE_FREE(tdb->tdb1.transaction->blocks); - tdb->tdb1.transaction->num_blocks = 0; - - /* ensure the new data is on disk */ - if (transaction1_sync(tdb, 0, tdb->file->map_size) == -1) { - return -1; - } - - /* - TODO: maybe write to some dummy hdr field, or write to magic - offset without mmap, before the last sync, instead of the - utime() call - */ - - /* on some systems (like Linux 2.6.x) changes via mmap/msync - don't change the mtime of the file, this means the file may - not be backed up (as tdb rounding to block sizes means that - file size changes are quite rare too). The following forces - mtime changes when a transaction completes */ -#if HAVE_UTIME - utime(tdb->name, NULL); -#endif - - /* use a transaction cancel to free memory and remove the - transaction locks */ - _tdb1_transaction_cancel(tdb); - - if (need_repack) { - if (tdb_repack(tdb) != 0) - return -1; - } - - return 0; -} - - -/* - recover from an aborted transaction. Must be called with exclusive - database write access already established (including the open - lock to prevent new processes attaching) -*/ -int tdb1_transaction_recover(struct tdb_context *tdb) -{ - tdb1_off_t recovery_head, recovery_eof; - unsigned char *data, *p; - uint32_t zero = 0; - struct tdb1_record rec; - - /* find the recovery area */ - if (tdb1_ofs_read(tdb, TDB1_RECOVERY_HEAD, &recovery_head) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_recover:" - " failed to read recovery head"); - return -1; - } - - if (recovery_head == 0) { - /* we have never allocated a recovery record */ - return 0; - } - - /* read the recovery record */ - if (tdb->tdb1.io->tdb1_read(tdb, recovery_head, &rec, - sizeof(rec), TDB1_DOCONV()) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_recover:" - " failed to read recovery record"); - return -1; - } - - if (rec.magic != TDB1_RECOVERY_MAGIC) { - /* there is no valid recovery data */ - return 0; - } - - if (tdb->flags & TDB_RDONLY) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb1_transaction_recover:" - " attempt to recover read only" - " database"); - return -1; - } - - recovery_eof = rec.key_len; - - data = (unsigned char *)malloc(rec.data_len); - if (data == NULL) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb1_transaction_recover:" - " failed to allocate recovery data"); - return -1; - } - - /* read the full recovery data */ - if (tdb->tdb1.io->tdb1_read(tdb, recovery_head + sizeof(rec), data, - rec.data_len, 0) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_recover:" - " failed to read recovery data"); - return -1; - } - - /* recover the file data */ - p = data; - while (p+8 < data + rec.data_len) { - uint32_t ofs, len; - if (TDB1_DOCONV()) { - tdb1_convert(p, 8); - } - memcpy(&ofs, p, 4); - memcpy(&len, p+4, 4); - - if (tdb->tdb1.io->tdb1_write(tdb, ofs, p+8, len) == -1) { - free(data); - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_recover: failed to recover" - " %d bytes at offset %d", len, ofs); - return -1; - } - p += 8 + len; - } - - free(data); - - if (transaction1_sync(tdb, 0, tdb->file->map_size) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_recover: failed to sync recovery"); - return -1; - } - - /* if the recovery area is after the recovered eof then remove it */ - if (recovery_eof <= recovery_head) { - if (tdb1_ofs_write(tdb, TDB1_RECOVERY_HEAD, &zero) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_recover: failed to remove" - " recovery head"); - return -1; - } - } - - /* remove the recovery magic */ - if (tdb1_ofs_write(tdb, recovery_head + offsetof(struct tdb1_record, magic), - &zero) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_recover: failed to remove" - " recovery magic"); - return -1; - } - - if (transaction1_sync(tdb, 0, recovery_eof) == -1) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_transaction_recover:" - " failed to sync2 recovery"); - return -1; - } - - tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING, - "tdb1_transaction_recover: recovered %d byte database", - recovery_eof); - - /* all done */ - return 0; -} - -/* Any I/O failures we say "needs recovery". */ -tdb_bool_err tdb1_needs_recovery(struct tdb_context *tdb) -{ - tdb1_off_t recovery_head; - struct tdb1_record rec; - - /* find the recovery area */ - if (tdb1_ofs_read(tdb, TDB1_RECOVERY_HEAD, &recovery_head) == -1) { - return TDB_ERR_TO_OFF(tdb->last_error); - } - - if (recovery_head == 0) { - /* we have never allocated a recovery record */ - return false; - } - - /* read the recovery record */ - if (tdb->tdb1.io->tdb1_read(tdb, recovery_head, &rec, - sizeof(rec), TDB1_DOCONV()) == -1) { - return TDB_ERR_TO_OFF(tdb->last_error); - } - - return (rec.magic == TDB1_RECOVERY_MAGIC); -} diff --git a/ccan/tdb2/tdb1_traverse.c b/ccan/tdb2/tdb1_traverse.c deleted file mode 100644 index d9d3649f..00000000 --- a/ccan/tdb2/tdb1_traverse.c +++ /dev/null @@ -1,373 +0,0 @@ - /* - Unix SMB/CIFS implementation. - - trivial database library - - Copyright (C) Andrew Tridgell 1999-2005 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000-2003 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#include "tdb1_private.h" - -#define TDB1_NEXT_LOCK_ERR ((tdb1_off_t)-1) - -static TDB_DATA tdb1_null; - -/* Uses traverse lock: 0 = finish, TDB1_NEXT_LOCK_ERR = error, - other = record offset */ -static tdb1_off_t tdb1_next_lock(struct tdb_context *tdb, struct tdb1_traverse_lock *tlock, - struct tdb1_record *rec) -{ - int want_next = (tlock->off != 0); - - /* Lock each chain from the start one. */ - for (; tlock->hash < tdb->tdb1.header.hash_size; tlock->hash++) { - if (!tlock->off && tlock->hash != 0) { - /* this is an optimisation for the common case where - the hash chain is empty, which is particularly - common for the use of tdb with ldb, where large - hashes are used. In that case we spend most of our - time in tdb1_brlock(), locking empty hash chains. - - To avoid this, we do an unlocked pre-check to see - if the hash chain is empty before starting to look - inside it. If it is empty then we can avoid that - hash chain. If it isn't empty then we can't believe - the value we get back, as we read it without a - lock, so instead we get the lock and re-fetch the - value below. - - Notice that not doing this optimisation on the - first hash chain is critical. We must guarantee - that we have done at least one fcntl lock at the - start of a search to guarantee that memory is - coherent on SMP systems. If records are added by - others during the search then thats OK, and we - could possibly miss those with this trick, but we - could miss them anyway without this trick, so the - semantics don't change. - - With a non-indexed ldb search this trick gains us a - factor of around 80 in speed on a linux 2.6.x - system (testing using ldbtest). - */ - tdb->tdb1.io->next_hash_chain(tdb, &tlock->hash); - if (tlock->hash == tdb->tdb1.header.hash_size) { - continue; - } - } - - if (tdb1_lock(tdb, tlock->hash, tlock->lock_rw) == -1) - return TDB1_NEXT_LOCK_ERR; - - /* No previous record? Start at top of chain. */ - if (!tlock->off) { - if (tdb1_ofs_read(tdb, TDB1_HASH_TOP(tlock->hash), - &tlock->off) == -1) - goto fail; - } else { - /* Otherwise unlock the previous record. */ - if (tdb1_unlock_record(tdb, tlock->off) != 0) - goto fail; - } - - if (want_next) { - /* We have offset of old record: grab next */ - if (tdb1_rec_read(tdb, tlock->off, rec) == -1) - goto fail; - tlock->off = rec->next; - } - - /* Iterate through chain */ - while( tlock->off) { - tdb1_off_t current; - if (tdb1_rec_read(tdb, tlock->off, rec) == -1) - goto fail; - - /* Detect infinite loops. From "Shlomi Yaakobovich" . */ - if (tlock->off == rec->next) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, - TDB_LOG_ERROR, - "tdb1_next_lock:" - " loop detected."); - goto fail; - } - - if (!TDB1_DEAD(rec)) { - /* Woohoo: we found one! */ - if (tdb1_lock_record(tdb, tlock->off) != 0) - goto fail; - return tlock->off; - } - - /* Try to clean dead ones from old traverses */ - current = tlock->off; - tlock->off = rec->next; - if (!((tdb->flags & TDB_RDONLY) || tdb->tdb1.traverse_read) && - tdb1_do_delete(tdb, current, rec) != 0) - goto fail; - } - tdb1_unlock(tdb, tlock->hash, tlock->lock_rw); - want_next = 0; - } - /* We finished iteration without finding anything */ - tdb->last_error = TDB_SUCCESS; - return 0; - - fail: - tlock->off = 0; - if (tdb1_unlock(tdb, tlock->hash, tlock->lock_rw) != 0) - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_next_lock: On error unlock failed!"); - return TDB1_NEXT_LOCK_ERR; -} - -/* traverse the entire database - calling fn(tdb, key, data) on each element. - return -1 on error or the record count traversed - if fn is NULL then it is not called - a non-zero return value from fn() indicates that the traversal should stop - */ -static int tdb1_traverse_internal(struct tdb_context *tdb, - int (*fn)(struct tdb_context *, - TDB_DATA, TDB_DATA, void *), - void *private_data, - struct tdb1_traverse_lock *tl) -{ - TDB_DATA key, dbuf; - struct tdb1_record rec; - int ret = 0, count = 0; - tdb1_off_t off; - - /* This was in the initializaton, above, but the IRIX compiler - * did not like it. crh - */ - tl->next = tdb->tdb1.travlocks.next; - - /* fcntl locks don't stack: beware traverse inside traverse */ - tdb->tdb1.travlocks.next = tl; - - /* tdb1_next_lock places locks on the record returned, and its chain */ - while ((off = tdb1_next_lock(tdb, tl, &rec)) != 0) { - if (off == TDB1_NEXT_LOCK_ERR) { - ret = -1; - goto out; - } - count++; - /* now read the full record */ - key.dptr = tdb1_alloc_read(tdb, tl->off + sizeof(rec), - rec.key_len + rec.data_len); - if (!key.dptr) { - ret = -1; - if (tdb1_unlock(tdb, tl->hash, tl->lock_rw) != 0) - goto out; - if (tdb1_unlock_record(tdb, tl->off) != 0) - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_traverse: key.dptr == NULL and" - " unlock_record failed!"); - goto out; - } - key.dsize = rec.key_len; - dbuf.dptr = key.dptr + rec.key_len; - dbuf.dsize = rec.data_len; - - /* Drop chain lock, call out */ - if (tdb1_unlock(tdb, tl->hash, tl->lock_rw) != 0) { - ret = -1; - SAFE_FREE(key.dptr); - goto out; - } - if (fn && fn(tdb, key, dbuf, private_data)) { - /* They want us to terminate traversal */ - if (tdb1_unlock_record(tdb, tl->off) != 0) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_traverse:" - " unlock_record failed!"); - ret = -1; - } - SAFE_FREE(key.dptr); - goto out; - } - SAFE_FREE(key.dptr); - } -out: - tdb->tdb1.travlocks.next = tl->next; - if (ret < 0) - return -1; - else - return count; -} - - -/* - a read style traverse - only if db read only -*/ -static int tdb1_traverse_read(struct tdb_context *tdb, - int (*fn)(struct tdb_context *, - TDB_DATA, TDB_DATA, void *), - void *private_data) -{ - struct tdb1_traverse_lock tl = { NULL, 0, 0, F_RDLCK }; - int ret; - - /* we need to get a read lock on the transaction lock here to - cope with the lock ordering semantics of solaris10 */ - if (tdb1_transaction_lock(tdb, F_RDLCK, TDB_LOCK_WAIT)) { - return -1; - } - - tdb->tdb1.traverse_read++; - ret = tdb1_traverse_internal(tdb, fn, private_data, &tl); - tdb->tdb1.traverse_read--; - - tdb1_transaction_unlock(tdb, F_RDLCK); - - return ret; -} - -/* - a write style traverse - needs to get the transaction lock to - prevent deadlocks - - WARNING: The data buffer given to the callback fn does NOT meet the - alignment restrictions malloc gives you. -*/ -int tdb1_traverse(struct tdb_context *tdb, - int (*fn)(struct tdb_context *, TDB_DATA, TDB_DATA, void *), - void *private_data) -{ - struct tdb1_traverse_lock tl = { NULL, 0, 0, F_WRLCK }; - int ret; - - /* If we're read-only, we don't have to write-lock whole db. */ - if (tdb->flags & TDB_RDONLY) { - return tdb1_traverse_read(tdb, fn, private_data); - } - - if (tdb1_transaction_lock(tdb, F_WRLCK, TDB_LOCK_WAIT)) { - return -1; - } - - tdb->tdb1.traverse_write++; - ret = tdb1_traverse_internal(tdb, fn, private_data, &tl); - tdb->tdb1.traverse_write--; - - tdb1_transaction_unlock(tdb, F_WRLCK); - - return ret; -} - - -/* find the first entry in the database and return its key */ -TDB_DATA tdb1_firstkey(struct tdb_context *tdb) -{ - TDB_DATA key; - struct tdb1_record rec; - tdb1_off_t off; - - /* release any old lock */ - if (tdb1_unlock_record(tdb, tdb->tdb1.travlocks.off) != 0) - return tdb1_null; - tdb->tdb1.travlocks.off = tdb->tdb1.travlocks.hash = 0; - tdb->tdb1.travlocks.lock_rw = F_RDLCK; - - /* Grab first record: locks chain and returned record. */ - off = tdb1_next_lock(tdb, &tdb->tdb1.travlocks, &rec); - if (off == 0 || off == TDB1_NEXT_LOCK_ERR) { - return tdb1_null; - } - /* now read the key */ - key.dsize = rec.key_len; - key.dptr =tdb1_alloc_read(tdb,tdb->tdb1.travlocks.off+sizeof(rec),key.dsize); - - /* Unlock the hash chain of the record we just read. */ - if (tdb1_unlock(tdb, tdb->tdb1.travlocks.hash, tdb->tdb1.travlocks.lock_rw) != 0) - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_firstkey:" - " error occurred while tdb1_unlocking!"); - return key; -} - -/* find the next entry in the database, returning its key */ -TDB_DATA tdb1_nextkey(struct tdb_context *tdb, TDB_DATA oldkey) -{ - uint32_t oldhash; - TDB_DATA key = tdb1_null; - struct tdb1_record rec; - unsigned char *k = NULL; - tdb1_off_t off; - - /* Is locked key the old key? If so, traverse will be reliable. */ - if (tdb->tdb1.travlocks.off) { - if (tdb1_lock(tdb,tdb->tdb1.travlocks.hash,tdb->tdb1.travlocks.lock_rw)) - return tdb1_null; - if (tdb1_rec_read(tdb, tdb->tdb1.travlocks.off, &rec) == -1 - || !(k = tdb1_alloc_read(tdb,tdb->tdb1.travlocks.off+sizeof(rec), - rec.key_len)) - || memcmp(k, oldkey.dptr, oldkey.dsize) != 0) { - /* No, it wasn't: unlock it and start from scratch */ - if (tdb1_unlock_record(tdb, tdb->tdb1.travlocks.off) != 0) { - SAFE_FREE(k); - return tdb1_null; - } - if (tdb1_unlock(tdb, tdb->tdb1.travlocks.hash, tdb->tdb1.travlocks.lock_rw) != 0) { - SAFE_FREE(k); - return tdb1_null; - } - tdb->tdb1.travlocks.off = 0; - } - - SAFE_FREE(k); - } - - if (!tdb->tdb1.travlocks.off) { - /* No previous element: do normal find, and lock record */ - tdb->tdb1.travlocks.off = tdb1_find_lock_hash(tdb, oldkey, tdb_hash(tdb, oldkey.dptr, oldkey.dsize), tdb->tdb1.travlocks.lock_rw, &rec); - if (!tdb->tdb1.travlocks.off) { - return tdb1_null; - } - tdb->tdb1.travlocks.hash = TDB1_BUCKET(rec.full_hash); - if (tdb1_lock_record(tdb, tdb->tdb1.travlocks.off) != 0) { - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_nextkey: lock_record failed (%s)!", - strerror(errno)); - return tdb1_null; - } - } - oldhash = tdb->tdb1.travlocks.hash; - - /* Grab next record: locks chain and returned record, - unlocks old record */ - off = tdb1_next_lock(tdb, &tdb->tdb1.travlocks, &rec); - if (off != TDB1_NEXT_LOCK_ERR && off != 0) { - key.dsize = rec.key_len; - key.dptr = tdb1_alloc_read(tdb, tdb->tdb1.travlocks.off+sizeof(rec), - key.dsize); - /* Unlock the chain of this new record */ - if (tdb1_unlock(tdb, tdb->tdb1.travlocks.hash, tdb->tdb1.travlocks.lock_rw) != 0) - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_nextkey: WARNING tdb1_unlock failed!"); - } - /* Unlock the chain of old record */ - if (tdb1_unlock(tdb, TDB1_BUCKET(oldhash), tdb->tdb1.travlocks.lock_rw) != 0) - tdb_logerr(tdb, tdb->last_error, TDB_LOG_ERROR, - "tdb1_nextkey: WARNING tdb1_unlock failed!"); - return key; -} diff --git a/ccan/tdb2/tdb2.h b/ccan/tdb2/tdb2.h deleted file mode 100644 index 3fa99b15..00000000 --- a/ccan/tdb2/tdb2.h +++ /dev/null @@ -1,924 +0,0 @@ -#ifndef CCAN_TDB2_H -#define CCAN_TDB2_H - -/* - TDB version 2: trivial database library - - Copyright (C) Andrew Tridgell 1999-2004 - Copyright (C) Rusty Russell 2010-2011 - - ** NOTE! The following LGPL license applies to the tdb - ** library. This does NOT imply that all of Samba is released - ** under the LGPL - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef _SAMBA_BUILD_ -#include "config.h" -#if HAVE_FILE_OFFSET_BITS -#define _FILE_OFFSET_BITS 64 -#endif -/* For mode_t */ -#include -/* For O_* flags. */ -#include -/* For sig_atomic_t. */ -#include -/* For uint64_t */ -#include -/* For bool */ -#include -/* For memcmp */ -#include -#endif -#include -#include -#include - -union tdb_attribute; -struct tdb_context; - -/** - * tdb_open - open a database file - * @name: the file name (can be NULL if flags contains TDB_INTERNAL) - * @tdb_flags: options for this database - * @open_flags: flags argument for tdb's open() call. - * @mode: mode argument for tdb's open() call. - * @attributes: linked list of extra attributes for this tdb. - * - * This call opens (and potentially creates) a database file. - * Multiple processes can have the TDB file open at once. - * - * On failure it will return NULL, and set errno: it may also call - * any log attribute found in @attributes. - * - * See also: - * union tdb_attribute - */ -struct tdb_context *tdb_open(const char *name, int tdb_flags, - int open_flags, mode_t mode, - union tdb_attribute *attributes); - - -/* flags for tdb_open() */ -#define TDB_DEFAULT 0 /* just a readability place holder */ -#define TDB_INTERNAL 2 /* don't store on disk */ -#define TDB_NOLOCK 4 /* don't do any locking */ -#define TDB_NOMMAP 8 /* don't use mmap */ -#define TDB_CONVERT 16 /* convert endian */ -#define TDB_NOSYNC 64 /* don't use synchronous transactions */ -#define TDB_SEQNUM 128 /* maintain a sequence number */ -#define TDB_ALLOW_NESTING 256 /* fake nested transactions */ -#define TDB_RDONLY 512 /* implied by O_RDONLY */ -#define TDB_VERSION1 1024 /* create/open an old style TDB */ -#define TDB_CANT_CHECK 2048 /* has a feature which we don't understand */ - -/** - * tdb1_incompatible_hash - better (Jenkins) hash for tdb1 - * - * This is better than the default hash for tdb1; but older versions of the - * tdb library (prior to version 1.2.6) won't be able to open them. - * - * It only makes sense to specify this (using tdb_attribute_hash) when - * creating (with O_CREAT) an old tdb version using TDB_VERSION1. It's - * equivalent to the TDB_INCOMPATIBLE_HASH flag for tdb1. - */ -uint64_t tdb1_incompatible_hash(const void *, size_t, uint64_t, void *); - -/** - * tdb_close - close and free a tdb. - * @tdb: the tdb context returned from tdb_open() - * - * This always succeeds, in that @tdb is unusable after this call. But if - * some unexpected error occurred while closing, it will return non-zero - * (the only clue as to cause will be via the log attribute). - */ -int tdb_close(struct tdb_context *tdb); - -/** - * struct tdb_data - representation of keys or values. - * @dptr: the data pointer - * @dsize: the size of the data pointed to by dptr. - * - * This is the "blob" representation of keys and data used by TDB. - */ -typedef struct tdb_data { - unsigned char *dptr; - size_t dsize; -} TDB_DATA; - -/** - * enum TDB_ERROR - error returns for TDB - * - * See Also: - * tdb_errorstr() - */ -enum TDB_ERROR { - TDB_SUCCESS = 0, /* No error. */ - TDB_ERR_CORRUPT = -1, /* We read the db, and it was bogus. */ - TDB_ERR_IO = -2, /* We couldn't read/write the db. */ - TDB_ERR_LOCK = -3, /* Locking failed. */ - TDB_ERR_OOM = -4, /* Out of Memory. */ - TDB_ERR_EXISTS = -5, /* The key already exists. */ - TDB_ERR_NOEXIST = -6, /* The key does not exist. */ - TDB_ERR_EINVAL = -7, /* You're using it wrong. */ - TDB_ERR_RDONLY = -8, /* The database is read-only. */ - TDB_ERR_LAST = TDB_ERR_RDONLY -}; - -/** - * tdb_store - store a key/value pair in a tdb. - * @tdb: the tdb context returned from tdb_open() - * @key: the key - * @dbuf: the data to associate with the key. - * @flag: TDB_REPLACE, TDB_INSERT or TDB_MODIFY. - * - * This inserts (or overwrites) a key/value pair in the TDB. If flag - * is TDB_REPLACE, it doesn't matter whether the key exists or not; - * TDB_INSERT means it must not exist (returns TDB_ERR_EXISTS otherwise), - * and TDB_MODIFY means it must exist (returns TDB_ERR_NOEXIST otherwise). - * - * On success, this returns TDB_SUCCESS. - * - * See also: - * tdb_fetch, tdb_transaction_start, tdb_append, tdb_delete. - */ -enum TDB_ERROR tdb_store(struct tdb_context *tdb, - struct tdb_data key, - struct tdb_data dbuf, - int flag); - -/* flags to tdb_store() */ -#define TDB_REPLACE 1 /* A readability place holder */ -#define TDB_INSERT 2 /* Don't overwrite an existing entry */ -#define TDB_MODIFY 3 /* Don't create an existing entry */ - -/** - * tdb_fetch - fetch a value from a tdb. - * @tdb: the tdb context returned from tdb_open() - * @key: the key - * @data: pointer to data. - * - * This looks up a key in the database and sets it in @data. - * - * If it returns TDB_SUCCESS, the key was found: it is your - * responsibility to call free() on @data->dptr. - * - * Otherwise, it returns an error (usually, TDB_ERR_NOEXIST) and @data is - * undefined. - */ -enum TDB_ERROR tdb_fetch(struct tdb_context *tdb, struct tdb_data key, - struct tdb_data *data); - -/** - * tdb_errorstr - map the tdb error onto a constant readable string - * @ecode: the enum TDB_ERROR to map. - * - * This is useful for displaying errors to users. - */ -const char *tdb_errorstr(enum TDB_ERROR ecode); - -/** - * tdb_append - append a value to a key/value pair in a tdb. - * @tdb: the tdb context returned from tdb_open() - * @key: the key - * @dbuf: the data to append. - * - * This is equivalent to fetching a record, reallocating .dptr to add the - * data, and writing it back, only it's much more efficient. If the key - * doesn't exist, it's equivalent to tdb_store (with an additional hint that - * you expect to expand the record in future). - * - * See Also: - * tdb_fetch(), tdb_store() - */ -enum TDB_ERROR tdb_append(struct tdb_context *tdb, - struct tdb_data key, struct tdb_data dbuf); - -/** - * tdb_delete - delete a key from a tdb. - * @tdb: the tdb context returned from tdb_open() - * @key: the key to delete. - * - * Returns TDB_SUCCESS on success, or an error (usually TDB_ERR_NOEXIST). - * - * See Also: - * tdb_fetch(), tdb_store() - */ -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_deq - are struct tdb_data equal? - * @a: one struct tdb_data - * @b: another struct tdb_data - */ -static inline bool tdb_deq(struct tdb_data a, struct tdb_data b) -{ - return a.dsize == b.dsize && memcmp(a.dptr, b.dptr, a.dsize) == 0; -} - -/** - * tdb_mkdata - make a struct tdb_data from const data - * @p: the constant pointer - * @len: the length - * - * As the dptr member of struct tdb_data is not constant, you need to - * cast it. This function keeps thost casts in one place, as well as - * suppressing the warning some compilers give when casting away a - * qualifier (eg. gcc with -Wcast-qual) - */ -static inline struct tdb_data tdb_mkdata(const void *p, size_t len) -{ - struct tdb_data d; - d.dptr = cast_const(void *, p); - d.dsize = len; - return d; -} - -/** - * tdb_transaction_start - start a transaction - * @tdb: the tdb context returned from tdb_open() - * - * This begins a series of atomic operations. Other processes will be able - * to read the tdb, but not alter it (they will block), nor will they see - * any changes until tdb_transaction_commit() is called. - * - * Note that if the TDB_ALLOW_NESTING flag is set, a tdb_transaction_start() - * within a transaction will succeed, but it's not a real transaction: - * (1) An inner transaction which is committed is not actually committed until - * the outer transaction is; if the outer transaction is cancelled, the - * inner ones are discarded. - * (2) tdb_transaction_cancel() marks the outer transaction as having an error, - * so the final tdb_transaction_commit() will fail. - * (3) the outer transaction will see the results of the inner transaction. - * - * See Also: - * tdb_transaction_cancel, tdb_transaction_commit. - */ -enum TDB_ERROR tdb_transaction_start(struct tdb_context *tdb); - -/** - * tdb_transaction_cancel - abandon a transaction - * @tdb: the tdb context returned from tdb_open() - * - * This aborts a transaction, discarding any changes which were made. - * tdb_close() does this implicitly. - */ -void tdb_transaction_cancel(struct tdb_context *tdb); - -/** - * tdb_transaction_commit - commit a transaction - * @tdb: the tdb context returned from tdb_open() - * - * This completes a transaction, writing any changes which were made. - * - * fsync() is used to commit the transaction (unless TDB_NOSYNC is set), - * making it robust against machine crashes, but very slow compared to - * other TDB operations. - * - * A failure can only be caused by unexpected errors (eg. I/O or - * memory); this is no point looping on transaction failure. - * - * See Also: - * tdb_transaction_prepare_commit() - */ -enum TDB_ERROR tdb_transaction_commit(struct tdb_context *tdb); - -/** - * tdb_transaction_prepare_commit - prepare to commit a transaction - * @tdb: the tdb context returned from tdb_open() - * - * This ensures we have the resources to commit a transaction (using - * tdb_transaction_commit): if this succeeds then a transaction will only - * fail if the write() or fsync() calls fail. - * - * If this fails you must still call tdb_transaction_cancel() to cancel - * the transaction. - * - * See Also: - * tdb_transaction_commit() - */ -enum TDB_ERROR tdb_transaction_prepare_commit(struct tdb_context *tdb); - -/** - * tdb_traverse - traverse a TDB - * @tdb: the tdb context returned from tdb_open() - * @fn: the function to call for every key/value pair (or NULL) - * @p: the pointer to hand to @f - * - * This walks the TDB until all they keys have been traversed, or @fn - * returns non-zero. If the traverse function or other processes are - * changing data or adding or deleting keys, the traverse may be - * unreliable: keys may be skipped or (rarely) visited twice. - * - * There is one specific exception: the special case of deleting the - * current key does not undermine the reliability of the traversal. - * - * On success, returns the number of keys iterated. On error returns - * a negative enum TDB_ERROR value. - */ -#define tdb_traverse(tdb, fn, p) \ - tdb_traverse_(tdb, typesafe_cb_preargs(int, void *, (fn), (p), \ - struct tdb_context *, \ - TDB_DATA, TDB_DATA), (p)) - -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 - * @data: 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, data) \ - tdb_parse_record_((tdb), (key), \ - typesafe_cb_preargs(enum TDB_ERROR, void *, \ - (parse), (data), \ - TDB_DATA, TDB_DATA), (data)) - -enum TDB_ERROR tdb_parse_record_(struct tdb_context *tdb, - TDB_DATA key, - enum TDB_ERROR (*parse)(TDB_DATA k, - TDB_DATA d, - void *data), - void *data); - -/** - * tdb_get_seqnum - get a database sequence number - * @tdb: the tdb context returned from tdb_open() - * - * This returns a sequence number: any change to the database from a - * tdb context opened with the TDB_SEQNUM flag will cause that number - * to increment. Note that the incrementing is unreliable (it is done - * without locking), so this is only useful as an optimization. - * - * For example, you may have a regular database backup routine which - * does not operate if the sequence number is unchanged. In the - * unlikely event of a failed increment, it will be backed up next - * time any way. - * - * Returns an enum TDB_ERROR (ie. negative) on error. - */ -int64_t tdb_get_seqnum(struct tdb_context *tdb); - -/** - * tdb_firstkey - get the "first" key in a TDB - * @tdb: the tdb context returned from tdb_open() - * @key: pointer to key. - * - * This returns an arbitrary key in the database; with tdb_nextkey() it allows - * open-coded traversal of the database, though it is slightly less efficient - * than tdb_traverse. - * - * It is your responsibility to free @key->dptr on success. - * - * Returns TDB_ERR_NOEXIST if the database is empty. - */ -enum TDB_ERROR tdb_firstkey(struct tdb_context *tdb, struct tdb_data *key); - -/** - * tdb_nextkey - get the "next" key in a TDB - * @tdb: the tdb context returned from tdb_open() - * @key: a key returned by tdb_firstkey() or tdb_nextkey(). - * - * This returns another key in the database; it will free @key.dptr for - * your convenience. - * - * Returns TDB_ERR_NOEXIST if there are no more keys. - */ -enum TDB_ERROR tdb_nextkey(struct tdb_context *tdb, struct tdb_data *key); - -/** - * tdb_chainlock - lock a record in the TDB - * @tdb: the tdb context returned from tdb_open() - * @key: the key to lock. - * - * This prevents any access occurring to a group of keys including @key, - * even if @key does not exist. This allows primitive atomic updates of - * records without using transactions. - * - * You cannot begin a transaction while holding a tdb_chainlock(), nor can - * you do any operations on any other keys in the database. This also means - * that you cannot hold more than one tdb_chainlock() at a time. - * - * See Also: - * tdb_chainunlock() - */ -enum TDB_ERROR tdb_chainlock(struct tdb_context *tdb, TDB_DATA key); - -/** - * tdb_chainunlock - unlock a record in the TDB - * @tdb: the tdb context returned from tdb_open() - * @key: the key to unlock. - * - * The key must have previously been locked by tdb_chainlock(). - */ -void tdb_chainunlock(struct tdb_context *tdb, TDB_DATA key); - -/** - * tdb_chainlock_read - lock a record in the TDB, for reading - * @tdb: the tdb context returned from tdb_open() - * @key: the key to lock. - * - * This prevents any changes from occurring to a group of keys including @key, - * even if @key does not exist. This allows primitive atomic updates of - * records without using transactions. - * - * You cannot begin a transaction while holding a tdb_chainlock_read(), nor can - * you do any operations on any other keys in the database. This also means - * that you cannot hold more than one tdb_chainlock()/read() at a time. - * - * See Also: - * tdb_chainlock() - */ -enum TDB_ERROR tdb_chainlock_read(struct tdb_context *tdb, TDB_DATA key); - -/** - * tdb_chainunlock_read - unlock a record in the TDB for reading - * @tdb: the tdb context returned from tdb_open() - * @key: the key to unlock. - * - * The key must have previously been locked by tdb_chainlock_read(). - */ -void tdb_chainunlock_read(struct tdb_context *tdb, TDB_DATA key); - -/** - * tdb_lockall - lock the entire TDB - * @tdb: the tdb context returned from tdb_open() - * - * You cannot hold a tdb_chainlock while calling this. It nests, so you - * must call tdb_unlockall as many times as you call tdb_lockall. - */ -enum TDB_ERROR tdb_lockall(struct tdb_context *tdb); - -/** - * tdb_unlockall - unlock the entire TDB - * @tdb: the tdb context returned from tdb_open() - */ -void tdb_unlockall(struct tdb_context *tdb); - -/** - * tdb_lockall_read - lock the entire TDB for reading - * @tdb: the tdb context returned from tdb_open() - * - * This prevents others writing to the database, eg. tdb_delete, tdb_store, - * tdb_append, but not tdb_fetch. - * - * You cannot hold a tdb_chainlock while calling this. It nests, so you - * must call tdb_unlockall_read as many times as you call tdb_lockall_read. - */ -enum TDB_ERROR tdb_lockall_read(struct tdb_context *tdb); - -/** - * tdb_unlockall_read - unlock the entire TDB for reading - * @tdb: the tdb context returned from tdb_open() - */ -void tdb_unlockall_read(struct tdb_context *tdb); - -/** - * 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_repack - repack the database - * @tdb: the tdb context returned from tdb_open() - * - * This repacks the database; if it is suffering from a great deal of - * fragmentation this might help. However, it can take twice the - * memory of the existing TDB. - */ -enum TDB_ERROR tdb_repack(struct tdb_context *tdb); - -/** - * tdb_check - check a TDB for consistency - * @tdb: the tdb context returned from tdb_open() - * @check: function to check each key/data pair (or NULL) - * @data: argument for @check, must match type. - * - * This performs a consistency check of the open database, optionally calling - * a check() function on each record so you can do your own data consistency - * checks as well. If check() returns an error, that is returned from - * tdb_check(). - * - * Note that the TDB uses a feature which we don't understand which - * indicates we can't run tdb_check(), this will log a warning to that - * effect and return TDB_SUCCESS. You can detect this condition by - * looking for TDB_CANT_CHECK in tdb_get_flags(). - * - * Returns TDB_SUCCESS or an error. - */ -#define tdb_check(tdb, check, data) \ - tdb_check_((tdb), typesafe_cb_preargs(enum TDB_ERROR, void *, \ - (check), (data), \ - struct tdb_data, \ - struct tdb_data), \ - (data)) - -enum TDB_ERROR tdb_check_(struct tdb_context *tdb, - enum TDB_ERROR (*check)(struct tdb_data k, - struct tdb_data d, - void *data), - void *data); - -/** - * tdb_error - get the last error (not threadsafe) - * @tdb: the tdb context returned from tdb_open() - * - * Returns the last error returned by a TDB function. - * - * This makes porting from TDB1 easier, but note that the last error is not - * reliable in threaded programs. - */ -enum TDB_ERROR tdb_error(struct tdb_context *tdb); - -/** - * enum tdb_summary_flags - flags for tdb_summary. - */ -enum tdb_summary_flags { - TDB_SUMMARY_HISTOGRAMS = 1 /* Draw graphs in the summary. */ -}; - -/** - * tdb_summary - return a string describing the TDB state - * @tdb: the tdb context returned from tdb_open() - * @flags: flags to control the summary output. - * @summary: pointer to string to allocate. - * - * This returns a developer-readable string describing the overall - * state of the tdb, such as the percentage used and sizes of records. - * It is designed to provide information about the tdb at a glance - * without displaying any keys or data in the database. - * - * On success, sets @summary to point to a malloc()'ed nul-terminated - * multi-line string. It is your responsibility to free() it. - */ -enum TDB_ERROR tdb_summary(struct tdb_context *tdb, - enum tdb_summary_flags flags, - char **summary); - - -/** - * tdb_get_flags - return the flags for a tdb - * @tdb: the tdb context returned from tdb_open() - * - * This returns the flags on the current tdb. Some of these are caused by - * the flags argument to tdb_open(), others (such as TDB_CONVERT) are - * intuited. - */ -unsigned int tdb_get_flags(struct tdb_context *tdb); - -/** - * tdb_add_flag - set a flag for a tdb - * @tdb: the tdb context returned from tdb_open() - * @flag: one of TDB_NOLOCK, TDB_NOMMAP, TDB_NOSYNC or TDB_ALLOW_NESTING. - * - * You can use this to set a flag on the TDB. You cannot set these flags - * on a TDB_INTERNAL tdb. - */ -void tdb_add_flag(struct tdb_context *tdb, unsigned flag); - -/** - * tdb_remove_flag - unset a flag for a tdb - * @tdb: the tdb context returned from tdb_open() - * @flag: one of TDB_NOLOCK, TDB_NOMMAP, TDB_NOSYNC or TDB_ALLOW_NESTING. - * - * You can use this to clear a flag on the TDB. You cannot clear flags - * on a TDB_INTERNAL tdb. - */ -void tdb_remove_flag(struct tdb_context *tdb, unsigned flag); - -/** - * enum tdb_attribute_type - descriminator for union tdb_attribute. - */ -enum tdb_attribute_type { - TDB_ATTRIBUTE_LOG = 0, - TDB_ATTRIBUTE_HASH = 1, - TDB_ATTRIBUTE_SEED = 2, - TDB_ATTRIBUTE_STATS = 3, - TDB_ATTRIBUTE_OPENHOOK = 4, - TDB_ATTRIBUTE_FLOCK = 5, - TDB_ATTRIBUTE_TDB1_HASHSIZE = 128, - TDB_ATTRIBUTE_TDB1_MAX_DEAD = 129, -}; - -/** - * tdb_get_attribute - get an attribute for an existing tdb - * @tdb: the tdb context returned from tdb_open() - * @attr: the union tdb_attribute to set. - * - * This gets an attribute from a TDB which has previously been set (or - * may return the default values). Set @attr.base.attr to the - * attribute type you want get. - */ -enum TDB_ERROR tdb_get_attribute(struct tdb_context *tdb, - union tdb_attribute *attr); - -/** - * tdb_set_attribute - set an attribute for an existing tdb - * @tdb: the tdb context returned from tdb_open() - * @attr: the union tdb_attribute to set. - * - * This sets an attribute on a TDB, overriding any previous attribute - * of the same type. It returns TDB_ERR_EINVAL if the attribute is - * unknown or invalid. - * - * Note that TDB_ATTRIBUTE_HASH, TDB_ATTRIBUTE_SEED, - * TDB_ATTRIBUTE_OPENHOOK and TDB_ATTRIBUTE_TDB1_HASHSIZE cannot - * currently be set after tdb_open. - */ -enum TDB_ERROR tdb_set_attribute(struct tdb_context *tdb, - const union tdb_attribute *attr); - -/** - * tdb_unset_attribute - reset an attribute for an existing tdb - * @tdb: the tdb context returned from tdb_open() - * @type: the attribute type to unset. - * - * This unsets an attribute on a TDB, returning it to the defaults - * (where applicable). - * - * Note that it only makes sense for TDB_ATTRIBUTE_LOG and TDB_ATTRIBUTE_FLOCK - * to be unset. - */ -void tdb_unset_attribute(struct tdb_context *tdb, - enum tdb_attribute_type type); - -/** - * tdb_name - get the name of a tdb - * @tdb: the tdb context returned from tdb_open() - * - * This returns a copy of the name string, made at tdb_open() time. If that - * argument was NULL (possible for a TDB_INTERNAL db) this will return NULL. - * - * This is mostly useful for logging. - */ -const char *tdb_name(const struct tdb_context *tdb); - -/** - * tdb_fd - get the file descriptor of a tdb - * @tdb: the tdb context returned from tdb_open() - * - * This returns the file descriptor for the underlying database file, or -1 - * for TDB_INTERNAL. - */ -int tdb_fd(const struct tdb_context *tdb); - -/** - * tdb_foreach - iterate through every open TDB. - * @fn: the function to call for every TDB - * @p: the pointer to hand to @fn - * - * TDB internally keeps track of all open TDBs; this function allows you to - * iterate through them. If @fn returns non-zero, traversal stops. - */ -#define tdb_foreach(fn, p) \ - tdb_foreach_(typesafe_cb_preargs(int, void *, (fn), (p), \ - struct tdb_context *), (p)) - -void tdb_foreach_(int (*fn)(struct tdb_context *, void *), void *p); - -/** - * struct tdb_attribute_base - common fields for all tdb attributes. - */ -struct tdb_attribute_base { - enum tdb_attribute_type attr; - union tdb_attribute *next; -}; - -/** - * enum tdb_log_level - log levels for tdb_attribute_log - * @TDB_LOG_ERROR: used to log unrecoverable errors such as I/O errors - * or internal consistency failures. - * @TDB_LOG_USE_ERROR: used to log usage errors such as invalid parameters - * or writing to a read-only database. - * @TDB_LOG_WARNING: used for informational messages on issues which - * are unusual but handled by TDB internally, such - * as a failure to mmap or failure to open /dev/urandom. - */ -enum tdb_log_level { - TDB_LOG_ERROR, - TDB_LOG_USE_ERROR, - TDB_LOG_WARNING -}; - -/** - * struct tdb_attribute_log - log function attribute - * - * This attribute provides a hook for you to log errors. - */ -struct tdb_attribute_log { - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_LOG */ - void (*fn)(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, - void *data); - void *data; -}; - -/** - * struct tdb_attribute_hash - hash function attribute - * - * This attribute allows you to provide an alternative hash function. - * This hash function will be handed keys from the database; it will also - * be handed the 8-byte TDB_HASH_MAGIC value for checking the header (the - * tdb_open() will fail if the hash value doesn't match the header). - * - * Note that if your hash function gives different results on - * different machine endians, your tdb will no longer work across - * different architectures! - */ -struct tdb_attribute_hash { - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_HASH */ - uint64_t (*fn)(const void *key, size_t len, uint64_t seed, - void *data); - void *data; -}; - -/** - * struct tdb_attribute_seed - hash function seed attribute - * - * The hash function seed is normally taken from /dev/urandom (or equivalent) - * but can be set manually here. This is mainly for testing purposes. - */ -struct tdb_attribute_seed { - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_SEED */ - uint64_t seed; -}; - -/** - * struct tdb_attribute_stats - tdb operational statistics - * - * This attribute records statistics of various low-level TDB operations. - * This can be used to assist performance evaluation. This is only - * useful for tdb_get_attribute(). - * - * New fields will be added at the end, hence the "size" argument which - * indicates how large your structure is: it must be filled in before - * calling tdb_get_attribute(), which will overwrite it with the size - * tdb knows about. - */ -struct tdb_attribute_stats { - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_STATS */ - size_t size; /* = sizeof(struct tdb_attribute_stats) */ - uint64_t allocs; - uint64_t alloc_subhash; - uint64_t alloc_chain; - uint64_t alloc_bucket_exact; - uint64_t alloc_bucket_max; - uint64_t alloc_leftover; - uint64_t alloc_coalesce_tried; - uint64_t alloc_coalesce_iterate_clash; - uint64_t alloc_coalesce_lockfail; - uint64_t alloc_coalesce_race; - uint64_t alloc_coalesce_succeeded; - uint64_t alloc_coalesce_num_merged; - uint64_t compares; - uint64_t compare_wrong_bucket; - uint64_t compare_wrong_offsetbits; - uint64_t compare_wrong_keylen; - uint64_t compare_wrong_rechash; - uint64_t compare_wrong_keycmp; - uint64_t transactions; - uint64_t transaction_cancel; - uint64_t transaction_nest; - uint64_t transaction_expand_file; - uint64_t transaction_read_direct; - uint64_t transaction_read_direct_fail; - uint64_t transaction_write_direct; - uint64_t transaction_write_direct_fail; - uint64_t expands; - uint64_t frees; - uint64_t locks; - uint64_t lock_lowlevel; - uint64_t lock_nonblock; - uint64_t lock_nonblock_fail; -}; - -/** - * struct tdb_attribute_openhook - tdb special effects hook for open - * - * This attribute contains a function to call once we have the OPEN_LOCK - * for the tdb, but before we've examined its contents. If this succeeds, - * the tdb will be populated if it's then zero-length. - * - * This is a hack to allow support for TDB1-style TDB_CLEAR_IF_FIRST - * behaviour. - */ -struct tdb_attribute_openhook { - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_OPENHOOK */ - enum TDB_ERROR (*fn)(int fd, void *data); - void *data; -}; - -/** - * struct tdb_attribute_flock - tdb special effects hook for file locking - * - * This attribute contains function to call to place locks on a file; it can - * be used to support non-blocking operations or lock proxying. - * - * They should return 0 on success, -1 on failure and set errno. - * - * An error will be logged on error if errno is neither EAGAIN nor EINTR - * (normally it would only return EAGAIN if waitflag is false, and - * loop internally on EINTR). - */ -struct tdb_attribute_flock { - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_FLOCK */ - int (*lock)(int fd,int rw, off_t off, off_t len, bool waitflag, void *); - int (*unlock)(int fd, int rw, off_t off, off_t len, void *); - void *data; -}; - -/** - * struct tdb_attribute_tdb1_hashsize - tdb1 hashsize - * - * This attribute allows setting the TDB1 hashsize; it only makes sense with - * O_CREAT and TDB_VERSION1. - * - * Hashsize should generally be a prime, such as 10007. - */ -struct tdb_attribute_tdb1_hashsize { - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_TDB1_HASHSIZE */ - unsigned int hsize; -}; - -/** - * struct tdb_attribute_tdb1_max_dead - tdb1 number of maximum dead records. - * - * TDB1 has a method to speed up its slow free list: it lets a certain - * number of "dead" records build up before freeing them. This is - * particularly useful for volatile TDBs; setting it to 5 is - * equivalent to tdb1's TDB_VOLATILE flag. - */ -struct tdb_attribute_tdb1_max_dead { - struct tdb_attribute_base base; /* .attr = TDB_ATTRIBUTE_TDB1_MAX_DEAD */ - unsigned int max_dead; -}; - -/** - * union tdb_attribute - tdb attributes. - * - * This represents all the known attributes. - * - * See also: - * struct tdb_attribute_log, struct tdb_attribute_hash, - * struct tdb_attribute_seed, struct tdb_attribute_stats, - * struct tdb_attribute_openhook, struct tdb_attribute_flock. - */ -union tdb_attribute { - struct tdb_attribute_base base; - struct tdb_attribute_log log; - struct tdb_attribute_hash hash; - struct tdb_attribute_seed seed; - struct tdb_attribute_stats stats; - struct tdb_attribute_openhook openhook; - struct tdb_attribute_flock flock; - struct tdb_attribute_tdb1_hashsize tdb1_hashsize; - struct tdb_attribute_tdb1_max_dead tdb1_max_dead; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* tdb2.h */ diff --git a/ccan/tdb2/test/api-12-store.c b/ccan/tdb2/test/api-12-store.c deleted file mode 100644 index ccec53e0..00000000 --- a/ccan/tdb2/test/api-12-store.c +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "logging.h" - -/* We use the same seed which we saw a failure on. */ -static uint64_t fixedhash(const void *key, size_t len, uint64_t seed, void *p) -{ - return hash64_stable((const unsigned char *)key, len, - *(uint64_t *)p); -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - uint64_t seed = 16014841315512641303ULL; - union tdb_attribute fixed_hattr - = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, - .fn = fixedhash, - .data = &seed } }; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT }; - struct tdb_data key = { (unsigned char *)&j, sizeof(j) }; - struct tdb_data data = { (unsigned char *)&j, sizeof(j) }; - - fixed_hattr.base.next = &tap_log_attr; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * (1 + 500 * 3) + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-12-store.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &fixed_hattr); - ok1(tdb); - if (!tdb) - continue; - - /* We seemed to lose some keys. - * Insert and check they're in there! */ - for (j = 0; j < 500; j++) { - struct tdb_data d = { NULL, 0 }; /* Bogus GCC warning */ - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == 0); - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(tdb_deq(d, data)); - free(d.dptr); - } - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-13-delete.c b/ccan/tdb2/test/api-13-delete.c deleted file mode 100644 index 0287a6ab..00000000 --- a/ccan/tdb2/test/api-13-delete.c +++ /dev/null @@ -1,210 +0,0 @@ -#include // For TDB_TOPLEVEL_HASH_BITS -#include -#include -#include -#include -#include -#include -#include "logging.h" - -/* We rig the hash so adjacent-numbered records always clash. */ -static uint64_t clash(const void *key, size_t len, uint64_t seed, void *priv) -{ - return ((uint64_t)*(const unsigned int *)key) - << (64 - TDB_TOPLEVEL_HASH_BITS - 1); -} - -/* We use the same seed which we saw a failure on. */ -static uint64_t fixedhash(const void *key, size_t len, uint64_t seed, void *p) -{ - return hash64_stable((const unsigned char *)key, len, - *(uint64_t *)p); -} - -static bool store_records(struct tdb_context *tdb) -{ - int i; - struct tdb_data key = { (unsigned char *)&i, sizeof(i) }; - struct tdb_data d, data = { (unsigned char *)&i, sizeof(i) }; - - for (i = 0; i < 1000; i++) { - if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) - return false; - tdb_fetch(tdb, key, &d); - if (!tdb_deq(d, data)) - return false; - free(d.dptr); - } - return true; -} - -static void test_val(struct tdb_context *tdb, uint64_t val) -{ - uint64_t v; - struct tdb_data key = { (unsigned char *)&v, sizeof(v) }; - struct tdb_data d, data = { (unsigned char *)&v, sizeof(v) }; - - /* Insert an entry, then delete it. */ - v = val; - /* Delete should fail. */ - ok1(tdb_delete(tdb, key) == TDB_ERR_NOEXIST); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Insert should succeed. */ - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Delete should succeed. */ - ok1(tdb_delete(tdb, key) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Re-add it, then add collision. */ - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - v = val + 1; - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Can find both? */ - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == data.dsize); - free(d.dptr); - v = val; - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == data.dsize); - free(d.dptr); - - /* Delete second one. */ - v = val + 1; - ok1(tdb_delete(tdb, key) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Re-add */ - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Now, try deleting first one. */ - v = val; - ok1(tdb_delete(tdb, key) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Can still find second? */ - v = val + 1; - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == data.dsize); - free(d.dptr); - - /* Now, this will be ideally placed. */ - v = val + 2; - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* This will collide with both. */ - v = val; - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - - /* We can still find them all, right? */ - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == data.dsize); - free(d.dptr); - v = val + 1; - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == data.dsize); - free(d.dptr); - v = val + 2; - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == data.dsize); - free(d.dptr); - - /* And if we delete val + 1, that val + 2 should not move! */ - v = val + 1; - ok1(tdb_delete(tdb, key) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - v = val; - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == data.dsize); - free(d.dptr); - v = val + 2; - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == data.dsize); - free(d.dptr); - - /* Delete those two, so we are empty. */ - ok1(tdb_delete(tdb, key) == 0); - v = val; - ok1(tdb_delete(tdb, key) == 0); - - ok1(tdb_check(tdb, NULL, NULL) == 0); -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - uint64_t seed = 16014841315512641303ULL; - union tdb_attribute clash_hattr - = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, - .fn = clash } }; - union tdb_attribute fixed_hattr - = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, - .fn = fixedhash, - .data = &seed } }; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - /* These two values gave trouble before. */ - int vals[] = { 755, 837 }; - - clash_hattr.base.next = &tap_log_attr; - fixed_hattr.base.next = &tap_log_attr; - - plan_tests(sizeof(flags) / sizeof(flags[0]) - * (39 * 3 + 5 + sizeof(vals)/sizeof(vals[0])*2) + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-13-delete.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &clash_hattr); - ok1(tdb); - if (!tdb) - continue; - - /* Check start of hash table. */ - test_val(tdb, 0); - - /* Check end of hash table. */ - test_val(tdb, -1ULL); - - /* Check mixed bitpattern. */ - test_val(tdb, 0x123456789ABCDEF0ULL); - - ok1(!tdb->file || (tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0)); - tdb_close(tdb); - - /* Deleting these entries in the db gave problems. */ - tdb = tdb_open("run-13-delete.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &fixed_hattr); - ok1(tdb); - if (!tdb) - continue; - - ok1(store_records(tdb)); - ok1(tdb_check(tdb, NULL, NULL) == 0); - for (j = 0; j < sizeof(vals)/sizeof(vals[0]); j++) { - struct tdb_data key; - - key.dptr = (unsigned char *)&vals[j]; - key.dsize = sizeof(vals[j]); - ok1(tdb_delete(tdb, key) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - } - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-14-exists.c b/ccan/tdb2/test/api-14-exists.c deleted file mode 100644 index 698006fa..00000000 --- a/ccan/tdb2/test/api-14-exists.c +++ /dev/null @@ -1,58 +0,0 @@ -#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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - 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/api-16-wipe_all.c b/ccan/tdb2/test/api-16-wipe_all.c deleted file mode 100644 index d17eff8e..00000000 --- a/ccan/tdb2/test/api-16-wipe_all.c +++ /dev/null @@ -1,50 +0,0 @@ -#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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - 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/api-21-parse_record.c b/ccan/tdb2/test/api-21-parse_record.c deleted file mode 100644 index def4f456..00000000 --- a/ccan/tdb2/test/api-21-parse_record.c +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include -#include -#include -#include -#include "logging.h" - -static enum TDB_ERROR parse(TDB_DATA key, TDB_DATA data, TDB_DATA *expected) -{ - if (!tdb_deq(data, *expected)) - 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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 2 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("api-21-parse_record.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/api-55-transaction.c b/ccan/tdb2/test/api-55-transaction.c deleted file mode 100644 index 9c1044b4..00000000 --- a/ccan/tdb2/test/api-55-transaction.c +++ /dev/null @@ -1,76 +0,0 @@ -#include // struct tdb_context -#include -#include -#include -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - unsigned char *buffer; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data; - - buffer = malloc(1000); - for (i = 0; i < 1000; i++) - buffer[i] = i; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 20 + 1); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-55-transaction.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - ok1(tdb_transaction_start(tdb) == 0); - data.dptr = buffer; - data.dsize = 1000; - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == 1000); - ok1(memcmp(data.dptr, buffer, data.dsize) == 0); - free(data.dptr); - - /* Cancelling a transaction means no store */ - tdb_transaction_cancel(tdb); - ok1(tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tdb_fetch(tdb, key, &data) == TDB_ERR_NOEXIST); - - /* Commit the transaction. */ - ok1(tdb_transaction_start(tdb) == 0); - data.dptr = buffer; - data.dsize = 1000; - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == 1000); - ok1(memcmp(data.dptr, buffer, data.dsize) == 0); - free(data.dptr); - ok1(tdb_transaction_commit(tdb) == 0); - ok1(tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == 1000); - ok1(memcmp(data.dptr, buffer, data.dsize) == 0); - free(data.dptr); - - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - free(buffer); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-80-tdb_fd.c b/ccan/tdb2/test/api-80-tdb_fd.c deleted file mode 100644 index 0088f9b7..00000000 --- a/ccan/tdb2/test/api-80-tdb_fd.c +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include -#include -#include -#include -#include "logging.h" - -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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 3); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("api-80-tdb_fd.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - continue; - - if (flags[i] & TDB_INTERNAL) - ok1(tdb_fd(tdb) == -1); - else - ok1(tdb_fd(tdb) > 2); - tdb_close(tdb); - ok1(tap_log_messages == 0); - } - return exit_status(); -} diff --git a/ccan/tdb2/test/api-81-seqnum.c b/ccan/tdb2/test/api-81-seqnum.c deleted file mode 100644 index c1eb7517..00000000 --- a/ccan/tdb2/test/api-81-seqnum.c +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i, seq; - struct tdb_context *tdb; - struct tdb_data d = { NULL, 0 }; /* Bogus GCC warning */ - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 15 + 8 * 13); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("api-81-seqnum.tdb", flags[i]|TDB_SEQNUM, - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - continue; - - seq = 0; - ok1(tdb_get_seqnum(tdb) == seq); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_get_seqnum(tdb) == ++seq); - /* Fetch doesn't change seqnum */ - if (ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS)) - free(d.dptr); - ok1(tdb_get_seqnum(tdb) == seq); - ok1(tdb_append(tdb, key, data) == TDB_SUCCESS); - /* Append in tdb1 (or store over value) bumps twice! */ - if (flags[i] & TDB_VERSION1) - seq++; - ok1(tdb_get_seqnum(tdb) == ++seq); - - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - ok1(tdb_get_seqnum(tdb) == ++seq); - /* Empty append works */ - ok1(tdb_append(tdb, key, data) == TDB_SUCCESS); - ok1(tdb_get_seqnum(tdb) == ++seq); - - ok1(tdb_wipe_all(tdb) == TDB_SUCCESS); - ok1(tdb_get_seqnum(tdb) == ++seq); - - if (!(flags[i] & TDB_INTERNAL)) { - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_get_seqnum(tdb) == ++seq); - /* Append in tdb1 (or store over value) bumps twice! */ - if (flags[i] & TDB_VERSION1) - seq++; - ok1(tdb_append(tdb, key, data) == TDB_SUCCESS); - ok1(tdb_get_seqnum(tdb) == ++seq); - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - ok1(tdb_get_seqnum(tdb) == ++seq); - ok1(tdb_transaction_commit(tdb) == TDB_SUCCESS); - ok1(tdb_get_seqnum(tdb) == seq); - - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_get_seqnum(tdb) == seq + 1); - tdb_transaction_cancel(tdb); - ok1(tdb_get_seqnum(tdb) == seq); - } - tdb_close(tdb); - ok1(tap_log_messages == 0); - } - return exit_status(); -} diff --git a/ccan/tdb2/test/api-82-lockattr.c b/ccan/tdb2/test/api-82-lockattr.c deleted file mode 100644 index 048feacf..00000000 --- a/ccan/tdb2/test/api-82-lockattr.c +++ /dev/null @@ -1,248 +0,0 @@ -#include // for tdb_fcntl_unlock -#include -#include -#include -#include -#include -#include -#include "logging.h" - -static int mylock(int fd, int rw, off_t off, off_t len, bool waitflag, - void *_err) -{ - int *lock_err = _err; - struct flock fl; - int ret; - - if (*lock_err) { - errno = *lock_err; - return -1; - } - - do { - fl.l_type = rw; - fl.l_whence = SEEK_SET; - fl.l_start = off; - fl.l_len = len; - - if (waitflag) - ret = fcntl(fd, F_SETLKW, &fl); - else - ret = fcntl(fd, F_SETLK, &fl); - } while (ret != 0 && errno == EINTR); - - return ret; -} - -static int trav_err; -static int trav(struct tdb_context *tdb, TDB_DATA k, TDB_DATA d, int *err) -{ - *err = trav_err; - return 0; -} - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - union tdb_attribute lock_attr; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - int lock_err; - - lock_attr.base.attr = TDB_ATTRIBUTE_FLOCK; - lock_attr.base.next = &tap_log_attr; - lock_attr.flock.lock = mylock; - lock_attr.flock.unlock = tdb_fcntl_unlock; - lock_attr.flock.data = &lock_err; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 80); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - struct tdb_data d; - unsigned int num_oom_messages; - - /* TDB1 double logs here. */ - if (flags[i] & TDB_VERSION1) { - num_oom_messages = 2; - } else { - num_oom_messages = 1; - } - - /* Nonblocking open; expect no error message. */ - lock_err = EAGAIN; - tdb = tdb_open("run-82-lockattr.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &lock_attr); - ok(errno == lock_err, "Errno is %u", errno); - ok1(!tdb); - ok1(tap_log_messages == 0); - - lock_err = EINTR; - tdb = tdb_open("run-82-lockattr.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &lock_attr); - ok(errno == lock_err, "Errno is %u", errno); - ok1(!tdb); - ok1(tap_log_messages == 0); - - /* Forced fail open. */ - lock_err = ENOMEM; - tdb = tdb_open("run-82-lockattr.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &lock_attr); - ok1(errno == lock_err); - ok1(!tdb); - ok1(tap_log_messages == 1); - tap_log_messages = 0; - - lock_err = 0; - tdb = tdb_open("run-82-lockattr.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &lock_attr); - if (!ok1(tdb)) - continue; - ok1(tap_log_messages == 0); - - /* Nonblocking store. */ - lock_err = EAGAIN; - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == TDB_ERR_LOCK); - ok1(tap_log_messages == num_oom_messages); - tap_log_messages = 0; - - /* Nonblocking fetch. */ - lock_err = EAGAIN; - ok1(!tdb_exists(tdb, key)); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(!tdb_exists(tdb, key)); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(!tdb_exists(tdb, key)); - ok1(tap_log_messages == num_oom_messages); - tap_log_messages = 0; - - lock_err = EAGAIN; - ok1(tdb_fetch(tdb, key, &d) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(tdb_fetch(tdb, key, &d) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(tdb_fetch(tdb, key, &d) == TDB_ERR_LOCK); - ok1(tap_log_messages == num_oom_messages); - tap_log_messages = 0; - - /* Nonblocking delete. */ - lock_err = EAGAIN; - ok1(tdb_delete(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(tdb_delete(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(tdb_delete(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == num_oom_messages); - tap_log_messages = 0; - - /* Nonblocking locks. */ - lock_err = EAGAIN; - ok1(tdb_chainlock(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(tdb_chainlock(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(tdb_chainlock(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == num_oom_messages); - tap_log_messages = 0; - - lock_err = EAGAIN; - ok1(tdb_chainlock_read(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(tdb_chainlock_read(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(tdb_chainlock_read(tdb, key) == TDB_ERR_LOCK); - ok1(tap_log_messages == num_oom_messages); - tap_log_messages = 0; - - lock_err = EAGAIN; - ok1(tdb_lockall(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(tdb_lockall(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(tdb_lockall(tdb) == TDB_ERR_LOCK); - /* This actually does divide and conquer. */ - ok1(tap_log_messages > 0); - tap_log_messages = 0; - - lock_err = EAGAIN; - ok1(tdb_lockall_read(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(tdb_lockall_read(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(tdb_lockall_read(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages > 0); - tap_log_messages = 0; - - /* Nonblocking traverse; go nonblock partway through. */ - lock_err = 0; - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == 0); - trav_err = EAGAIN; - ok1(tdb_traverse(tdb, trav, &lock_err) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - trav_err = EINTR; - lock_err = 0; - ok1(tdb_traverse(tdb, trav, &lock_err) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - trav_err = ENOMEM; - lock_err = 0; - ok1(tdb_traverse(tdb, trav, &lock_err) == TDB_ERR_LOCK); - ok1(tap_log_messages == num_oom_messages); - tap_log_messages = 0; - - /* Nonblocking transactions. */ - lock_err = EAGAIN; - ok1(tdb_transaction_start(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = EINTR; - ok1(tdb_transaction_start(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - lock_err = ENOMEM; - ok1(tdb_transaction_start(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 1); - tap_log_messages = 0; - - /* Nonblocking transaction prepare. */ - lock_err = 0; - ok1(tdb_transaction_start(tdb) == 0); - ok1(tdb_delete(tdb, key) == 0); - - lock_err = EAGAIN; - ok1(tdb_transaction_prepare_commit(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - - lock_err = 0; - ok1(tdb_transaction_prepare_commit(tdb) == 0); - ok1(tdb_transaction_commit(tdb) == 0); - - /* And the transaction was committed, right? */ - ok1(!tdb_exists(tdb, key)); - tdb_close(tdb); - ok1(tap_log_messages == 0); - } - return exit_status(); -} diff --git a/ccan/tdb2/test/api-83-openhook.c b/ccan/tdb2/test/api-83-openhook.c deleted file mode 100644 index e7e94738..00000000 --- a/ccan/tdb2/test/api-83-openhook.c +++ /dev/null @@ -1,99 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "external-agent.h" -#include "logging.h" - -static enum TDB_ERROR clear_if_first(int fd, void *arg) -{ -/* We hold a lock offset 4 always, so we can tell if anyone is holding it. - * (This is compatible with tdb1's TDB_CLEAR_IF_FIRST flag). */ - struct flock fl; - - if (arg != clear_if_first) - return TDB_ERR_CORRUPT; - - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 4; - fl.l_len = 1; - - if (fcntl(fd, F_SETLK, &fl) == 0) { - /* We must be first ones to open it! */ - diag("truncating file!"); - if (ftruncate(fd, 0) != 0) { - return TDB_ERR_IO; - } - } - fl.l_type = F_RDLCK; - if (fcntl(fd, F_SETLKW, &fl) != 0) { - return TDB_ERR_IO; - } - return TDB_SUCCESS; -} - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - struct agent *agent; - union tdb_attribute cif; - struct tdb_data key = tdb_mkdata("key", 3); - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - cif.openhook.base.attr = TDB_ATTRIBUTE_OPENHOOK; - cif.openhook.base.next = &tap_log_attr; - cif.openhook.fn = clear_if_first; - cif.openhook.data = clear_if_first; - - agent = prepare_external_agent(); - plan_tests(sizeof(flags) / sizeof(flags[0]) * 13); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - /* Create it */ - tdb = tdb_open("run-83-openhook.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, NULL); - ok1(tdb); - ok1(tdb_store(tdb, key, key, TDB_REPLACE) == 0); - tdb_close(tdb); - - /* Now, open with CIF, should clear it. */ - tdb = tdb_open("run-83-openhook.tdb", flags[i], - O_RDWR, 0, &cif); - ok1(tdb); - ok1(!tdb_exists(tdb, key)); - ok1(tdb_store(tdb, key, key, TDB_REPLACE) == 0); - - /* Agent should not clear it, since it's still open. */ - ok1(external_agent_operation(agent, OPEN_WITH_HOOK, - "run-83-openhook.tdb") == SUCCESS); - ok1(external_agent_operation(agent, FETCH, "key") == SUCCESS); - ok1(external_agent_operation(agent, CLOSE, "") == SUCCESS); - - /* Still exists for us too. */ - ok1(tdb_exists(tdb, key)); - - /* Close it, now agent should clear it. */ - tdb_close(tdb); - - ok1(external_agent_operation(agent, OPEN_WITH_HOOK, - "run-83-openhook.tdb") == SUCCESS); - ok1(external_agent_operation(agent, FETCH, "key") == FAILED); - ok1(external_agent_operation(agent, CLOSE, "") == SUCCESS); - - ok1(tap_log_messages == 0); - } - - free_external_agent(agent); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-91-get-stats.c b/ccan/tdb2/test/api-91-get-stats.c deleted file mode 100644 index d9a22ca4..00000000 --- a/ccan/tdb2/test/api-91-get-stats.c +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 11); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - union tdb_attribute *attr; - struct tdb_data key = tdb_mkdata("key", 3); - - tdb = tdb_open("run-91-get-stats.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - ok1(tdb_store(tdb, key, key, TDB_REPLACE) == 0); - - /* Use malloc so valgrind will catch overruns. */ - attr = malloc(sizeof *attr); - attr->stats.base.attr = TDB_ATTRIBUTE_STATS; - attr->stats.size = sizeof(*attr); - - ok1(tdb_get_attribute(tdb, attr) == 0); - ok1(attr->stats.size == sizeof(*attr)); - ok1(attr->stats.allocs > 0); - ok1(attr->stats.expands > 0); - ok1(attr->stats.locks > 0); - free(attr); - - /* Try short one. */ - attr = malloc(offsetof(struct tdb_attribute_stats, allocs) - + sizeof(attr->stats.allocs)); - attr->stats.base.attr = TDB_ATTRIBUTE_STATS; - attr->stats.size = offsetof(struct tdb_attribute_stats, allocs) - + sizeof(attr->stats.allocs); - ok1(tdb_get_attribute(tdb, attr) == 0); - ok1(attr->stats.size == sizeof(*attr)); - ok1(attr->stats.allocs > 0); - free(attr); - ok1(tap_log_messages == 0); - - tdb_close(tdb); - - } - return exit_status(); -} diff --git a/ccan/tdb2/test/api-92-get-set-readonly.c b/ccan/tdb2/test/api-92-get-set-readonly.c deleted file mode 100644 index 483b50d7..00000000 --- a/ccan/tdb2/test/api-92-get-set-readonly.c +++ /dev/null @@ -1,120 +0,0 @@ -#include -#include -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i, extra_msgs; - struct tdb_context *tdb; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 48); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - /* RW -> R0 */ - tdb = tdb_open("run-92-get-set-readonly.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - ok1(!(tdb_get_flags(tdb) & TDB_RDONLY)); - - /* TDB1 complains multiple times. */ - if (flags[i] & TDB_VERSION1) { - extra_msgs = 1; - } else { - extra_msgs = 0; - } - - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - - tdb_add_flag(tdb, TDB_RDONLY); - ok1(tdb_get_flags(tdb) & TDB_RDONLY); - - /* Can't store, append, delete. */ - ok1(tdb_store(tdb, key, data, TDB_MODIFY) == TDB_ERR_RDONLY); - ok1(tap_log_messages == 1); - ok1(tdb_append(tdb, key, data) == TDB_ERR_RDONLY); - tap_log_messages -= extra_msgs; - ok1(tap_log_messages == 2); - ok1(tdb_delete(tdb, key) == TDB_ERR_RDONLY); - tap_log_messages -= extra_msgs; - ok1(tap_log_messages == 3); - - /* Can't start a transaction, or any write lock. */ - ok1(tdb_transaction_start(tdb) == TDB_ERR_RDONLY); - ok1(tap_log_messages == 4); - ok1(tdb_chainlock(tdb, key) == TDB_ERR_RDONLY); - tap_log_messages -= extra_msgs; - ok1(tap_log_messages == 5); - ok1(tdb_lockall(tdb) == TDB_ERR_RDONLY); - ok1(tap_log_messages == 6); - ok1(tdb_wipe_all(tdb) == TDB_ERR_RDONLY); - ok1(tap_log_messages == 7); - - /* Back to RW. */ - tdb_remove_flag(tdb, TDB_RDONLY); - ok1(!(tdb_get_flags(tdb) & TDB_RDONLY)); - - ok1(tdb_store(tdb, key, data, TDB_MODIFY) == TDB_SUCCESS); - ok1(tdb_append(tdb, key, data) == TDB_SUCCESS); - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - ok1(tdb_transaction_commit(tdb) == TDB_SUCCESS); - - ok1(tdb_chainlock(tdb, key) == TDB_SUCCESS); - tdb_chainunlock(tdb, key); - ok1(tdb_lockall(tdb) == TDB_SUCCESS); - tdb_unlockall(tdb); - ok1(tdb_wipe_all(tdb) == TDB_SUCCESS); - ok1(tap_log_messages == 7); - - tdb_close(tdb); - - /* R0 -> RW */ - tdb = tdb_open("run-92-get-set-readonly.tdb", flags[i], - O_RDONLY, 0600, &tap_log_attr); - ok1(tdb); - ok1(tdb_get_flags(tdb) & TDB_RDONLY); - - /* Can't store, append, delete. */ - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_ERR_RDONLY); - ok1(tap_log_messages == 8); - ok1(tdb_append(tdb, key, data) == TDB_ERR_RDONLY); - tap_log_messages -= extra_msgs; - ok1(tap_log_messages == 9); - ok1(tdb_delete(tdb, key) == TDB_ERR_RDONLY); - tap_log_messages -= extra_msgs; - ok1(tap_log_messages == 10); - - /* Can't start a transaction, or any write lock. */ - ok1(tdb_transaction_start(tdb) == TDB_ERR_RDONLY); - ok1(tap_log_messages == 11); - ok1(tdb_chainlock(tdb, key) == TDB_ERR_RDONLY); - tap_log_messages -= extra_msgs; - ok1(tap_log_messages == 12); - ok1(tdb_lockall(tdb) == TDB_ERR_RDONLY); - ok1(tap_log_messages == 13); - ok1(tdb_wipe_all(tdb) == TDB_ERR_RDONLY); - ok1(tap_log_messages == 14); - - /* Can't remove TDB_RDONLY since we opened with O_RDONLY */ - tdb_remove_flag(tdb, TDB_RDONLY); - ok1(tap_log_messages == 15); - ok1(tdb_get_flags(tdb) & TDB_RDONLY); - tdb_close(tdb); - - ok1(tap_log_messages == 15); - tap_log_messages = 0; - } - return exit_status(); -} diff --git a/ccan/tdb2/test/api-93-repack.c b/ccan/tdb2/test/api-93-repack.c deleted file mode 100644 index 74a8b5ed..00000000 --- a/ccan/tdb2/test/api-93-repack.c +++ /dev/null @@ -1,82 +0,0 @@ -#include -#include -#include -#include -#include -#include "logging.h" - -#define NUM_TESTS 1000 - -static bool store_all(struct tdb_context *tdb) -{ - unsigned int i; - struct tdb_data key = { (unsigned char *)&i, sizeof(i) }; - struct tdb_data dbuf = { (unsigned char *)&i, sizeof(i) }; - - for (i = 0; i < NUM_TESTS; i++) { - if (tdb_store(tdb, key, dbuf, TDB_INSERT) != TDB_SUCCESS) - return false; - } - return true; -} - -static int mark_entry(struct tdb_context *tdb, - TDB_DATA key, TDB_DATA data, bool found[]) -{ - unsigned int num; - - if (key.dsize != sizeof(num)) - return -1; - memcpy(&num, key.dptr, key.dsize); - if (num >= NUM_TESTS) - return -1; - if (found[num]) - return -1; - found[num] = true; - return 0; -} - -static bool is_all_set(bool found[], unsigned int num) -{ - unsigned int i; - - for (i = 0; i < num; i++) - if (!found[i]) - return false; - return true; -} - -int main(int argc, char *argv[]) -{ - unsigned int i; - bool found[NUM_TESTS]; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_VERSION1|TDB_NOMMAP, - TDB_VERSION1|TDB_CONVERT, - TDB_VERSION1|TDB_NOMMAP|TDB_CONVERT - }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 6 + 1); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-93-repack.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - break; - - ok1(store_all(tdb)); - - ok1(tdb_repack(tdb) == TDB_SUCCESS); - memset(found, 0, sizeof(found)); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - ok1(tdb_traverse(tdb, mark_entry, found) == NUM_TESTS); - ok1(is_all_set(found, NUM_TESTS)); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-add-remove-flags.c b/ccan/tdb2/test/api-add-remove-flags.c deleted file mode 100644 index 231b9f6c..00000000 --- a/ccan/tdb2/test/api-add-remove-flags.c +++ /dev/null @@ -1,94 +0,0 @@ -#include // for tdb_context -#include -#include -#include -#include -#include -#include "logging.h" - -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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - plan_tests(173); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-add-remove-flags.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - ok1(tdb_get_flags(tdb) == tdb->flags); - tap_log_messages = 0; - tdb_add_flag(tdb, TDB_NOLOCK); - if (flags[i] & TDB_INTERNAL) - ok1(tap_log_messages == 1); - else { - ok1(tap_log_messages == 0); - ok1(tdb_get_flags(tdb) & TDB_NOLOCK); - } - - tap_log_messages = 0; - tdb_add_flag(tdb, TDB_NOMMAP); - if (flags[i] & TDB_INTERNAL) - ok1(tap_log_messages == 1); - else { - ok1(tap_log_messages == 0); - ok1(tdb_get_flags(tdb) & TDB_NOMMAP); - ok1(tdb->file->map_ptr == NULL); - } - - tap_log_messages = 0; - tdb_add_flag(tdb, TDB_NOSYNC); - if (flags[i] & TDB_INTERNAL) - ok1(tap_log_messages == 1); - else { - ok1(tap_log_messages == 0); - ok1(tdb_get_flags(tdb) & TDB_NOSYNC); - } - - ok1(tdb_get_flags(tdb) == tdb->flags); - - tap_log_messages = 0; - tdb_remove_flag(tdb, TDB_NOLOCK); - if (flags[i] & TDB_INTERNAL) - ok1(tap_log_messages == 1); - else { - ok1(tap_log_messages == 0); - ok1(!(tdb_get_flags(tdb) & TDB_NOLOCK)); - } - - tap_log_messages = 0; - tdb_remove_flag(tdb, TDB_NOMMAP); - if (flags[i] & TDB_INTERNAL) - ok1(tap_log_messages == 1); - else { - ok1(tap_log_messages == 0); - ok1(!(tdb_get_flags(tdb) & TDB_NOMMAP)); - ok1(tdb->file->map_ptr != NULL); - } - - tap_log_messages = 0; - tdb_remove_flag(tdb, TDB_NOSYNC); - if (flags[i] & TDB_INTERNAL) - ok1(tap_log_messages == 1); - else { - ok1(tap_log_messages == 0); - ok1(!(tdb_get_flags(tdb) & TDB_NOSYNC)); - } - - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-check-callback.c b/ccan/tdb2/test/api-check-callback.c deleted file mode 100644 index 1ea263d3..00000000 --- a/ccan/tdb2/test/api-check-callback.c +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include -#include -#include -#include -#include "logging.h" - -#define NUM_RECORDS 1000 - -static bool store_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 < NUM_RECORDS; i++) - if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) - return false; - return true; -} - -static enum TDB_ERROR check(struct tdb_data key, - struct tdb_data data, - bool *array) -{ - int val; - - if (key.dsize != sizeof(val)) { - diag("Wrong key size: %zu\n", key.dsize); - return TDB_ERR_CORRUPT; - } - - if (key.dsize != data.dsize - || memcmp(key.dptr, data.dptr, sizeof(val)) != 0) { - diag("Key and data differ\n"); - return TDB_ERR_CORRUPT; - } - - memcpy(&val, key.dptr, sizeof(val)); - if (val >= NUM_RECORDS || val < 0) { - diag("check value %i\n", val); - return TDB_ERR_CORRUPT; - } - - if (array[val]) { - diag("Value %i already seen\n", val); - return TDB_ERR_CORRUPT; - } - - array[val] = true; - return TDB_SUCCESS; -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 4 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - bool array[NUM_RECORDS]; - - tdb = tdb_open("run-check-callback.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - ok1(store_records(tdb)); - for (j = 0; j < NUM_RECORDS; j++) - array[j] = false; - ok1(tdb_check(tdb, check, array) == TDB_SUCCESS); - for (j = 0; j < NUM_RECORDS; j++) - if (!array[j]) - break; - ok1(j == NUM_RECORDS); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-firstkey-nextkey.c b/ccan/tdb2/test/api-firstkey-nextkey.c deleted file mode 100644 index e0374d86..00000000 --- a/ccan/tdb2/test/api-firstkey-nextkey.c +++ /dev/null @@ -1,163 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "logging.h" - -#define NUM_RECORDS 1000 - -static bool store_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 < NUM_RECORDS; i++) - if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) - return false; - return true; -} - -struct trav_data { - unsigned int records[NUM_RECORDS]; - unsigned int calls; -}; - -static int trav(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, void *p) -{ - struct trav_data *td = p; - int val; - - memcpy(&val, dbuf.dptr, dbuf.dsize); - td->records[td->calls++] = val; - return 0; -} - -/* Since tdb_nextkey frees dptr, we need to clone it. */ -static TDB_DATA dup_key(TDB_DATA key) -{ - void *p = malloc(key.dsize); - memcpy(p, key.dptr, key.dsize); - key.dptr = p; - return key; -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - int num; - struct trav_data td; - TDB_DATA k; - struct tdb_context *tdb; - union tdb_attribute seed_attr; - enum TDB_ERROR ecode; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - seed_attr.base.attr = TDB_ATTRIBUTE_SEED; - seed_attr.base.next = &tap_log_attr; - seed_attr.seed.seed = 6334326220117065685ULL; - - plan_tests(sizeof(flags) / sizeof(flags[0]) - * (NUM_RECORDS*6 + (NUM_RECORDS-1)*3 + 22) + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("api-firstkey-nextkey.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, - flags[i] & TDB_VERSION1 ? NULL : &seed_attr); - ok1(tdb); - if (!tdb) - continue; - - ok1(tdb_firstkey(tdb, &k) == TDB_ERR_NOEXIST); - - /* One entry... */ - k.dptr = (unsigned char *)# - k.dsize = sizeof(num); - num = 0; - ok1(tdb_store(tdb, k, k, TDB_INSERT) == 0); - ok1(tdb_firstkey(tdb, &k) == TDB_SUCCESS); - ok1(k.dsize == sizeof(num)); - ok1(memcmp(k.dptr, &num, sizeof(num)) == 0); - ok1(tdb_nextkey(tdb, &k) == TDB_ERR_NOEXIST); - - /* Two entries. */ - k.dptr = (unsigned char *)# - k.dsize = sizeof(num); - num = 1; - ok1(tdb_store(tdb, k, k, TDB_INSERT) == 0); - ok1(tdb_firstkey(tdb, &k) == TDB_SUCCESS); - ok1(k.dsize == sizeof(num)); - memcpy(&num, k.dptr, sizeof(num)); - ok1(num == 0 || num == 1); - ok1(tdb_nextkey(tdb, &k) == TDB_SUCCESS); - ok1(k.dsize == sizeof(j)); - memcpy(&j, k.dptr, sizeof(j)); - ok1(j == 0 || j == 1); - ok1(j != num); - ok1(tdb_nextkey(tdb, &k) == TDB_ERR_NOEXIST); - - /* Clean up. */ - k.dptr = (unsigned char *)# - k.dsize = sizeof(num); - num = 0; - ok1(tdb_delete(tdb, k) == 0); - num = 1; - ok1(tdb_delete(tdb, k) == 0); - - /* Now lots of records. */ - ok1(store_records(tdb)); - td.calls = 0; - - num = tdb_traverse(tdb, trav, &td); - ok1(num == NUM_RECORDS); - ok1(td.calls == NUM_RECORDS); - - /* Simple loop should match tdb_traverse */ - for (j = 0, ecode = tdb_firstkey(tdb, &k); j < td.calls; j++) { - int val; - - ok1(ecode == TDB_SUCCESS); - ok1(k.dsize == sizeof(val)); - memcpy(&val, k.dptr, k.dsize); - ok1(td.records[j] == val); - ecode = tdb_nextkey(tdb, &k); - } - - /* But arbitrary orderings should work too. */ - for (j = td.calls-1; j > 0; j--) { - k.dptr = (unsigned char *)&td.records[j-1]; - k.dsize = sizeof(td.records[j-1]); - k = dup_key(k); - ok1(tdb_nextkey(tdb, &k) == TDB_SUCCESS); - ok1(k.dsize == sizeof(td.records[j])); - ok1(memcmp(k.dptr, &td.records[j], k.dsize) == 0); - free(k.dptr); - } - - /* Even delete should work. */ - for (j = 0, ecode = tdb_firstkey(tdb, &k); - ecode != TDB_ERR_NOEXIST; - j++) { - ok1(ecode == TDB_SUCCESS); - ok1(k.dsize == 4); - ok1(tdb_delete(tdb, k) == 0); - ecode = tdb_nextkey(tdb, &k); - } - - diag("delete using first/nextkey gave %u of %u records", - j, NUM_RECORDS); - ok1(j == NUM_RECORDS); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-fork-test.c b/ccan/tdb2/test/api-fork-test.c deleted file mode 100644 index 6feb618c..00000000 --- a/ccan/tdb2/test/api-fork-test.c +++ /dev/null @@ -1,204 +0,0 @@ -/* Test forking while holding lock. - * - * There are only five ways to do this currently: - * (1) grab a tdb_chainlock, then fork. - * (2) grab a tdb_lockall, then fork. - * (3) grab a tdb_lockall_read, then fork. - * (4) start a transaction, then fork. - * (5) fork from inside a tdb_parse() callback. - * - * Note that we don't hold a lock across tdb_traverse callbacks, so - * that doesn't matter. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "logging.h" - -static enum TDB_ERROR fork_in_parse(TDB_DATA key, TDB_DATA data, - struct tdb_context *tdb) -{ - int status, extra_messages; - - if (tdb_get_flags(tdb) & TDB_VERSION1) { - extra_messages = 1; - } else { - extra_messages = 0; - } - - if (fork() == 0) { - /* We expect this to fail. */ - if (tdb_store(tdb, key, data, TDB_REPLACE) != TDB_ERR_LOCK) - exit(1); - tap_log_messages -= extra_messages; - - if (tdb_fetch(tdb, key, &data) != TDB_ERR_LOCK) - exit(1); - - tap_log_messages -= extra_messages; - if (tap_log_messages != 2) - exit(2); - - tdb_close(tdb); - if (tap_log_messages != 2) - exit(3); - exit(0); - } - wait(&status); - ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0); - return TDB_SUCCESS; -} - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 14); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - int status, extra_messages; - - if (flags[i] & TDB_VERSION1) { - extra_messages = 1; - } else { - extra_messages = 0; - } - - tap_log_messages = 0; - - tdb = tdb_open("run-fork-test.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - continue; - - /* Put a record in here. */ - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == TDB_SUCCESS); - - ok1(tdb_chainlock(tdb, key) == TDB_SUCCESS); - if (fork() == 0) { - /* We expect this to fail. */ - if (tdb_store(tdb, key, data, TDB_REPLACE) != TDB_ERR_LOCK) - return 1; - tap_log_messages -= extra_messages; - - if (tdb_fetch(tdb, key, &data) != TDB_ERR_LOCK) - return 1; - tap_log_messages -= extra_messages; - - if (tap_log_messages != 2) - return 2; - - tdb_chainunlock(tdb, key); - if (tap_log_messages != 3) - return 3; - tdb_close(tdb); - if (tap_log_messages != 3) - return 4; - return 0; - } - wait(&status); - ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0); - tdb_chainunlock(tdb, key); - - ok1(tdb_lockall(tdb) == TDB_SUCCESS); - if (fork() == 0) { - /* We expect this to fail. */ - if (tdb_store(tdb, key, data, TDB_REPLACE) != TDB_ERR_LOCK) - return 1; - tap_log_messages -= extra_messages; - - if (tdb_fetch(tdb, key, &data) != TDB_ERR_LOCK) - return 1; - tap_log_messages -= extra_messages; - - if (tap_log_messages != 2) - return 2; - - tdb_unlockall(tdb); - if (tap_log_messages != 2) - return 3; - tdb_close(tdb); - if (tap_log_messages != 2) - return 4; - return 0; - } - wait(&status); - ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0); - tdb_unlockall(tdb); - - ok1(tdb_lockall_read(tdb) == TDB_SUCCESS); - if (fork() == 0) { - /* We expect this to fail. */ - /* This would always fail anyway... */ - if (tdb_store(tdb, key, data, TDB_REPLACE) != TDB_ERR_LOCK) - return 1; - tap_log_messages -= extra_messages; - - if (tdb_fetch(tdb, key, &data) != TDB_ERR_LOCK) - return 1; - tap_log_messages -= extra_messages; - - if (tap_log_messages != 2) - return 2; - - tdb_unlockall_read(tdb); - if (tap_log_messages != 2) - return 3; - tdb_close(tdb); - if (tap_log_messages != 2) - return 4; - return 0; - } - wait(&status); - ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0); - tdb_unlockall_read(tdb); - - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - /* If transactions is empty, noop "commit" succeeds. */ - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - if (fork() == 0) { - /* We expect this to fail. */ - if (tdb_store(tdb, key, data, TDB_REPLACE) != TDB_ERR_LOCK) - return 1; - tap_log_messages -= extra_messages; - - if (tdb_fetch(tdb, key, &data) != TDB_ERR_LOCK) - return 1; - tap_log_messages -= extra_messages; - - if (tap_log_messages != 2) - return 2; - - if (tdb_transaction_commit(tdb) != TDB_ERR_LOCK) - return 3; - tap_log_messages -= extra_messages; - - tdb_close(tdb); - if (tap_log_messages < 3) - return 4; - return 0; - } - wait(&status); - ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0); - tdb_transaction_cancel(tdb); - - ok1(tdb_parse_record(tdb, key, fork_in_parse, tdb) - == TDB_SUCCESS); - tdb_close(tdb); - ok1(tap_log_messages == 0); - } - return exit_status(); -} diff --git a/ccan/tdb2/test/api-locktimeout.c b/ccan/tdb2/test/api-locktimeout.c deleted file mode 100644 index 21a26c46..00000000 --- a/ccan/tdb2/test/api-locktimeout.c +++ /dev/null @@ -1,194 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "logging.h" -#include "external-agent.h" - -#undef alarm -#define alarm fast_alarm - -/* Speed things up by doing things in milliseconds. */ -static unsigned int fast_alarm(unsigned int milli_seconds) -{ - struct itimerval it; - - it.it_interval.tv_sec = it.it_interval.tv_usec = 0; - it.it_value.tv_sec = milli_seconds / 1000; - it.it_value.tv_usec = milli_seconds * 1000; - setitimer(ITIMER_REAL, &it, NULL); - return 0; -} - -#define CatchSignal(sig, handler) signal((sig), (handler)) - -static void do_nothing(int signum) -{ -} - -/* This example code is taken from SAMBA, so try not to change it. */ -static struct flock flock_struct; - -/* Return a value which is none of v1, v2 or v3. */ -static inline short int invalid_value(short int v1, short int v2, short int v3) -{ - short int try = (v1+v2+v3)^((v1+v2+v3) << 16); - while (try == v1 || try == v2 || try == v3) - try++; - return try; -} - -/* We invalidate in as many ways as we can, so the OS rejects it */ -static void invalidate_flock_struct(int signum) -{ - flock_struct.l_type = invalid_value(F_RDLCK, F_WRLCK, F_UNLCK); - flock_struct.l_whence = invalid_value(SEEK_SET, SEEK_CUR, SEEK_END); - flock_struct.l_start = -1; - /* A large negative. */ - flock_struct.l_len = (((off_t)1 << (sizeof(off_t)*CHAR_BIT - 1)) + 1); -} - -static int timeout_lock(int fd, int rw, off_t off, off_t len, bool waitflag, - void *_timeout) -{ - int ret, saved_errno = errno; - unsigned int timeout = *(unsigned int *)_timeout; - - flock_struct.l_type = rw; - flock_struct.l_whence = SEEK_SET; - flock_struct.l_start = off; - flock_struct.l_len = len; - - CatchSignal(SIGALRM, invalidate_flock_struct); - alarm(timeout); - - for (;;) { - if (waitflag) - ret = fcntl(fd, F_SETLKW, &flock_struct); - else - ret = fcntl(fd, F_SETLK, &flock_struct); - - if (ret == 0) - break; - - /* Not signalled? Something else went wrong. */ - if (flock_struct.l_len == len) { - if (errno == EAGAIN || errno == EINTR) - continue; - saved_errno = errno; - break; - } else { - saved_errno = EINTR; - break; - } - } - - alarm(0); - errno = saved_errno; - return ret; -} - -static int tdb_chainlock_with_timeout_internal(struct tdb_context *tdb, - TDB_DATA key, - unsigned int timeout, - int rw_type) -{ - union tdb_attribute locking; - enum TDB_ERROR ecode; - - if (timeout) { - locking.base.attr = TDB_ATTRIBUTE_FLOCK; - ecode = tdb_get_attribute(tdb, &locking); - if (ecode != TDB_SUCCESS) - return ecode; - - /* Replace locking function with our own. */ - locking.flock.data = &timeout; - locking.flock.lock = timeout_lock; - - ecode = tdb_set_attribute(tdb, &locking); - if (ecode != TDB_SUCCESS) - return ecode; - } - if (rw_type == F_RDLCK) - ecode = tdb_chainlock_read(tdb, key); - else - ecode = tdb_chainlock(tdb, key); - - if (timeout) { - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_FLOCK); - } - return ecode; -} - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - TDB_DATA key = tdb_mkdata("hello", 5); - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct agent *agent; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 15); - - agent = prepare_external_agent(); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - enum TDB_ERROR ecode; - tdb = tdb_open("run-locktimeout.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - break; - - /* Simple cases: should succeed. */ - ecode = tdb_chainlock_with_timeout_internal(tdb, key, 20, - F_RDLCK); - ok1(ecode == TDB_SUCCESS); - ok1(tap_log_messages == 0); - - tdb_chainunlock_read(tdb, key); - ok1(tap_log_messages == 0); - - ecode = tdb_chainlock_with_timeout_internal(tdb, key, 20, - F_WRLCK); - ok1(ecode == TDB_SUCCESS); - ok1(tap_log_messages == 0); - - tdb_chainunlock(tdb, key); - ok1(tap_log_messages == 0); - - /* OK, get agent to start transaction, then we should time out. */ - ok1(external_agent_operation(agent, OPEN, "run-locktimeout.tdb") - == SUCCESS); - ok1(external_agent_operation(agent, TRANSACTION_START, "") - == SUCCESS); - ecode = tdb_chainlock_with_timeout_internal(tdb, key, 20, - F_WRLCK); - ok1(ecode == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - - /* Even if we get a different signal, should be fine. */ - CatchSignal(SIGUSR1, do_nothing); - external_agent_operation(agent, SEND_SIGNAL, ""); - ecode = tdb_chainlock_with_timeout_internal(tdb, key, 20, - F_WRLCK); - ok1(ecode == TDB_ERR_LOCK); - ok1(tap_log_messages == 0); - - ok1(external_agent_operation(agent, TRANSACTION_COMMIT, "") - == SUCCESS); - ok1(external_agent_operation(agent, CLOSE, "") - == SUCCESS); - tdb_close(tdb); - } - free_external_agent(agent); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-missing-entries.c b/ccan/tdb2/test/api-missing-entries.c deleted file mode 100644 index 0b21e1ee..00000000 --- a/ccan/tdb2/test/api-missing-entries.c +++ /dev/null @@ -1,43 +0,0 @@ -/* Another test revealed that we lost an entry. This reproduces it. */ -#include -#include -#include -#include -#include -#include -#include "logging.h" - -#define NUM_RECORDS 1189 - -/* We use the same seed which we saw this failure on. */ -static uint64_t failhash(const void *key, size_t len, uint64_t seed, void *p) -{ - seed = 699537674708983027ULL; - return hash64_stable((const unsigned char *)key, len, seed); -} - -int main(int argc, char *argv[]) -{ - int i; - struct tdb_context *tdb; - struct tdb_data key = { (unsigned char *)&i, sizeof(i) }; - struct tdb_data data = { (unsigned char *)&i, sizeof(i) }; - union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, - .fn = failhash } }; - - hattr.base.next = &tap_log_attr; - plan_tests(1 + NUM_RECORDS + 2); - - tdb = tdb_open("run-missing-entries.tdb", TDB_INTERNAL, - O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr); - if (ok1(tdb)) { - for (i = 0; i < NUM_RECORDS; i++) { - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == 0); - } - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-open-multiple-times.c b/ccan/tdb2/test/api-open-multiple-times.c deleted file mode 100644 index 16562069..00000000 --- a/ccan/tdb2/test/api-open-multiple-times.c +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i, extra_messages; - struct tdb_context *tdb, *tdb2; - struct tdb_data key = { (unsigned char *)&i, sizeof(i) }; - struct tdb_data data = { (unsigned char *)&i, sizeof(i) }; - struct tdb_data d = { NULL, 0 }; /* Bogus GCC warning */ - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 28); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-open-multiple-times.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - if (flags[i] & TDB_VERSION1) { - extra_messages = 1; - } else { - extra_messages = 0; - } - tdb2 = tdb_open("run-open-multiple-times.tdb", flags[i], - O_RDWR|O_CREAT, 0600, &tap_log_attr); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tdb_check(tdb2, NULL, NULL) == 0); - - /* Store in one, fetch in the other. */ - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == 0); - ok1(tdb_fetch(tdb2, key, &d) == TDB_SUCCESS); - ok1(tdb_deq(d, data)); - free(d.dptr); - - /* Vice versa, with delete. */ - ok1(tdb_delete(tdb2, key) == 0); - ok1(tdb_fetch(tdb, key, &d) == TDB_ERR_NOEXIST); - - /* OK, now close first one, check second still good. */ - ok1(tdb_close(tdb) == 0); - - ok1(tdb_store(tdb2, key, data, TDB_REPLACE) == 0); - ok1(tdb_fetch(tdb2, key, &d) == TDB_SUCCESS); - ok1(tdb_deq(d, data)); - free(d.dptr); - - /* Reopen */ - tdb = tdb_open("run-open-multiple-times.tdb", flags[i], - O_RDWR|O_CREAT, 0600, &tap_log_attr); - ok1(tdb); - - ok1(tdb_transaction_start(tdb2) == 0); - - /* Anything in the other one should fail. */ - ok1(tdb_fetch(tdb, key, &d) == TDB_ERR_LOCK); - tap_log_messages -= extra_messages; - ok1(tap_log_messages == 1); - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == TDB_ERR_LOCK); - tap_log_messages -= extra_messages; - ok1(tap_log_messages == 2); - ok1(tdb_transaction_start(tdb) == TDB_ERR_LOCK); - ok1(tap_log_messages == 3); - ok1(tdb_chainlock(tdb, key) == TDB_ERR_LOCK); - tap_log_messages -= extra_messages; - ok1(tap_log_messages == 4); - - /* Transaciton should work as normal. */ - ok1(tdb_store(tdb2, key, data, TDB_REPLACE) == TDB_SUCCESS); - - /* Now... try closing with locks held. */ - ok1(tdb_close(tdb2) == 0); - - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(tdb_deq(d, data)); - free(d.dptr); - ok1(tdb_close(tdb) == 0); - ok1(tap_log_messages == 4); - tap_log_messages = 0; - } - - return exit_status(); -} diff --git a/ccan/tdb2/test/api-record-expand.c b/ccan/tdb2/test/api-record-expand.c deleted file mode 100644 index 48ad1cdf..00000000 --- a/ccan/tdb2/test/api-record-expand.c +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "logging.h" - -#define MAX_SIZE 10000 -#define SIZE_STEP 131 - -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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data; - - data.dptr = malloc(MAX_SIZE); - memset(data.dptr, 0x24, MAX_SIZE); - - plan_tests(sizeof(flags) / sizeof(flags[0]) - * (3 + (1 + (MAX_SIZE/SIZE_STEP)) * 2) + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-record-expand.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - data.dsize = 0; - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - for (data.dsize = 0; - data.dsize < MAX_SIZE; - data.dsize += SIZE_STEP) { - memset(data.dptr, data.dsize, data.dsize); - ok1(tdb_store(tdb, key, data, TDB_MODIFY) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - } - tdb_close(tdb); - } - ok1(tap_log_messages == 0); - free(data.dptr); - - return exit_status(); -} diff --git a/ccan/tdb2/test/api-simple-delete.c b/ccan/tdb2/test/api-simple-delete.c deleted file mode 100644 index a5b65d60..00000000 --- a/ccan/tdb2/test/api-simple-delete.c +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include -#include -#include -#include "logging.h" - -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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 7 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-simple-delete.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (tdb) { - /* Delete should fail. */ - ok1(tdb_delete(tdb, key) == TDB_ERR_NOEXIST); - ok1(tdb_check(tdb, NULL, NULL) == 0); - /* Insert should succeed. */ - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - /* Delete should now work. */ - ok1(tdb_delete(tdb, key) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - } - } - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-summary.c b/ccan/tdb2/test/api-summary.c deleted file mode 100644 index e0e292ea..00000000 --- a/ccan/tdb2/test/api-summary.c +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = { (unsigned char *)&j, sizeof(j) }; - struct tdb_data data = { (unsigned char *)&j, sizeof(j) }; - char *summary; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * (1 + 2 * 5) + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-summary.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - /* Put some stuff in there. */ - for (j = 0; j < 500; j++) { - /* Make sure padding varies to we get some graphs! */ - data.dsize = j % (sizeof(j) + 1); - if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) - fail("Storing in tdb"); - } - - for (j = 0; - j <= TDB_SUMMARY_HISTOGRAMS; - j += TDB_SUMMARY_HISTOGRAMS) { - ok1(tdb_summary(tdb, j, &summary) == TDB_SUCCESS); - ok1(strstr(summary, "Number of records: 500\n")); - ok1(strstr(summary, "Smallest/average/largest keys: 4/4/4\n")); - ok1(strstr(summary, "Smallest/average/largest data: 0/2/4\n")); - if (!(flags[i] & TDB_VERSION1) - && j == TDB_SUMMARY_HISTOGRAMS) { - ok1(strstr(summary, "|") - && strstr(summary, "*")); - } else { - ok1(!strstr(summary, "|") - && !strstr(summary, "*")); - } - free(summary); - } - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/api-tdb1-flag-removal.c b/ccan/tdb2/test/api-tdb1-flag-removal.c deleted file mode 100644 index 28f24e63..00000000 --- a/ccan/tdb2/test/api-tdb1-flag-removal.c +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 3 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-12-store.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - continue; - - tdb_close(tdb); - - tdb = tdb_open("run-12-store.tdb", flags[i] | TDB_VERSION1, - O_RDWR, 0600, &tap_log_attr); - if (!ok1(tdb)) - continue; - /* It's not a version1 */ - ok1(!(tdb_get_flags(tdb) & TDB_VERSION1)); - - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/external-agent.c b/ccan/tdb2/test/external-agent.c deleted file mode 100644 index 01c7106f..00000000 --- a/ccan/tdb2/test/external-agent.c +++ /dev/null @@ -1,256 +0,0 @@ -#include "external-agent.h" -#include "logging.h" -#include "lock-tracking.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static struct tdb_context *tdb; - -void (*external_agent_free)(void *) = free; - -static enum TDB_ERROR clear_if_first(int fd, void *arg) -{ -/* We hold a lock offset 4 always, so we can tell if anyone is holding it. - * (This is compatible with tdb1's TDB_CLEAR_IF_FIRST flag). */ - struct flock fl; - - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 4; - fl.l_len = 1; - - if (fcntl(fd, F_SETLK, &fl) == 0) { - /* We must be first ones to open it! */ - diag("agent truncating file!"); - if (ftruncate(fd, 0) != 0) { - return TDB_ERR_IO; - } - } - fl.l_type = F_RDLCK; - if (fcntl(fd, F_SETLKW, &fl) != 0) { - return TDB_ERR_IO; - } - return TDB_SUCCESS; -} - -static enum agent_return do_operation(enum operation op, const char *name) -{ - TDB_DATA k; - enum agent_return ret; - TDB_DATA data; - enum TDB_ERROR ecode; - union tdb_attribute cif; - - if (op != OPEN && op != OPEN_WITH_HOOK && !tdb) { - diag("external: No tdb open!"); - return OTHER_FAILURE; - } - - diag("external: %s", operation_name(op)); - - k = tdb_mkdata(name, strlen(name)); - - locking_would_block = 0; - switch (op) { - case OPEN: - if (tdb) { - diag("Already have tdb %s open", tdb->name); - return OTHER_FAILURE; - } - tdb = tdb_open(name, TDB_DEFAULT, O_RDWR, 0, &tap_log_attr); - if (!tdb) { - if (!locking_would_block) - diag("Opening tdb gave %s", strerror(errno)); - forget_locking(); - ret = OTHER_FAILURE; - } else - ret = SUCCESS; - break; - case OPEN_WITH_HOOK: - if (tdb) { - diag("Already have tdb %s open", tdb->name); - return OTHER_FAILURE; - } - cif.openhook.base.attr = TDB_ATTRIBUTE_OPENHOOK; - cif.openhook.base.next = &tap_log_attr; - cif.openhook.fn = clear_if_first; - tdb = tdb_open(name, TDB_DEFAULT, O_RDWR, 0, &cif); - if (!tdb) { - if (!locking_would_block) - diag("Opening tdb gave %s", strerror(errno)); - forget_locking(); - ret = OTHER_FAILURE; - } else - ret = SUCCESS; - break; - case FETCH: - ecode = tdb_fetch(tdb, k, &data); - if (ecode == TDB_ERR_NOEXIST) { - ret = FAILED; - } else if (ecode < 0) { - ret = OTHER_FAILURE; - } else if (!tdb_deq(data, k)) { - ret = OTHER_FAILURE; - external_agent_free(data.dptr); - } else { - ret = SUCCESS; - external_agent_free(data.dptr); - } - break; - case STORE: - ret = tdb_store(tdb, k, k, 0) == 0 ? SUCCESS : OTHER_FAILURE; - break; - case TRANSACTION_START: - ret = tdb_transaction_start(tdb) == 0 ? SUCCESS : OTHER_FAILURE; - break; - case TRANSACTION_COMMIT: - ret = tdb_transaction_commit(tdb)==0 ? SUCCESS : OTHER_FAILURE; - break; - case NEEDS_RECOVERY: - if (tdb->flags & TDB_VERSION1) - ret = tdb1_needs_recovery(tdb) ? SUCCESS : FAILED; - else - ret = tdb_needs_recovery(tdb) ? SUCCESS : FAILED; - break; - case CHECK: - ret = tdb_check(tdb, NULL, NULL) == 0 ? SUCCESS : OTHER_FAILURE; - break; - case CLOSE: - ret = tdb_close(tdb) == 0 ? SUCCESS : OTHER_FAILURE; - tdb = NULL; - break; - case SEND_SIGNAL: - /* We do this async */ - ret = SUCCESS; - break; - default: - ret = OTHER_FAILURE; - } - - if (locking_would_block) - ret = WOULD_HAVE_BLOCKED; - - return ret; -} - -struct agent { - int cmdfd, responsefd; -}; - -/* Do this before doing any tdb stuff. Return handle, or NULL. */ -struct agent *prepare_external_agent(void) -{ - int pid, ret; - int command[2], response[2]; - char name[1+PATH_MAX]; - - if (pipe(command) != 0 || pipe(response) != 0) - return NULL; - - pid = fork(); - if (pid < 0) - return NULL; - - if (pid != 0) { - struct agent *agent = malloc(sizeof(*agent)); - - close(command[0]); - close(response[1]); - agent->cmdfd = command[1]; - agent->responsefd = response[0]; - return agent; - } - - close(command[1]); - close(response[0]); - - /* We want to fail, not block. */ - nonblocking_locks = true; - log_prefix = "external: "; - while ((ret = read(command[0], name, sizeof(name))) > 0) { - enum agent_return result; - - result = do_operation(name[0], name+1); - if (write(response[1], &result, sizeof(result)) - != sizeof(result)) - err(1, "Writing response"); - if (name[0] == SEND_SIGNAL) { - struct timeval ten_ms; - ten_ms.tv_sec = 0; - ten_ms.tv_usec = 10000; - select(0, NULL, NULL, NULL, &ten_ms); - kill(getppid(), SIGUSR1); - } - } - exit(0); -} - -/* Ask the external agent to try to do an operation. */ -enum agent_return external_agent_operation(struct agent *agent, - enum operation op, - const char *name) -{ - enum agent_return res; - unsigned int len; - char *string; - - if (!name) - name = ""; - len = 1 + strlen(name) + 1; - string = malloc(len); - - string[0] = op; - strcpy(string+1, name); - - if (write(agent->cmdfd, string, len) != len - || read(agent->responsefd, &res, sizeof(res)) != sizeof(res)) - res = AGENT_DIED; - - free(string); - return res; -} - -const char *agent_return_name(enum agent_return ret) -{ - return ret == SUCCESS ? "SUCCESS" - : ret == WOULD_HAVE_BLOCKED ? "WOULD_HAVE_BLOCKED" - : ret == AGENT_DIED ? "AGENT_DIED" - : ret == FAILED ? "FAILED" - : ret == OTHER_FAILURE ? "OTHER_FAILURE" - : "**INVALID**"; -} - -const char *operation_name(enum operation op) -{ - switch (op) { - case OPEN: return "OPEN"; - case OPEN_WITH_HOOK: return "OPEN_WITH_HOOK"; - case FETCH: return "FETCH"; - case STORE: return "STORE"; - case CHECK: return "CHECK"; - case TRANSACTION_START: return "TRANSACTION_START"; - case TRANSACTION_COMMIT: return "TRANSACTION_COMMIT"; - case NEEDS_RECOVERY: return "NEEDS_RECOVERY"; - case SEND_SIGNAL: return "SEND_SIGNAL"; - case CLOSE: return "CLOSE"; - } - return "**INVALID**"; -} - -void free_external_agent(struct agent *agent) -{ - close(agent->cmdfd); - close(agent->responsefd); - free(agent); -} diff --git a/ccan/tdb2/test/external-agent.h b/ccan/tdb2/test/external-agent.h deleted file mode 100644 index 9d25c582..00000000 --- a/ccan/tdb2/test/external-agent.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef TDB2_TEST_EXTERNAL_AGENT_H -#define TDB2_TEST_EXTERNAL_AGENT_H - -/* For locking tests, we need a different process to try things at - * various times. */ -enum operation { - OPEN, - OPEN_WITH_HOOK, - FETCH, - STORE, - TRANSACTION_START, - TRANSACTION_COMMIT, - NEEDS_RECOVERY, - CHECK, - SEND_SIGNAL, - CLOSE, -}; - -/* Do this before doing any tdb stuff. Return handle, or -1. */ -struct agent *prepare_external_agent(void); - -enum agent_return { - SUCCESS, - WOULD_HAVE_BLOCKED, - AGENT_DIED, - FAILED, /* For fetch, or NEEDS_RECOVERY */ - OTHER_FAILURE, -}; - -/* Ask the external agent to try to do an operation. - * name == tdb name for OPEN/OPEN_WITH_CLEAR_IF_FIRST, - * record name for FETCH/STORE (store stores name as data too) - */ -enum agent_return external_agent_operation(struct agent *handle, - enum operation op, - const char *name); - -/* Hook into free() on tdb_data in external agent. */ -void (*external_agent_free)(void *); - -/* Mapping enum -> string. */ -const char *agent_return_name(enum agent_return ret); -const char *operation_name(enum operation op); - -void free_external_agent(struct agent *agent); -#endif /* TDB2_TEST_EXTERNAL_AGENT_H */ diff --git a/ccan/tdb2/test/failtest_helper.c b/ccan/tdb2/test/failtest_helper.c deleted file mode 100644 index ab79de19..00000000 --- a/ccan/tdb2/test/failtest_helper.c +++ /dev/null @@ -1,96 +0,0 @@ -#include "failtest_helper.h" -#include "logging.h" -#include -#include - -bool failtest_suppress = false; - -/* FIXME: From ccan/str */ -static inline bool strends(const char *str, const char *postfix) -{ - if (strlen(str) < strlen(postfix)) - return false; - - return !strcmp(str + strlen(str) - strlen(postfix), postfix); -} - -bool failmatch(const struct failtest_call *call, - const char *file, int line, enum failtest_call_type type) -{ - return call->type == type - && call->line == line - && ((strcmp(call->file, file) == 0) - || (strends(call->file, file) - && (call->file[strlen(call->file) - strlen(file) - 1] - == '/'))); -} - -static bool is_nonblocking_lock(const struct failtest_call *call) -{ - return call->type == FAILTEST_FCNTL && call->u.fcntl.cmd == F_SETLK; -} - -static bool is_unlock(const struct failtest_call *call) -{ - return call->type == FAILTEST_FCNTL - && call->u.fcntl.arg.fl.l_type == F_UNLCK; -} - -bool exit_check_log(struct tlist_calls *history) -{ - const struct failtest_call *i; - - tlist_for_each(history, i, list) { - if (!i->fail) - continue; - /* Failing the /dev/urandom open doesn't count: we fall back. */ - if (failmatch(i, URANDOM_OPEN)) - continue; - - /* Similarly with read fail. */ - if (failmatch(i, URANDOM_READ)) - continue; - - /* Initial allocation of tdb doesn't log. */ - if (failmatch(i, INITIAL_TDB_MALLOC)) - continue; - - /* We don't block "failures" on non-blocking locks. */ - if (is_nonblocking_lock(i)) - continue; - - if (!tap_log_messages) - diag("We didn't log for %s:%u", i->file, i->line); - return tap_log_messages != 0; - } - return true; -} - -/* Some places we soldier on despite errors: only fail them once. */ -enum failtest_result -block_repeat_failures(struct tlist_calls *history) -{ - const struct failtest_call *last; - - last = tlist_tail(history, list); - - if (failtest_suppress) - return FAIL_DONT_FAIL; - - if (failmatch(last, INITIAL_TDB_MALLOC) - || failmatch(last, URANDOM_OPEN) - || failmatch(last, URANDOM_READ)) { - return FAIL_PROBE; - } - - /* We handle mmap failing, by falling back to read/write, so - * don't try all possible paths. */ - if (last->type == FAILTEST_MMAP) - return FAIL_PROBE; - - /* Unlock or non-blocking lock is fail-once. */ - if (is_unlock(last) || is_nonblocking_lock(last)) - return FAIL_PROBE; - - return FAIL_OK; -} diff --git a/ccan/tdb2/test/failtest_helper.h b/ccan/tdb2/test/failtest_helper.h deleted file mode 100644 index 4130aff1..00000000 --- a/ccan/tdb2/test/failtest_helper.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TDB2_TEST_FAILTEST_HELPER_H -#define TDB2_TEST_FAILTEST_HELPER_H -#include -#include - -/* FIXME: Check these! */ -#define INITIAL_TDB_MALLOC "open.c", 445, FAILTEST_MALLOC -#define URANDOM_OPEN "open.c", 62, FAILTEST_OPEN -#define URANDOM_READ "open.c", 42, FAILTEST_READ - -bool exit_check_log(struct tlist_calls *history); -bool failmatch(const struct failtest_call *call, - const char *file, int line, enum failtest_call_type type); -enum failtest_result block_repeat_failures(struct tlist_calls *history); - -/* Set this to suppress failure. */ -extern bool failtest_suppress; - -#endif /* TDB2_TEST_LOGGING_H */ diff --git a/ccan/tdb2/test/jenkins-be-hash.tdb1 b/ccan/tdb2/test/jenkins-be-hash.tdb1 deleted file mode 100644 index b652840414857969987a986c848ede159804166e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 696 ucmWG>aZ*Uj%t_^9zz)aZ*Uj%t_^9zz%XH8P%H6q@GUMcAd{ -#include -#include -#include -#include "logging.h" - -struct tdb_layout *new_tdb_layout(void) -{ - struct tdb_layout *layout = malloc(sizeof(*layout)); - layout->num_elems = 0; - layout->elem = NULL; - return layout; -} - -static void add(struct tdb_layout *layout, union tdb_layout_elem elem) -{ - layout->elem = realloc(layout->elem, - sizeof(layout->elem[0]) - * (layout->num_elems+1)); - layout->elem[layout->num_elems++] = elem; -} - -void tdb_layout_add_freetable(struct tdb_layout *layout) -{ - union tdb_layout_elem elem; - elem.base.type = FREETABLE; - add(layout, elem); -} - -void tdb_layout_add_free(struct tdb_layout *layout, tdb_len_t len, - unsigned ftable) -{ - union tdb_layout_elem elem; - elem.base.type = FREE; - elem.free.len = len; - elem.free.ftable_num = ftable; - add(layout, elem); -} - -void tdb_layout_add_capability(struct tdb_layout *layout, - uint64_t type, - bool write_breaks, - bool check_breaks, - bool open_breaks, - tdb_len_t extra) -{ - union tdb_layout_elem elem; - elem.base.type = CAPABILITY; - elem.capability.type = type; - if (write_breaks) - elem.capability.type |= TDB_CAP_NOWRITE; - if (open_breaks) - elem.capability.type |= TDB_CAP_NOOPEN; - if (check_breaks) - elem.capability.type |= TDB_CAP_NOCHECK; - elem.capability.extra = extra; - add(layout, elem); -} - -static struct tdb_data dup_key(struct tdb_data key) -{ - struct tdb_data ret; - ret.dsize = key.dsize; - ret.dptr = malloc(ret.dsize); - memcpy(ret.dptr, key.dptr, ret.dsize); - return ret; -} - -void tdb_layout_add_used(struct tdb_layout *layout, - TDB_DATA key, TDB_DATA data, - tdb_len_t extra) -{ - union tdb_layout_elem elem; - elem.base.type = DATA; - elem.used.key = dup_key(key); - elem.used.data = dup_key(data); - elem.used.extra = extra; - add(layout, elem); -} - -static tdb_len_t free_record_len(tdb_len_t len) -{ - return sizeof(struct tdb_used_record) + len; -} - -static tdb_len_t data_record_len(struct tle_used *used) -{ - tdb_len_t len; - len = sizeof(struct tdb_used_record) - + used->key.dsize + used->data.dsize + used->extra; - assert(len >= sizeof(struct tdb_free_record)); - return len; -} - -static tdb_len_t hashtable_len(struct tle_hashtable *htable) -{ - return sizeof(struct tdb_used_record) - + (sizeof(tdb_off_t) << TDB_SUBLEVEL_HASH_BITS) - + htable->extra; -} - -static tdb_len_t capability_len(struct tle_capability *cap) -{ - return sizeof(struct tdb_capability) + cap->extra; -} - -static tdb_len_t freetable_len(struct tle_freetable *ftable) -{ - return sizeof(struct tdb_freetable); -} - -static void set_free_record(void *mem, tdb_len_t len) -{ - /* We do all the work in add_to_freetable */ -} - -static void add_zero_pad(struct tdb_used_record *u, size_t len, size_t extra) -{ - if (extra) - ((char *)(u + 1))[len] = '\0'; -} - -static void set_data_record(void *mem, struct tdb_context *tdb, - struct tle_used *used) -{ - struct tdb_used_record *u = mem; - - set_header(tdb, u, TDB_USED_MAGIC, used->key.dsize, used->data.dsize, - used->key.dsize + used->data.dsize + used->extra, - tdb_hash(tdb, used->key.dptr, used->key.dsize)); - memcpy(u + 1, used->key.dptr, used->key.dsize); - memcpy((char *)(u + 1) + used->key.dsize, - used->data.dptr, used->data.dsize); - add_zero_pad(u, used->key.dsize + used->data.dsize, used->extra); -} - -static void set_hashtable(void *mem, struct tdb_context *tdb, - struct tle_hashtable *htable) -{ - struct tdb_used_record *u = mem; - tdb_len_t len = sizeof(tdb_off_t) << TDB_SUBLEVEL_HASH_BITS; - - set_header(tdb, u, TDB_HTABLE_MAGIC, 0, len, len + htable->extra, 0); - memset(u + 1, 0, len); - add_zero_pad(u, len, htable->extra); -} - -static void set_capability(void *mem, struct tdb_context *tdb, - struct tle_capability *cap, struct tdb_header *hdr, - tdb_off_t last_cap) -{ - struct tdb_capability *c = mem; - tdb_len_t len = sizeof(*c) - sizeof(struct tdb_used_record) + cap->extra; - - c->type = cap->type; - c->next = 0; - set_header(tdb, &c->hdr, TDB_CAP_MAGIC, 0, len, len, 0); - - /* Append to capability list. */ - if (!last_cap) { - hdr->capabilities = cap->base.off; - } else { - c = (struct tdb_capability *)((char *)hdr + last_cap); - c->next = cap->base.off; - } -} - -static void set_freetable(void *mem, struct tdb_context *tdb, - struct tle_freetable *freetable, struct tdb_header *hdr, - tdb_off_t last_ftable) -{ - struct tdb_freetable *ftable = mem; - memset(ftable, 0, sizeof(*ftable)); - set_header(tdb, &ftable->hdr, TDB_FTABLE_MAGIC, 0, - sizeof(*ftable) - sizeof(ftable->hdr), - sizeof(*ftable) - sizeof(ftable->hdr), 0); - - if (last_ftable) { - ftable = (struct tdb_freetable *)((char *)hdr + last_ftable); - ftable->next = freetable->base.off; - } else { - hdr->free_table = freetable->base.off; - } -} - -static void add_to_freetable(struct tdb_context *tdb, - tdb_off_t eoff, - tdb_off_t elen, - unsigned ftable, - struct tle_freetable *freetable) -{ - tdb->tdb2.ftable_off = freetable->base.off; - tdb->tdb2.ftable = ftable; - add_free_record(tdb, eoff, sizeof(struct tdb_used_record) + elen, - TDB_LOCK_WAIT, false); -} - -static tdb_off_t hbucket_off(tdb_off_t group_start, unsigned ingroup) -{ - return group_start - + (ingroup % (1 << TDB_HASH_GROUP_BITS)) * sizeof(tdb_off_t); -} - -/* Get bits from a value. */ -static uint32_t bits(uint64_t val, unsigned start, unsigned num) -{ - assert(num <= 32); - return (val >> start) & ((1U << num) - 1); -} - -/* We take bits from the top: that way we can lock whole sections of the hash - * by using lock ranges. */ -static uint32_t use_bits(uint64_t h, unsigned num, unsigned *used) -{ - *used += num; - return bits(h, 64 - *used, num); -} - -static tdb_off_t encode_offset(tdb_off_t new_off, unsigned bucket, - uint64_t h) -{ - return bucket - | new_off - | ((uint64_t)bits(h, 64 - TDB_OFF_UPPER_STEAL_EXTRA, - TDB_OFF_UPPER_STEAL_EXTRA) - << TDB_OFF_HASH_EXTRA_BIT); -} - -/* FIXME: Our hash table handling here is primitive: we don't expand! */ -static void add_to_hashtable(struct tdb_context *tdb, - tdb_off_t eoff, - struct tdb_data key) -{ - uint64_t h = tdb_hash(tdb, key.dptr, key.dsize); - tdb_off_t b_off, group_start; - unsigned i, group, in_group; - unsigned used = 0; - - group = use_bits(h, TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS, &used); - in_group = use_bits(h, TDB_HASH_GROUP_BITS, &used); - - group_start = offsetof(struct tdb_header, hashtable) - + group * (sizeof(tdb_off_t) << TDB_HASH_GROUP_BITS); - - for (i = 0; i < (1 << TDB_HASH_GROUP_BITS); i++) { - unsigned bucket = (in_group + i) % (1 << TDB_HASH_GROUP_BITS); - - b_off = hbucket_off(group_start, bucket); - if (tdb_read_off(tdb, b_off) == 0) { - tdb_write_off(tdb, b_off, - encode_offset(eoff, in_group, h)); - return; - } - } - abort(); -} - -static struct tle_freetable *find_ftable(struct tdb_layout *layout, unsigned num) -{ - unsigned i; - - for (i = 0; i < layout->num_elems; i++) { - if (layout->elem[i].base.type != FREETABLE) - continue; - if (num == 0) - return &layout->elem[i].ftable; - num--; - } - abort(); -} - -/* FIXME: Support TDB_CONVERT */ -struct tdb_context *tdb_layout_get(struct tdb_layout *layout, - void (*freefn)(void *), - union tdb_attribute *attr) -{ - unsigned int i; - tdb_off_t off, len, last_ftable, last_cap; - char *mem; - struct tdb_context *tdb; - - off = sizeof(struct tdb_header); - - /* First pass of layout: calc lengths */ - for (i = 0; i < layout->num_elems; i++) { - union tdb_layout_elem *e = &layout->elem[i]; - e->base.off = off; - switch (e->base.type) { - case FREETABLE: - len = freetable_len(&e->ftable); - break; - case FREE: - len = free_record_len(e->free.len); - break; - case DATA: - len = data_record_len(&e->used); - break; - case HASHTABLE: - len = hashtable_len(&e->hashtable); - break; - case CAPABILITY: - len = capability_len(&e->capability); - break; - default: - abort(); - } - off += len; - } - - mem = malloc(off); - /* Fill with some weird pattern. */ - memset(mem, 0x99, off); - /* Now populate our header, cribbing from a real TDB header. */ - tdb = tdb_open(NULL, TDB_INTERNAL, O_RDWR, 0, attr); - memcpy(mem, tdb->file->map_ptr, sizeof(struct tdb_header)); - - /* Mug the tdb we have to make it use this. */ - freefn(tdb->file->map_ptr); - tdb->file->map_ptr = mem; - tdb->file->map_size = off; - - last_ftable = 0; - last_cap = 0; - for (i = 0; i < layout->num_elems; i++) { - union tdb_layout_elem *e = &layout->elem[i]; - switch (e->base.type) { - case FREETABLE: - set_freetable(mem + e->base.off, tdb, &e->ftable, - (struct tdb_header *)mem, last_ftable); - last_ftable = e->base.off; - break; - case FREE: - set_free_record(mem + e->base.off, e->free.len); - break; - case DATA: - set_data_record(mem + e->base.off, tdb, &e->used); - break; - case HASHTABLE: - set_hashtable(mem + e->base.off, tdb, &e->hashtable); - break; - case CAPABILITY: - set_capability(mem + e->base.off, tdb, &e->capability, - (struct tdb_header *)mem, last_cap); - last_cap = e->base.off; - break; - } - } - /* Must have a free table! */ - assert(last_ftable); - - /* Now fill the free and hash tables. */ - for (i = 0; i < layout->num_elems; i++) { - union tdb_layout_elem *e = &layout->elem[i]; - switch (e->base.type) { - case FREE: - add_to_freetable(tdb, e->base.off, e->free.len, - e->free.ftable_num, - find_ftable(layout, e->free.ftable_num)); - break; - case DATA: - add_to_hashtable(tdb, e->base.off, e->used.key); - break; - default: - break; - } - } - - tdb->tdb2.ftable_off = find_ftable(layout, 0)->base.off; - return tdb; -} - -void tdb_layout_write(struct tdb_layout *layout, void (*freefn)(void *), - union tdb_attribute *attr, const char *filename) -{ - struct tdb_context *tdb = tdb_layout_get(layout, freefn, attr); - int fd; - - fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0600); - if (fd < 0) - err(1, "opening %s for writing", filename); - if (write(fd, tdb->file->map_ptr, tdb->file->map_size) - != tdb->file->map_size) - err(1, "writing %s", filename); - close(fd); - tdb_close(tdb); -} - -void tdb_layout_free(struct tdb_layout *layout) -{ - unsigned int i; - - for (i = 0; i < layout->num_elems; i++) { - if (layout->elem[i].base.type == DATA) { - free(layout->elem[i].used.key.dptr); - free(layout->elem[i].used.data.dptr); - } - } - free(layout->elem); - free(layout); -} diff --git a/ccan/tdb2/test/layout.h b/ccan/tdb2/test/layout.h deleted file mode 100644 index 9a714846..00000000 --- a/ccan/tdb2/test/layout.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef TDB2_TEST_LAYOUT_H -#define TDB2_TEST_LAYOUT_H -#include - -struct tdb_layout *new_tdb_layout(void); -void tdb_layout_add_freetable(struct tdb_layout *layout); -void tdb_layout_add_free(struct tdb_layout *layout, tdb_len_t len, - unsigned ftable); -void tdb_layout_add_used(struct tdb_layout *layout, - TDB_DATA key, TDB_DATA data, - tdb_len_t extra); -void tdb_layout_add_capability(struct tdb_layout *layout, - uint64_t type, - bool write_breaks, - bool check_breaks, - bool open_breaks, - tdb_len_t extra); - -#if 0 /* FIXME: Allow allocation of subtables */ -void tdb_layout_add_hashtable(struct tdb_layout *layout, - int htable_parent, /* -1 == toplevel */ - unsigned int bucket, - tdb_len_t extra); -#endif -/* freefn is needed if we're using failtest_free. */ -struct tdb_context *tdb_layout_get(struct tdb_layout *layout, - void (*freefn)(void *), - union tdb_attribute *attr); -void tdb_layout_write(struct tdb_layout *layout, void (*freefn)(void *), - union tdb_attribute *attr, const char *filename); - -void tdb_layout_free(struct tdb_layout *layout); - -enum layout_type { - FREETABLE, FREE, DATA, HASHTABLE, CAPABILITY -}; - -/* Shared by all union members. */ -struct tle_base { - enum layout_type type; - tdb_off_t off; -}; - -struct tle_freetable { - struct tle_base base; -}; - -struct tle_free { - struct tle_base base; - tdb_len_t len; - unsigned ftable_num; -}; - -struct tle_used { - struct tle_base base; - TDB_DATA key; - TDB_DATA data; - tdb_len_t extra; -}; - -struct tle_hashtable { - struct tle_base base; - int parent; - unsigned int bucket; - tdb_len_t extra; -}; - -struct tle_capability { - struct tle_base base; - uint64_t type; - tdb_len_t extra; -}; - -union tdb_layout_elem { - struct tle_base base; - struct tle_freetable ftable; - struct tle_free free; - struct tle_used used; - struct tle_hashtable hashtable; - struct tle_capability capability; -}; - -struct tdb_layout { - unsigned int num_elems; - union tdb_layout_elem *elem; -}; -#endif /* TDB2_TEST_LAYOUT_H */ diff --git a/ccan/tdb2/test/lock-tracking.c b/ccan/tdb2/test/lock-tracking.c deleted file mode 100644 index e253db9f..00000000 --- a/ccan/tdb2/test/lock-tracking.c +++ /dev/null @@ -1,158 +0,0 @@ -/* We save the locks so we can reaquire them. */ -#include -#include -#include -#include -#include -#include -#include "lock-tracking.h" - -struct lock { - struct lock *next; - unsigned int off; - unsigned int len; - int type; -}; -static struct lock *locks; -int locking_errors = 0; -bool suppress_lockcheck = false; -bool nonblocking_locks; -int locking_would_block = 0; -void (*unlock_callback)(int fd); - -int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ) -{ - va_list ap; - int ret, arg3; - struct flock *fl; - bool may_block = false; - - if (cmd != F_SETLK && cmd != F_SETLKW) { - /* This may be totally bogus, but we don't know in general. */ - va_start(ap, cmd); - arg3 = va_arg(ap, int); - va_end(ap); - - return fcntl(fd, cmd, arg3); - } - - va_start(ap, cmd); - fl = va_arg(ap, struct flock *); - va_end(ap); - - if (cmd == F_SETLKW && nonblocking_locks) { - cmd = F_SETLK; - may_block = true; - } - ret = fcntl(fd, cmd, fl); - - /* Detect when we failed, but might have been OK if we waited. */ - if (may_block && ret == -1 && (errno == EAGAIN || errno == EACCES)) { - locking_would_block++; - } - - if (fl->l_type == F_UNLCK) { - struct lock **l; - struct lock *old = NULL; - - for (l = &locks; *l; l = &(*l)->next) { - if ((*l)->off == fl->l_start - && (*l)->len == fl->l_len) { - if (ret == 0) { - old = *l; - *l = (*l)->next; - free(old); - } - break; - } - } - if (!old && !suppress_lockcheck) { - diag("Unknown unlock %u@%u - %i", - (int)fl->l_len, (int)fl->l_start, ret); - locking_errors++; - } - } else { - struct lock *new, *i; - unsigned int fl_end = fl->l_start + fl->l_len; - if (fl->l_len == 0) - fl_end = (unsigned int)-1; - - /* Check for overlaps: we shouldn't do this. */ - for (i = locks; i; i = i->next) { - unsigned int i_end = i->off + i->len; - if (i->len == 0) - i_end = (unsigned int)-1; - - if (fl->l_start >= i->off && fl->l_start < i_end) - break; - if (fl_end > i->off && fl_end < i_end) - break; - - /* tdb_allrecord_lock does this, handle adjacent: */ - if (fl->l_start > TDB_HASH_LOCK_START - && fl->l_start == i_end && fl->l_type == i->type) { - if (ret == 0) { - i->len = fl->l_len - ? i->len + fl->l_len - : 0; - } - goto done; - } - } - if (i) { - /* Special case: upgrade of allrecord lock. */ - if (i->type == F_RDLCK && fl->l_type == F_WRLCK - && i->off == TDB_HASH_LOCK_START - && fl->l_start == TDB_HASH_LOCK_START - && i->len == 0 - && fl->l_len == 0) { - if (ret == 0) - i->type = F_WRLCK; - goto done; - } - /* allrecord upgrade for tdb1. */ - if (i->type == F_RDLCK && fl->l_type == F_WRLCK - && i->off == TDB1_FREELIST_TOP - && fl->l_start == TDB1_FREELIST_TOP - && i->len == 0 - && fl->l_len == 0) { - if (ret == 0) - i->type = F_WRLCK; - goto done; - } - - if (!suppress_lockcheck) { - diag("%s lock %u@%u overlaps %u@%u", - fl->l_type == F_WRLCK ? "write" : "read", - (int)fl->l_len, (int)fl->l_start, - i->len, (int)i->off); - locking_errors++; - } - } - - if (ret == 0) { - new = malloc(sizeof *new); - new->off = fl->l_start; - new->len = fl->l_len; - new->type = fl->l_type; - new->next = locks; - locks = new; - } - } -done: - if (ret == 0 && fl->l_type == F_UNLCK && unlock_callback) - unlock_callback(fd); - return ret; -} - -unsigned int forget_locking(void) -{ - unsigned int num = 0; - while (locks) { - struct lock *next = locks->next; - free(locks); - locks = next; - num++; - } - return num; -} diff --git a/ccan/tdb2/test/lock-tracking.h b/ccan/tdb2/test/lock-tracking.h deleted file mode 100644 index f2c9c446..00000000 --- a/ccan/tdb2/test/lock-tracking.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef LOCK_TRACKING_H -#define LOCK_TRACKING_H -#include - -/* Set this if you want a callback after fnctl unlock. */ -extern void (*unlock_callback)(int fd); - -/* Replacement fcntl. */ -int fcntl_with_lockcheck(int fd, int cmd, ... /* arg */ ); - -/* Discard locking info: returns number of locks outstanding. */ -unsigned int forget_locking(void); - -/* Number of errors in locking. */ -extern int locking_errors; - -/* Suppress lock checking. */ -extern bool suppress_lockcheck; - -/* Make all locks non-blocking. */ -extern bool nonblocking_locks; - -/* Number of times we failed a lock because we made it non-blocking. */ -extern int locking_would_block; -#endif /* LOCK_TRACKING_H */ diff --git a/ccan/tdb2/test/logging.c b/ccan/tdb2/test/logging.c deleted file mode 100644 index 0712cc00..00000000 --- a/ccan/tdb2/test/logging.c +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include -#include -#include "logging.h" - -unsigned tap_log_messages; -const char *log_prefix = ""; -char *log_last = NULL; -bool suppress_logging; - -union tdb_attribute tap_log_attr = { - .log = { .base = { .attr = TDB_ATTRIBUTE_LOG }, - .fn = tap_log_fn } -}; - -void tap_log_fn(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, void *priv) -{ - if (suppress_logging) - return; - - diag("tdb log level %u: %s: %s%s", - level, tdb_errorstr(ecode), log_prefix, message); - if (log_last) - free(log_last); - log_last = strdup(message); - tap_log_messages++; -} - diff --git a/ccan/tdb2/test/logging.h b/ccan/tdb2/test/logging.h deleted file mode 100644 index 2dfea145..00000000 --- a/ccan/tdb2/test/logging.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef TDB2_TEST_LOGGING_H -#define TDB2_TEST_LOGGING_H -#include -#include -#include - -extern bool suppress_logging; -extern const char *log_prefix; -extern unsigned tap_log_messages; -extern union tdb_attribute tap_log_attr; -extern char *log_last; - -void tap_log_fn(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, void *priv); -#endif /* TDB2_TEST_LOGGING_H */ diff --git a/ccan/tdb2/test/old-nohash-be.tdb1 b/ccan/tdb2/test/old-nohash-be.tdb1 deleted file mode 100644 index 1c49116c1d94ba64c90e6b36651fc5bb41e9b16c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 696 jcmWG>aZ*Uj%t_^9zz)aZ*Uj%t_^9zz%XH8P%GxOO1+-hQNS@0020x1JwWk diff --git a/ccan/tdb2/test/run-001-encode.c b/ccan/tdb2/test/run-001-encode.c deleted file mode 100644 index 67616fcd..00000000 --- a/ccan/tdb2/test/run-001-encode.c +++ /dev/null @@ -1,41 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_used_record rec; - struct tdb_context tdb = { .log_fn = tap_log_fn }; - - plan_tests(64 + 32 + 48*6 + 1); - - /* We should be able to encode any data value. */ - for (i = 0; i < 64; i++) - ok1(set_header(&tdb, &rec, TDB_USED_MAGIC, 0, 1ULL << i, - 1ULL << i, 0) == 0); - - /* And any key and data with < 64 bits between them. */ - for (i = 0; i < 32; i++) { - tdb_len_t dlen = 1ULL >> (63 - i), klen = 1ULL << i; - ok1(set_header(&tdb, &rec, TDB_USED_MAGIC, klen, dlen, - klen + dlen, 0) == 0); - } - - /* We should neatly encode all values. */ - for (i = 0; i < 48; i++) { - uint64_t h = 1ULL << (i < 5 ? i : 4); - uint64_t klen = 1ULL << (i < 16 ? i : 15); - uint64_t dlen = 1ULL << i; - uint64_t xlen = 1ULL << (i < 32 ? i : 31); - ok1(set_header(&tdb, &rec, TDB_USED_MAGIC, klen, dlen, - klen+dlen+xlen, h) == 0); - ok1(rec_key_length(&rec) == klen); - ok1(rec_data_length(&rec) == dlen); - ok1(rec_extra_padding(&rec) == xlen); - ok1((uint64_t)rec_hash(&rec) == h); - ok1(rec_magic(&rec) == TDB_USED_MAGIC); - } - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-001-fls.c b/ccan/tdb2/test/run-001-fls.c deleted file mode 100644 index 4449f69d..00000000 --- a/ccan/tdb2/test/run-001-fls.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "tdb2-source.h" -#include - -static unsigned int dumb_fls(uint64_t num) -{ - int i; - - for (i = 63; i >= 0; i--) { - if (num & (1ULL << i)) - break; - } - return i + 1; -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - - plan_tests(64 * 64 + 2); - - ok1(fls64(0) == 0); - ok1(dumb_fls(0) == 0); - - for (i = 0; i < 64; i++) { - for (j = 0; j < 64; j++) { - uint64_t val = (1ULL << i) | (1ULL << j); - ok(fls64(val) == dumb_fls(val), - "%llu -> %u should be %u", (long long)val, - fls64(val), dumb_fls(val)); - } - } - return exit_status(); -} diff --git a/ccan/tdb2/test/run-01-new_database.c b/ccan/tdb2/test/run-01-new_database.c deleted file mode 100644 index a5f0dd37..00000000 --- a/ccan/tdb2/test/run-01-new_database.c +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include "tdb2-source.h" -#include -#include -#include "logging.h" -#include "failtest_helper.h" - -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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - failtest_init(argc, argv); - failtest_hook = block_repeat_failures; - failtest_exit_check = exit_check_log; - plan_tests(sizeof(flags) / sizeof(flags[0]) * 3); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-new_database.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - failtest_exit(exit_status()); - - failtest_suppress = true; - ok1(tdb_check(tdb, NULL, NULL) == 0); - failtest_suppress = false; - tdb_close(tdb); - if (!ok1(tap_log_messages == 0)) - break; - } - failtest_exit(exit_status()); -} diff --git a/ccan/tdb2/test/run-02-expand.c b/ccan/tdb2/test/run-02-expand.c deleted file mode 100644 index e3f5905a..00000000 --- a/ccan/tdb2/test/run-02-expand.c +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include "tdb2-source.h" -#include -#include -#include "logging.h" -#include "failtest_helper.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - uint64_t val; - 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]) * 11 + 1); - - failtest_init(argc, argv); - failtest_hook = block_repeat_failures; - failtest_exit_check = exit_check_log; - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - failtest_suppress = true; - tdb = tdb_open("run-expand.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - break; - - val = tdb->file->map_size; - /* Need some hash lock for expand. */ - ok1(tdb_lock_hashes(tdb, 0, 1, F_WRLCK, TDB_LOCK_WAIT) == 0); - failtest_suppress = false; - if (!ok1(tdb_expand(tdb, 1) == 0)) { - failtest_suppress = true; - tdb_close(tdb); - break; - } - failtest_suppress = true; - - ok1(tdb->file->map_size >= val + 1 * TDB_EXTENSION_FACTOR); - ok1(tdb_unlock_hashes(tdb, 0, 1, F_WRLCK) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - val = tdb->file->map_size; - ok1(tdb_lock_hashes(tdb, 0, 1, F_WRLCK, TDB_LOCK_WAIT) == 0); - failtest_suppress = false; - if (!ok1(tdb_expand(tdb, 1024) == 0)) { - failtest_suppress = true; - tdb_close(tdb); - break; - } - failtest_suppress = true; - ok1(tdb_unlock_hashes(tdb, 0, 1, F_WRLCK) == 0); - ok1(tdb->file->map_size >= val + 1024 * TDB_EXTENSION_FACTOR); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - failtest_exit(exit_status()); -} diff --git a/ccan/tdb2/test/run-03-coalesce.c b/ccan/tdb2/test/run-03-coalesce.c deleted file mode 100644 index 99f94fe1..00000000 --- a/ccan/tdb2/test/run-03-coalesce.c +++ /dev/null @@ -1,178 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" -#include "layout.h" - -static tdb_len_t free_record_length(struct tdb_context *tdb, tdb_off_t off) -{ - struct tdb_free_record f; - enum TDB_ERROR ecode; - - ecode = tdb_read_convert(tdb, off, &f, sizeof(f)); - if (ecode != TDB_SUCCESS) - return ecode; - if (frec_magic(&f) != TDB_FREE_MAGIC) - return TDB_ERR_CORRUPT; - return frec_len(&f); -} - -int main(int argc, char *argv[]) -{ - tdb_off_t b_off, test; - struct tdb_context *tdb; - struct tdb_layout *layout; - struct tdb_data data, key; - tdb_len_t len; - - /* FIXME: Test TDB_CONVERT */ - /* FIXME: Test lock order fail. */ - - plan_tests(42); - data = tdb_mkdata("world", 5); - key = tdb_mkdata("hello", 5); - - /* No coalescing can be done due to EOF */ - layout = new_tdb_layout(); - tdb_layout_add_freetable(layout); - len = 1024; - tdb_layout_add_free(layout, len, 0); - tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb"); - /* NOMMAP is for lockcheck. */ - tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0, - &tap_log_attr); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(free_record_length(tdb, layout->elem[1].base.off) == len); - - /* Figure out which bucket free entry is. */ - b_off = bucket_off(tdb->tdb2.ftable_off, size_to_bucket(len)); - /* Lock and fail to coalesce. */ - ok1(tdb_lock_free_bucket(tdb, b_off, TDB_LOCK_WAIT) == 0); - test = layout->elem[1].base.off; - ok1(coalesce(tdb, layout->elem[1].base.off, b_off, len, &test) - == 0); - tdb_unlock_free_bucket(tdb, b_off); - ok1(free_record_length(tdb, layout->elem[1].base.off) == len); - ok1(test == layout->elem[1].base.off); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - tdb_layout_free(layout); - - /* No coalescing can be done due to used record */ - layout = new_tdb_layout(); - tdb_layout_add_freetable(layout); - tdb_layout_add_free(layout, 1024, 0); - tdb_layout_add_used(layout, key, data, 6); - tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb"); - /* NOMMAP is for lockcheck. */ - tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0, - &tap_log_attr); - ok1(free_record_length(tdb, layout->elem[1].base.off) == 1024); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Figure out which bucket free entry is. */ - b_off = bucket_off(tdb->tdb2.ftable_off, size_to_bucket(1024)); - /* Lock and fail to coalesce. */ - ok1(tdb_lock_free_bucket(tdb, b_off, TDB_LOCK_WAIT) == 0); - test = layout->elem[1].base.off; - ok1(coalesce(tdb, layout->elem[1].base.off, b_off, 1024, &test) - == 0); - tdb_unlock_free_bucket(tdb, b_off); - ok1(free_record_length(tdb, layout->elem[1].base.off) == 1024); - ok1(test == layout->elem[1].base.off); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - tdb_layout_free(layout); - - /* Coalescing can be done due to two free records, then EOF */ - layout = new_tdb_layout(); - tdb_layout_add_freetable(layout); - tdb_layout_add_free(layout, 1024, 0); - tdb_layout_add_free(layout, 2048, 0); - tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb"); - /* NOMMAP is for lockcheck. */ - tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0, - &tap_log_attr); - ok1(free_record_length(tdb, layout->elem[1].base.off) == 1024); - ok1(free_record_length(tdb, layout->elem[2].base.off) == 2048); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Figure out which bucket (first) free entry is. */ - b_off = bucket_off(tdb->tdb2.ftable_off, size_to_bucket(1024)); - /* Lock and coalesce. */ - ok1(tdb_lock_free_bucket(tdb, b_off, TDB_LOCK_WAIT) == 0); - test = layout->elem[2].base.off; - ok1(coalesce(tdb, layout->elem[1].base.off, b_off, 1024, &test) - == 1024 + sizeof(struct tdb_used_record) + 2048); - /* Should tell us it's erased this one... */ - ok1(test == TDB_ERR_NOEXIST); - ok1(tdb->file->allrecord_lock.count == 0 && tdb->file->num_lockrecs == 0); - ok1(free_record_length(tdb, layout->elem[1].base.off) - == 1024 + sizeof(struct tdb_used_record) + 2048); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - tdb_layout_free(layout); - - /* Coalescing can be done due to two free records, then data */ - layout = new_tdb_layout(); - tdb_layout_add_freetable(layout); - tdb_layout_add_free(layout, 1024, 0); - tdb_layout_add_free(layout, 512, 0); - tdb_layout_add_used(layout, key, data, 6); - tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb"); - /* NOMMAP is for lockcheck. */ - tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0, - &tap_log_attr); - ok1(free_record_length(tdb, layout->elem[1].base.off) == 1024); - ok1(free_record_length(tdb, layout->elem[2].base.off) == 512); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Figure out which bucket free entry is. */ - b_off = bucket_off(tdb->tdb2.ftable_off, size_to_bucket(1024)); - /* Lock and coalesce. */ - ok1(tdb_lock_free_bucket(tdb, b_off, TDB_LOCK_WAIT) == 0); - test = layout->elem[2].base.off; - ok1(coalesce(tdb, layout->elem[1].base.off, b_off, 1024, &test) - == 1024 + sizeof(struct tdb_used_record) + 512); - ok1(tdb->file->allrecord_lock.count == 0 && tdb->file->num_lockrecs == 0); - ok1(free_record_length(tdb, layout->elem[1].base.off) - == 1024 + sizeof(struct tdb_used_record) + 512); - ok1(test == TDB_ERR_NOEXIST); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - tdb_layout_free(layout); - - /* Coalescing can be done due to three free records, then EOF */ - layout = new_tdb_layout(); - tdb_layout_add_freetable(layout); - tdb_layout_add_free(layout, 1024, 0); - tdb_layout_add_free(layout, 512, 0); - tdb_layout_add_free(layout, 256, 0); - tdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.tdb"); - /* NOMMAP is for lockcheck. */ - tdb = tdb_open("run-03-coalesce.tdb", TDB_NOMMAP, O_RDWR, 0, - &tap_log_attr); - ok1(free_record_length(tdb, layout->elem[1].base.off) == 1024); - ok1(free_record_length(tdb, layout->elem[2].base.off) == 512); - ok1(free_record_length(tdb, layout->elem[3].base.off) == 256); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Figure out which bucket free entry is. */ - b_off = bucket_off(tdb->tdb2.ftable_off, size_to_bucket(1024)); - /* Lock and coalesce. */ - ok1(tdb_lock_free_bucket(tdb, b_off, TDB_LOCK_WAIT) == 0); - test = layout->elem[2].base.off; - ok1(coalesce(tdb, layout->elem[1].base.off, b_off, 1024, &test) - == 1024 + sizeof(struct tdb_used_record) + 512 - + sizeof(struct tdb_used_record) + 256); - ok1(tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0); - ok1(free_record_length(tdb, layout->elem[1].base.off) - == 1024 + sizeof(struct tdb_used_record) + 512 - + sizeof(struct tdb_used_record) + 256); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - tdb_layout_free(layout); - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-04-basichash.c b/ccan/tdb2/test/run-04-basichash.c deleted file mode 100644 index 48527444..00000000 --- a/ccan/tdb2/test/run-04-basichash.c +++ /dev/null @@ -1,260 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -/* We rig the hash so adjacent-numbered records always clash. */ -static uint64_t clash(const void *key, size_t len, uint64_t seed, void *priv) -{ - return ((uint64_t)*(const unsigned int *)key) - << (64 - TDB_TOPLEVEL_HASH_BITS - 1); -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - unsigned int v; - struct tdb_used_record rec; - struct tdb_data key = { (unsigned char *)&v, sizeof(v) }; - struct tdb_data dbuf = { (unsigned char *)&v, sizeof(v) }; - union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, - .fn = clash } }; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - }; - - hattr.base.next = &tap_log_attr; - - plan_tests(sizeof(flags) / sizeof(flags[0]) - * (91 + (2 * ((1 << TDB_HASH_GROUP_BITS) - 1))) + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - struct hash_info h; - tdb_off_t new_off, off, subhash; - - tdb = tdb_open("run-04-basichash.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr); - ok1(tdb); - if (!tdb) - continue; - - v = 0; - /* Should not find it. */ - ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) == 0); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have located space in group 0, bucket 0. */ - ok1(h.group_start == offsetof(struct tdb_header, hashtable)); - ok1(h.home_bucket == 0); - ok1(h.found_bucket == 0); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS); - - /* Should have lock on bucket 0 */ - ok1(h.hlock_start == 0); - ok1(h.hlock_range == - 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS))); - ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1); - ok1((tdb->flags & TDB_NOLOCK) - || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START); - /* FIXME: Check lock length */ - - /* Allocate a new record. */ - new_off = alloc(tdb, key.dsize, dbuf.dsize, h.h, - TDB_USED_MAGIC, false); - ok1(!TDB_OFF_IS_ERR(new_off)); - - /* We should be able to add it now. */ - ok1(add_to_hash(tdb, &h, new_off) == 0); - - /* Make sure we fill it in for later finding. */ - off = new_off + sizeof(struct tdb_used_record); - ok1(!tdb->tdb2.io->twrite(tdb, off, key.dptr, key.dsize)); - off += key.dsize; - ok1(!tdb->tdb2.io->twrite(tdb, off, dbuf.dptr, dbuf.dsize)); - - /* We should be able to unlock that OK. */ - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_WRLCK) == 0); - - /* Database should be consistent. */ - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Now, this should give a successful lookup. */ - ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) - == new_off); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have located space in group 0, bucket 0. */ - ok1(h.group_start == offsetof(struct tdb_header, hashtable)); - ok1(h.home_bucket == 0); - ok1(h.found_bucket == 0); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS); - - /* Should have lock on bucket 0 */ - ok1(h.hlock_start == 0); - ok1(h.hlock_range == - 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS))); - ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1); - ok1((tdb->flags & TDB_NOLOCK) - || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START); - /* FIXME: Check lock length */ - - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_WRLCK) == 0); - - /* Database should be consistent. */ - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Test expansion. */ - v = 1; - ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) == 0); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have located space in group 0, bucket 1. */ - ok1(h.group_start == offsetof(struct tdb_header, hashtable)); - ok1(h.home_bucket == 0); - ok1(h.found_bucket == 1); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS); - - /* Should have lock on bucket 0 */ - ok1(h.hlock_start == 0); - ok1(h.hlock_range == - 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS))); - ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1); - ok1((tdb->flags & TDB_NOLOCK) - || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START); - /* FIXME: Check lock length */ - - /* Make it expand 0'th bucket. */ - ok1(expand_group(tdb, &h) == 0); - /* First one should be subhash, next should be empty. */ - ok1(is_subhash(h.group[0])); - subhash = (h.group[0] & TDB_OFF_MASK); - for (j = 1; j < (1 << TDB_HASH_GROUP_BITS); j++) - ok1(h.group[j] == 0); - - ok1(tdb_write_convert(tdb, h.group_start, - h.group, sizeof(h.group)) == 0); - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_WRLCK) == 0); - - /* Should be happy with expansion. */ - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Should be able to find it. */ - v = 0; - ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) - == new_off); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have located space in expanded group 0, bucket 0. */ - ok1(h.group_start == subhash + sizeof(struct tdb_used_record)); - ok1(h.home_bucket == 0); - ok1(h.found_bucket == 0); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS - + TDB_SUBLEVEL_HASH_BITS); - - /* Should have lock on bucket 0 */ - ok1(h.hlock_start == 0); - ok1(h.hlock_range == - 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS))); - ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1); - ok1((tdb->flags & TDB_NOLOCK) - || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START); - /* FIXME: Check lock length */ - - /* Simple delete should work. */ - ok1(delete_from_hash(tdb, &h) == 0); - ok1(add_free_record(tdb, new_off, - sizeof(struct tdb_used_record) - + rec_key_length(&rec) - + rec_data_length(&rec) - + rec_extra_padding(&rec), - TDB_LOCK_NOWAIT, false) == 0); - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_WRLCK) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Test second-level expansion: should expand 0th bucket. */ - v = 0; - ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) == 0); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have located space in group 0, bucket 0. */ - ok1(h.group_start == subhash + sizeof(struct tdb_used_record)); - ok1(h.home_bucket == 0); - ok1(h.found_bucket == 0); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS+TDB_SUBLEVEL_HASH_BITS); - - /* Should have lock on bucket 0 */ - ok1(h.hlock_start == 0); - ok1(h.hlock_range == - 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS))); - ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1); - ok1((tdb->flags & TDB_NOLOCK) - || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START); - /* FIXME: Check lock length */ - - ok1(expand_group(tdb, &h) == 0); - /* First one should be subhash, next should be empty. */ - ok1(is_subhash(h.group[0])); - subhash = (h.group[0] & TDB_OFF_MASK); - for (j = 1; j < (1 << TDB_HASH_GROUP_BITS); j++) - ok1(h.group[j] == 0); - ok1(tdb_write_convert(tdb, h.group_start, - h.group, sizeof(h.group)) == 0); - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_WRLCK) == 0); - - /* Should be happy with expansion. */ - ok1(tdb_check(tdb, NULL, NULL) == 0); - - ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) == 0); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have located space in group 0, bucket 0. */ - ok1(h.group_start == subhash + sizeof(struct tdb_used_record)); - ok1(h.home_bucket == 0); - ok1(h.found_bucket == 0); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS - + TDB_SUBLEVEL_HASH_BITS * 2); - - /* We should be able to add it now. */ - /* Allocate a new record. */ - new_off = alloc(tdb, key.dsize, dbuf.dsize, h.h, - TDB_USED_MAGIC, false); - ok1(!TDB_OFF_IS_ERR(new_off)); - ok1(add_to_hash(tdb, &h, new_off) == 0); - - /* Make sure we fill it in for later finding. */ - off = new_off + sizeof(struct tdb_used_record); - ok1(!tdb->tdb2.io->twrite(tdb, off, key.dptr, key.dsize)); - off += key.dsize; - ok1(!tdb->tdb2.io->twrite(tdb, off, dbuf.dptr, dbuf.dsize)); - - /* We should be able to unlock that OK. */ - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_WRLCK) == 0); - - /* Database should be consistent. */ - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Should be able to find it. */ - v = 0; - ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) - == new_off); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have located space in expanded group 0, bucket 0. */ - ok1(h.group_start == subhash + sizeof(struct tdb_used_record)); - ok1(h.home_bucket == 0); - ok1(h.found_bucket == 0); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS - + TDB_SUBLEVEL_HASH_BITS * 2); - - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-05-readonly-open.c b/ccan/tdb2/test/run-05-readonly-open.c deleted file mode 100644 index 80eb5675..00000000 --- a/ccan/tdb2/test/run-05-readonly-open.c +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include "tdb2-source.h" -#include -#include -#include "logging.h" -#include "failtest_helper.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4), d; - union tdb_attribute seed_attr; - unsigned int msgs = 0; - - failtest_init(argc, argv); - failtest_hook = block_repeat_failures; - failtest_exit_check = exit_check_log; - - seed_attr.base.attr = TDB_ATTRIBUTE_SEED; - seed_attr.base.next = &tap_log_attr; - seed_attr.seed.seed = 0; - - failtest_suppress = true; - plan_tests(sizeof(flags) / sizeof(flags[0]) * 11); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-05-readonly-open.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, - flags[i] & TDB_VERSION1 - ? &tap_log_attr : &seed_attr); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - tdb_close(tdb); - - failtest_suppress = false; - tdb = tdb_open("run-05-readonly-open.tdb", flags[i], - O_RDONLY, 0600, &tap_log_attr); - if (!ok1(tdb)) - break; - ok1(tap_log_messages == msgs); - /* Fetch should succeed, stores should fail. */ - if (!ok1(tdb_fetch(tdb, key, &d) == 0)) - goto fail; - ok1(tdb_deq(d, data)); - free(d.dptr); - if (!ok1(tdb_store(tdb, key, data, TDB_MODIFY) - == TDB_ERR_RDONLY)) - goto fail; - ok1(tap_log_messages == ++msgs); - if (!ok1(tdb_store(tdb, key, data, TDB_INSERT) - == TDB_ERR_RDONLY)) - goto fail; - ok1(tap_log_messages == ++msgs); - failtest_suppress = true; - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - ok1(tap_log_messages == msgs); - /* SIGH: failtest bug, it doesn't save the tdb file because - * we have it read-only. If we go around again, it gets - * changed underneath us and things get screwy. */ - if (failtest_has_failed()) - break; - } - failtest_exit(exit_status()); - -fail: - failtest_suppress = true; - tdb_close(tdb); - failtest_exit(exit_status()); -} diff --git a/ccan/tdb2/test/run-10-simple-store.c b/ccan/tdb2/test/run-10-simple-store.c deleted file mode 100644 index 10bbb498..00000000 --- a/ccan/tdb2/test/run-10-simple-store.c +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include "tdb2-source.h" -#include -#include -#include "logging.h" -#include "failtest_helper.h" - -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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - - failtest_init(argc, argv); - failtest_hook = block_repeat_failures; - failtest_exit_check = exit_check_log; - - failtest_suppress = true; - plan_tests(sizeof(flags) / sizeof(flags[0]) * 7 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-10-simple-store.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - break; - /* Modify should fail. */ - failtest_suppress = false; - if (!ok1(tdb_store(tdb, key, data, TDB_MODIFY) - == TDB_ERR_NOEXIST)) - goto fail; - failtest_suppress = true; - ok1(tdb_check(tdb, NULL, NULL) == 0); - /* Insert should succeed. */ - failtest_suppress = false; - if (!ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0)) - goto fail; - failtest_suppress = true; - ok1(tdb_check(tdb, NULL, NULL) == 0); - /* Second insert should fail. */ - failtest_suppress = false; - if (!ok1(tdb_store(tdb, key, data, TDB_INSERT) - == TDB_ERR_EXISTS)) - goto fail; - failtest_suppress = true; - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - } - ok1(tap_log_messages == 0); - failtest_exit(exit_status()); - -fail: - failtest_suppress = true; - tdb_close(tdb); - failtest_exit(exit_status()); -} diff --git a/ccan/tdb2/test/run-11-simple-fetch.c b/ccan/tdb2/test/run-11-simple-fetch.c deleted file mode 100644 index ad97be30..00000000 --- a/ccan/tdb2/test/run-11-simple-fetch.c +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include "tdb2-source.h" -#include -#include -#include "logging.h" -#include "failtest_helper.h" - -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, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - - failtest_init(argc, argv); - failtest_hook = block_repeat_failures; - failtest_exit_check = exit_check_log; - - failtest_suppress = true; - plan_tests(sizeof(flags) / sizeof(flags[0]) * 8 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-11-simple-fetch.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (tdb) { - struct tdb_data d = { NULL, 0 }; /* Bogus GCC warning */ - - /* fetch should fail. */ - failtest_suppress = false; - if (!ok1(tdb_fetch(tdb, key, &d) == TDB_ERR_NOEXIST)) - goto fail; - failtest_suppress = true; - ok1(tdb_check(tdb, NULL, NULL) == 0); - /* Insert should succeed. */ - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - /* Fetch should now work. */ - failtest_suppress = false; - if (!ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS)) - goto fail; - failtest_suppress = true; - ok1(tdb_deq(d, data)); - free(d.dptr); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - } - } - ok1(tap_log_messages == 0); - failtest_exit(exit_status()); - -fail: - failtest_suppress = true; - tdb_close(tdb); - failtest_exit(exit_status()); -} diff --git a/ccan/tdb2/test/run-12-check.c b/ccan/tdb2/test/run-12-check.c deleted file mode 100644 index b55bfe7d..00000000 --- a/ccan/tdb2/test/run-12-check.c +++ /dev/null @@ -1,50 +0,0 @@ -#include -#include -#include "tdb2-source.h" -#include -#include -#include "logging.h" -#include "failtest_helper.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_INTERNAL, - TDB_INTERNAL|TDB_CONVERT, - TDB_CONVERT, - TDB_INTERNAL|TDB_VERSION1, - TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - - failtest_init(argc, argv); - failtest_hook = block_repeat_failures; - failtest_exit_check = exit_check_log; - - failtest_suppress = true; - plan_tests(sizeof(flags) / sizeof(flags[0]) * 3 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-12-check.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - - /* This is what we really want to test: tdb_check(). */ - failtest_suppress = false; - if (!ok1(tdb_check(tdb, NULL, NULL) == 0)) - goto fail; - failtest_suppress = true; - - tdb_close(tdb); - } - ok1(tap_log_messages == 0); - failtest_exit(exit_status()); - -fail: - failtest_suppress = true; - tdb_close(tdb); - failtest_exit(exit_status()); -} diff --git a/ccan/tdb2/test/run-15-append.c b/ccan/tdb2/test/run-15-append.c deleted file mode 100644 index 39afaf7c..00000000 --- a/ccan/tdb2/test/run-15-append.c +++ /dev/null @@ -1,153 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include "logging.h" - -#define MAX_SIZE 13100 -#define SIZE_STEP 131 - -static tdb_off_t tdb_offset(struct tdb_context *tdb, struct tdb_data key) -{ - tdb_off_t off; - struct tdb_used_record rec; - struct hash_info h; - - if (tdb_get_flags(tdb) & TDB_VERSION1) { - struct tdb1_record rec; - return tdb1_find(tdb, key, tdb_hash(tdb, key.dptr, key.dsize), - &rec); - } - - off = find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL); - if (TDB_OFF_IS_ERR(off)) - return 0; - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK); - return off; -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j, moves; - struct tdb_context *tdb; - unsigned char *buffer; - tdb_off_t oldoff = 0, newoff; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - TDB_INTERNAL|TDB_VERSION1, TDB_VERSION1, - TDB_NOMMAP|TDB_VERSION1, - TDB_INTERNAL|TDB_CONVERT|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data; - - buffer = malloc(MAX_SIZE); - for (i = 0; i < MAX_SIZE; i++) - buffer[i] = i; - - plan_tests(sizeof(flags) / sizeof(flags[0]) - * ((3 + MAX_SIZE/SIZE_STEP * 5) * 2 + 7) - + 1); - - /* Using tdb_store. */ - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-append.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - moves = 0; - for (j = 0; j < MAX_SIZE; j += SIZE_STEP) { - data.dptr = buffer; - data.dsize = j; - ok1(tdb_store(tdb, key, data, TDB_REPLACE) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == j); - ok1(memcmp(data.dptr, buffer, data.dsize) == 0); - free(data.dptr); - newoff = tdb_offset(tdb, key); - if (newoff != oldoff) - moves++; - oldoff = newoff; - } - ok1(!tdb->file || (tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0)); - if (flags[i] & TDB_VERSION1) { - /* TDB1 simply over-size by 25%. */ - ok(moves <= ilog64(j / SIZE_STEP)*4, - "Moved %u times", moves); - } else { - /* We should increase by 50% each time... */ - ok(moves <= ilog64(j / SIZE_STEP)*2, - "Moved %u times", moves); - } - tdb_close(tdb); - } - - /* Using tdb_append. */ - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - size_t prev_len = 0; - tdb = tdb_open("run-append.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - moves = 0; - for (j = 0; j < MAX_SIZE; j += SIZE_STEP) { - data.dptr = buffer + prev_len; - data.dsize = j - prev_len; - ok1(tdb_append(tdb, key, data) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == j); - ok1(memcmp(data.dptr, buffer, data.dsize) == 0); - free(data.dptr); - prev_len = data.dsize; - newoff = tdb_offset(tdb, key); - if (newoff != oldoff) - moves++; - oldoff = newoff; - } - ok1(!tdb->file || (tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0)); - if (flags[i] & TDB_VERSION1) { - /* TDB1 simply over-size by 25%. */ - ok(moves <= ilog64(j / SIZE_STEP)*4, - "Moved %u times", moves); - } else { - /* We should increase by 50% each time... */ - ok(moves <= ilog64(j / SIZE_STEP)*2, - "Moved %u times", moves); - } - tdb_close(tdb); - } - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-append.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - /* Huge initial store. */ - data.dptr = buffer; - data.dsize = MAX_SIZE; - ok1(tdb_append(tdb, key, data) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == MAX_SIZE); - ok1(memcmp(data.dptr, buffer, data.dsize) == 0); - free(data.dptr); - ok1(!tdb->file || (tdb->file->allrecord_lock.count == 0 - && tdb->file->num_lockrecs == 0)); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - free(buffer); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-20-growhash.c b/ccan/tdb2/test/run-20-growhash.c deleted file mode 100644 index 65cead03..00000000 --- a/ccan/tdb2/test/run-20-growhash.c +++ /dev/null @@ -1,137 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -static uint64_t myhash(const void *key, size_t len, uint64_t seed, void *priv) -{ - return *(const uint64_t *)key; -} - -static void add_bits(uint64_t *val, unsigned new, unsigned new_bits, - unsigned *done) -{ - *done += new_bits; - *val |= ((uint64_t)new << (64 - *done)); -} - -static uint64_t make_key(unsigned topgroup, unsigned topbucket, - unsigned subgroup1, unsigned subbucket1, - unsigned subgroup2, unsigned subbucket2) -{ - uint64_t key = 0; - unsigned done = 0; - - add_bits(&key, topgroup, TDB_TOPLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS, - &done); - add_bits(&key, topbucket, TDB_HASH_GROUP_BITS, &done); - add_bits(&key, subgroup1, TDB_SUBLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS, - &done); - add_bits(&key, subbucket1, TDB_HASH_GROUP_BITS, &done); - add_bits(&key, subgroup2, TDB_SUBLEVEL_HASH_BITS - TDB_HASH_GROUP_BITS, - &done); - add_bits(&key, subbucket2, TDB_HASH_GROUP_BITS, &done); - return key; -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - uint64_t kdata; - struct tdb_used_record rec; - struct tdb_data key = { (unsigned char *)&kdata, sizeof(kdata) }; - struct tdb_data dbuf = { (unsigned char *)&kdata, sizeof(kdata) }; - union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, - .fn = myhash } }; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - }; - - hattr.base.next = &tap_log_attr; - - plan_tests(sizeof(flags) / sizeof(flags[0]) - * (9 + (20 + 2 * ((1 << TDB_HASH_GROUP_BITS) - 2)) - * (1 << TDB_HASH_GROUP_BITS)) + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - struct hash_info h; - - tdb = tdb_open("run-20-growhash.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr); - ok1(tdb); - if (!tdb) - continue; - - /* Fill a group. */ - for (j = 0; j < (1 << TDB_HASH_GROUP_BITS); j++) { - kdata = make_key(0, j, 0, 0, 0, 0); - ok1(tdb_store(tdb, key, dbuf, TDB_INSERT) == 0); - } - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Check first still exists. */ - kdata = make_key(0, 0, 0, 0, 0, 0); - ok1(find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL) != 0); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have located space in group 0, bucket 0. */ - ok1(h.group_start == offsetof(struct tdb_header, hashtable)); - ok1(h.home_bucket == 0); - ok1(h.found_bucket == 0); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS); - /* Entire group should be full! */ - for (j = 0; j < (1 << TDB_HASH_GROUP_BITS); j++) - ok1(h.group[j] != 0); - - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_RDLCK) == 0); - - /* Now, add one more to each should expand (that) bucket. */ - for (j = 0; j < (1 << TDB_HASH_GROUP_BITS); j++) { - unsigned int k; - kdata = make_key(0, j, 0, 1, 0, 0); - ok1(tdb_store(tdb, key, dbuf, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - ok1(find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL)); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have moved to subhash */ - ok1(h.group_start >= sizeof(struct tdb_header)); - ok1(h.home_bucket == 1); - ok1(h.found_bucket == 1); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS - + TDB_SUBLEVEL_HASH_BITS); - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_RDLCK) == 0); - - /* Keep adding, make it expand again. */ - for (k = 2; k < (1 << TDB_HASH_GROUP_BITS); k++) { - kdata = make_key(0, j, 0, k, 0, 0); - ok1(tdb_store(tdb, key, dbuf, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - } - - /* This should tip it over to sub-sub-hash. */ - kdata = make_key(0, j, 0, 0, 0, 1); - ok1(tdb_store(tdb, key, dbuf, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - ok1(find_and_lock(tdb, key, F_RDLCK, &h, &rec, NULL)); - /* Should have created correct hash. */ - ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize)); - /* Should have moved to subhash */ - ok1(h.group_start >= sizeof(struct tdb_header)); - ok1(h.home_bucket == 1); - ok1(h.found_bucket == 1); - ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS - + TDB_SUBLEVEL_HASH_BITS + TDB_SUBLEVEL_HASH_BITS); - ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, - F_RDLCK) == 0); - } - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-25-hashoverload.c b/ccan/tdb2/test/run-25-hashoverload.c deleted file mode 100644 index 0e143026..00000000 --- a/ccan/tdb2/test/run-25-hashoverload.c +++ /dev/null @@ -1,113 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -static uint64_t badhash(const void *key, size_t len, uint64_t seed, void *priv) -{ - return 0; -} - -static int trav(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, void *p) -{ - if (p) - return tdb_delete(tdb, key); - return 0; -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - struct tdb_data key = { (unsigned char *)&j, sizeof(j) }; - struct tdb_data dbuf = { (unsigned char *)&j, sizeof(j) }; - union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, - .fn = badhash } }; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT, - }; - - hattr.base.next = &tap_log_attr; - - plan_tests(6883); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - struct tdb_data d = { NULL, 0 }; /* Bogus GCC warning */ - - tdb = tdb_open("run-25-hashoverload.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr); - ok1(tdb); - if (!tdb) - continue; - - /* Fill a group. */ - for (j = 0; j < (1 << TDB_HASH_GROUP_BITS); j++) { - ok1(tdb_store(tdb, key, dbuf, TDB_INSERT) == 0); - } - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Now store one last value: should form chain. */ - ok1(tdb_store(tdb, key, dbuf, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Check we can find them all. */ - for (j = 0; j < (1 << TDB_HASH_GROUP_BITS) + 1; j++) { - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == sizeof(j)); - ok1(d.dptr != NULL); - ok1(d.dptr && memcmp(d.dptr, &j, d.dsize) == 0); - free(d.dptr); - } - - /* Now add a *lot* more. */ - for (j = (1 << TDB_HASH_GROUP_BITS) + 1; - j < (16 << TDB_HASH_GROUP_BITS); - j++) { - ok1(tdb_store(tdb, key, dbuf, TDB_INSERT) == 0); - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == sizeof(j)); - ok1(d.dptr != NULL); - ok1(d.dptr && memcmp(d.dptr, &j, d.dsize) == 0); - free(d.dptr); - } - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Traverse through them. */ - ok1(tdb_traverse(tdb, trav, NULL) == j); - - /* Empty the first chain-worth. */ - for (j = 0; j < (1 << TDB_HASH_GROUP_BITS); j++) - ok1(tdb_delete(tdb, key) == 0); - - ok1(tdb_check(tdb, NULL, NULL) == 0); - - for (j = (1 << TDB_HASH_GROUP_BITS); - j < (16 << TDB_HASH_GROUP_BITS); - j++) { - ok1(tdb_fetch(tdb, key, &d) == TDB_SUCCESS); - ok1(d.dsize == sizeof(j)); - ok1(d.dptr != NULL); - ok1(d.dptr && memcmp(d.dptr, &j, d.dsize) == 0); - free(d.dptr); - } - - /* Traverse through them. */ - ok1(tdb_traverse(tdb, trav, NULL) - == (15 << TDB_HASH_GROUP_BITS)); - - /* Re-add */ - for (j = 0; j < (1 << TDB_HASH_GROUP_BITS); j++) { - ok1(tdb_store(tdb, key, dbuf, TDB_INSERT) == 0); - } - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Now try deleting as we go. */ - ok1(tdb_traverse(tdb, trav, trav) - == (16 << TDB_HASH_GROUP_BITS)); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tdb_traverse(tdb, trav, NULL) == 0); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-30-exhaust-before-expand.c b/ccan/tdb2/test/run-30-exhaust-before-expand.c deleted file mode 100644 index db391e7e..00000000 --- a/ccan/tdb2/test/run-30-exhaust-before-expand.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include "logging.h" - -static bool empty_freetable(struct tdb_context *tdb) -{ - struct tdb_freetable ftab; - unsigned int i; - - /* Now, free table should be completely exhausted in zone 0 */ - if (tdb_read_convert(tdb, tdb->tdb2.ftable_off, &ftab, sizeof(ftab)) != 0) - abort(); - - for (i = 0; i < sizeof(ftab.buckets)/sizeof(ftab.buckets[0]); i++) { - if (ftab.buckets[i]) - return false; - } - return true; -} - - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - 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]) * 9 + 1); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - TDB_DATA k; - uint64_t size; - bool was_empty = false; - - k.dptr = (void *)&j; - k.dsize = sizeof(j); - - tdb = tdb_open("run-30-exhaust-before-expand.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - ok1(empty_freetable(tdb)); - /* Need some hash lock for expand. */ - ok1(tdb_lock_hashes(tdb, 0, 1, F_WRLCK, TDB_LOCK_WAIT) == 0); - /* Create some free space. */ - ok1(tdb_expand(tdb, 1) == 0); - ok1(tdb_unlock_hashes(tdb, 0, 1, F_WRLCK) == 0); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(!empty_freetable(tdb)); - - size = tdb->file->map_size; - /* Insert minimal-length records until we expand. */ - for (j = 0; tdb->file->map_size == size; j++) { - was_empty = empty_freetable(tdb); - if (tdb_store(tdb, k, k, TDB_INSERT) != 0) - err(1, "Failed to store record %i", j); - } - - /* Would have been empty before expansion, but no longer. */ - ok1(was_empty); - ok1(!empty_freetable(tdb)); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-35-convert.c b/ccan/tdb2/test/run-35-convert.c deleted file mode 100644 index b7b1e6e5..00000000 --- a/ccan/tdb2/test/run-35-convert.c +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include "tdb2-source.h" -#include -#include -#include "logging.h" -#include "failtest_helper.h" - -int main(int argc, char *argv[]) -{ - unsigned int i, messages = 0; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - - failtest_init(argc, argv); - failtest_hook = block_repeat_failures; - failtest_exit_check = exit_check_log; - plan_tests(sizeof(flags) / sizeof(flags[0]) * 4); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-35-convert.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - if (!ok1(tdb)) - failtest_exit(exit_status()); - - tdb_close(tdb); - /* If we say TDB_CONVERT, it must be converted */ - tdb = tdb_open("run-35-convert.tdb", - flags[i]|TDB_CONVERT, - O_RDWR, 0600, &tap_log_attr); - if (flags[i] & TDB_CONVERT) { - if (!tdb) - failtest_exit(exit_status()); - ok1(tdb_get_flags(tdb) & TDB_CONVERT); - tdb_close(tdb); - } else { - if (!ok1(!tdb && errno == EIO)) - failtest_exit(exit_status()); - ok1(tap_log_messages == ++messages); - if (!ok1(log_last && strstr(log_last, "TDB_CONVERT"))) - failtest_exit(exit_status()); - } - - /* If don't say TDB_CONVERT, it *may* be converted */ - tdb = tdb_open("run-35-convert.tdb", - flags[i] & ~TDB_CONVERT, - O_RDWR, 0600, &tap_log_attr); - if (!tdb) - failtest_exit(exit_status()); - ok1(tdb_get_flags(tdb) == flags[i]); - tdb_close(tdb); - } - failtest_exit(exit_status()); -} diff --git a/ccan/tdb2/test/run-50-multiple-freelists.c b/ccan/tdb2/test/run-50-multiple-freelists.c deleted file mode 100644 index 44fee941..00000000 --- a/ccan/tdb2/test/run-50-multiple-freelists.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" -#include "layout.h" - -int main(int argc, char *argv[]) -{ - tdb_off_t off; - struct tdb_context *tdb; - struct tdb_layout *layout; - TDB_DATA key, data; - union tdb_attribute seed; - - /* This seed value previously tickled a layout.c bug. */ - seed.base.attr = TDB_ATTRIBUTE_SEED; - seed.seed.seed = 0xb1142bc054d035b4ULL; - seed.base.next = &tap_log_attr; - - plan_tests(11); - key = tdb_mkdata("Hello", 5); - data = tdb_mkdata("world", 5); - - /* Create a TDB with three free tables. */ - layout = new_tdb_layout(); - tdb_layout_add_freetable(layout); - tdb_layout_add_freetable(layout); - tdb_layout_add_freetable(layout); - tdb_layout_add_free(layout, 80, 0); - /* Used record prevent coalescing. */ - tdb_layout_add_used(layout, key, data, 6); - tdb_layout_add_free(layout, 160, 1); - key.dsize--; - tdb_layout_add_used(layout, key, data, 7); - tdb_layout_add_free(layout, 320, 2); - key.dsize--; - tdb_layout_add_used(layout, key, data, 8); - tdb_layout_add_free(layout, 40, 0); - tdb = tdb_layout_get(layout, free, &seed); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - off = get_free(tdb, 0, 80 - sizeof(struct tdb_used_record), 0, - TDB_USED_MAGIC, 0); - ok1(off == layout->elem[3].base.off); - ok1(tdb->tdb2.ftable_off == layout->elem[0].base.off); - - off = get_free(tdb, 0, 160 - sizeof(struct tdb_used_record), 0, - TDB_USED_MAGIC, 0); - ok1(off == layout->elem[5].base.off); - ok1(tdb->tdb2.ftable_off == layout->elem[1].base.off); - - off = get_free(tdb, 0, 320 - sizeof(struct tdb_used_record), 0, - TDB_USED_MAGIC, 0); - ok1(off == layout->elem[7].base.off); - ok1(tdb->tdb2.ftable_off == layout->elem[2].base.off); - - off = get_free(tdb, 0, 40 - sizeof(struct tdb_used_record), 0, - TDB_USED_MAGIC, 0); - ok1(off == layout->elem[9].base.off); - ok1(tdb->tdb2.ftable_off == layout->elem[0].base.off); - - /* Now we fail. */ - off = get_free(tdb, 0, 0, 1, TDB_USED_MAGIC, 0); - ok1(off == 0); - - tdb_close(tdb); - tdb_layout_free(layout); - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-56-open-during-transaction.c b/ccan/tdb2/test/run-56-open-during-transaction.c deleted file mode 100644 index 9262c052..00000000 --- a/ccan/tdb2/test/run-56-open-during-transaction.c +++ /dev/null @@ -1,169 +0,0 @@ -#include -#include -#include "lock-tracking.h" - -static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset); -static ssize_t write_check(int fd, const void *buf, size_t count); -static int ftruncate_check(int fd, off_t length); - -#define pwrite pwrite_check -#define write write_check -#define fcntl fcntl_with_lockcheck -#define ftruncate ftruncate_check - -#include "tdb2-source.h" -#include -#include -#include -#include -#include -#include "external-agent.h" -#include "logging.h" - -static struct agent *agent; -static bool opened; -static int errors = 0; -#define TEST_DBNAME "run-56-open-during-transaction.tdb" - -#undef write -#undef pwrite -#undef fcntl -#undef ftruncate - -static bool is_same(const char *snapshot, const char *latest, off_t len) -{ - unsigned i; - - for (i = 0; i < len; i++) { - if (snapshot[i] != latest[i]) - return false; - } - return true; -} - -static bool compare_file(int fd, const char *snapshot, off_t snapshot_len) -{ - char *contents; - bool same; - - /* over-length read serves as length check. */ - contents = malloc(snapshot_len+1); - same = pread(fd, contents, snapshot_len+1, 0) == snapshot_len - && is_same(snapshot, contents, snapshot_len); - free(contents); - return same; -} - -static void check_file_intact(int fd) -{ - enum agent_return ret; - struct stat st; - char *contents; - - fstat(fd, &st); - contents = malloc(st.st_size); - if (pread(fd, contents, st.st_size, 0) != st.st_size) { - diag("Read fail"); - errors++; - return; - } - - /* Ask agent to open file. */ - ret = external_agent_operation(agent, OPEN, TEST_DBNAME); - - /* It's OK to open it, but it must not have changed! */ - if (!compare_file(fd, contents, st.st_size)) { - diag("Agent changed file after opening %s", - agent_return_name(ret)); - errors++; - } - - if (ret == SUCCESS) { - ret = external_agent_operation(agent, CLOSE, NULL); - if (ret != SUCCESS) { - diag("Agent failed to close tdb: %s", - agent_return_name(ret)); - errors++; - } - } else if (ret != WOULD_HAVE_BLOCKED) { - diag("Agent opening file gave %s", - agent_return_name(ret)); - errors++; - } - - free(contents); -} - -static void after_unlock(int fd) -{ - if (opened) - check_file_intact(fd); -} - -static ssize_t pwrite_check(int fd, - const void *buf, size_t count, off_t offset) -{ - if (opened) - check_file_intact(fd); - - return pwrite(fd, buf, count, offset); -} - -static ssize_t write_check(int fd, const void *buf, size_t count) -{ - if (opened) - check_file_intact(fd); - - return write(fd, buf, count); -} - -static int ftruncate_check(int fd, off_t length) -{ - if (opened) - check_file_intact(fd); - - return ftruncate(fd, length); - -} - -int main(int argc, char *argv[]) -{ - const int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - int i; - struct tdb_context *tdb; - TDB_DATA key, data; - - plan_tests(sizeof(flags)/sizeof(flags[0]) * 5); - agent = prepare_external_agent(); - if (!agent) - err(1, "preparing agent"); - - unlock_callback = after_unlock; - for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) { - diag("Test with %s and %s\n", - (flags[i] & TDB_CONVERT) ? "CONVERT" : "DEFAULT", - (flags[i] & TDB_NOMMAP) ? "no mmap" : "mmap"); - unlink(TEST_DBNAME); - tdb = tdb_open(TEST_DBNAME, flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - - opened = true; - ok1(tdb_transaction_start(tdb) == 0); - key = tdb_mkdata("hi", strlen("hi")); - data = tdb_mkdata("world", strlen("world")); - - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb_transaction_commit(tdb) == 0); - ok(!errors, "We had %u open errors", errors); - - opened = false; - tdb_close(tdb); - } - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-57-die-during-transaction.c b/ccan/tdb2/test/run-57-die-during-transaction.c deleted file mode 100644 index 42102ae7..00000000 --- a/ccan/tdb2/test/run-57-die-during-transaction.c +++ /dev/null @@ -1,296 +0,0 @@ -#include -#include -#include "lock-tracking.h" -#include -#include -#include -static ssize_t pwrite_check(int fd, const void *buf, size_t count, off_t offset); -static ssize_t write_check(int fd, const void *buf, size_t count); -static int ftruncate_check(int fd, off_t length); - -#define pwrite pwrite_check -#define write write_check -#define fcntl fcntl_with_lockcheck -#define ftruncate ftruncate_check - -/* There's a malloc inside transaction_setup_recovery, and valgrind complains - * when we longjmp and leak it. */ -#define MAX_ALLOCATIONS 10 -static void *allocated[MAX_ALLOCATIONS]; -static unsigned max_alloc = 0; - -static void *malloc_noleak(size_t len) -{ - unsigned int i; - - for (i = 0; i < MAX_ALLOCATIONS; i++) - if (!allocated[i]) { - allocated[i] = malloc(len); - if (i > max_alloc) { - max_alloc = i; - diag("max_alloc: %i", max_alloc); - } - return allocated[i]; - } - diag("Too many allocations!"); - abort(); -} - -static void *realloc_noleak(void *p, size_t size) -{ - unsigned int i; - - for (i = 0; i < MAX_ALLOCATIONS; i++) { - if (allocated[i] == p) { - if (i > max_alloc) { - max_alloc = i; - diag("max_alloc: %i", max_alloc); - } - return allocated[i] = realloc(p, size); - } - } - diag("Untracked realloc!"); - abort(); -} - -static void free_noleak(void *p) -{ - unsigned int i; - - /* We don't catch asprintf, so don't complain if we miss one. */ - for (i = 0; i < MAX_ALLOCATIONS; i++) { - if (allocated[i] == p) { - allocated[i] = NULL; - break; - } - } - free(p); -} - -static void free_all(void) -{ - unsigned int i; - - for (i = 0; i < MAX_ALLOCATIONS; i++) { - free(allocated[i]); - allocated[i] = NULL; - } -} - -#define malloc malloc_noleak -#define free free_noleak -#define realloc realloc_noleak - -#include "tdb2-source.h" - -#undef malloc -#undef free -#undef realloc -#undef write -#undef pwrite -#undef fcntl -#undef ftruncate - -#include -#include -#include -#include -#include "external-agent.h" -#include "logging.h" - -static bool in_transaction; -static int target, current; -static jmp_buf jmpbuf; -#define TEST_DBNAME "run-57-die-during-transaction.tdb" -#define KEY_STRING "helloworld" - -static void maybe_die(int fd) -{ - if (in_transaction && current++ == target) { - longjmp(jmpbuf, 1); - } -} - -static ssize_t pwrite_check(int fd, - const void *buf, size_t count, off_t offset) -{ - ssize_t ret; - - maybe_die(fd); - - ret = pwrite(fd, buf, count, offset); - if (ret != count) - return ret; - - maybe_die(fd); - return ret; -} - -static ssize_t write_check(int fd, const void *buf, size_t count) -{ - ssize_t ret; - - maybe_die(fd); - - ret = write(fd, buf, count); - if (ret != count) - return ret; - - maybe_die(fd); - return ret; -} - -static int ftruncate_check(int fd, off_t length) -{ - int ret; - - maybe_die(fd); - - ret = ftruncate(fd, length); - - maybe_die(fd); - return ret; -} - -static bool test_death(enum operation op, struct agent *agent, int flags) -{ - struct tdb_context *tdb = NULL; - TDB_DATA key; - enum agent_return ret; - int needed_recovery = 0; - - current = target = 0; -reset: - unlink(TEST_DBNAME); - tdb = tdb_open(TEST_DBNAME, flags|TDB_NOMMAP, - O_CREAT|O_TRUNC|O_RDWR, 0600, &tap_log_attr); - if (!tdb) { - diag("Failed opening TDB: %s", strerror(errno)); - return false; - } - - if (setjmp(jmpbuf) != 0) { - /* We're partway through. Simulate our death. */ - close(tdb->file->fd); - forget_locking(); - in_transaction = false; - - ret = external_agent_operation(agent, NEEDS_RECOVERY, ""); - if (ret == SUCCESS) - needed_recovery++; - else if (ret != FAILED) { - diag("Step %u agent NEEDS_RECOVERY = %s", current, - agent_return_name(ret)); - return false; - } - - ret = external_agent_operation(agent, op, KEY_STRING); - if (ret != SUCCESS) { - diag("Step %u op %s failed = %s", current, - operation_name(op), - agent_return_name(ret)); - return false; - } - - ret = external_agent_operation(agent, NEEDS_RECOVERY, ""); - if (ret != FAILED) { - diag("Still needs recovery after step %u = %s", - current, agent_return_name(ret)); - return false; - } - - ret = external_agent_operation(agent, CHECK, ""); - if (ret != SUCCESS) { - diag("Step %u check failed = %s", current, - agent_return_name(ret)); - return false; - } - - ret = external_agent_operation(agent, CLOSE, ""); - if (ret != SUCCESS) { - diag("Step %u close failed = %s", current, - agent_return_name(ret)); - return false; - } - - /* Suppress logging as this tries to use closed fd. */ - suppress_logging = true; - suppress_lockcheck = true; - tdb_close(tdb); - suppress_logging = false; - suppress_lockcheck = false; - target++; - current = 0; - free_all(); - goto reset; - } - - /* Put key for agent to fetch. */ - key = tdb_mkdata(KEY_STRING, strlen(KEY_STRING)); - if (tdb_store(tdb, key, key, TDB_INSERT) != 0) - return false; - - /* This is the key we insert in transaction. */ - key.dsize--; - - ret = external_agent_operation(agent, OPEN, TEST_DBNAME); - if (ret != SUCCESS) - errx(1, "Agent failed to open: %s", agent_return_name(ret)); - - ret = external_agent_operation(agent, FETCH, KEY_STRING); - if (ret != SUCCESS) - errx(1, "Agent failed find key: %s", agent_return_name(ret)); - - in_transaction = true; - if (tdb_transaction_start(tdb) != 0) - return false; - - if (tdb_store(tdb, key, key, TDB_INSERT) != 0) - return false; - - if (tdb_transaction_commit(tdb) != 0) - return false; - - in_transaction = false; - - /* We made it! */ - diag("Completed %u runs", current); - tdb_close(tdb); - ret = external_agent_operation(agent, CLOSE, ""); - if (ret != SUCCESS) { - diag("Step %u close failed = %s", current, - agent_return_name(ret)); - return false; - } - - ok1(needed_recovery); - ok1(locking_errors == 0); - ok1(forget_locking() == 0); - locking_errors = 0; - return true; -} - -int main(int argc, char *argv[]) -{ - enum operation ops[] = { FETCH, STORE, TRANSACTION_START }; - struct agent *agent; - int i, flags; - - plan_tests(24); - unlock_callback = maybe_die; - - external_agent_free = free_noleak; - agent = prepare_external_agent(); - if (!agent) - err(1, "preparing agent"); - - for (flags = TDB_DEFAULT; flags <= TDB_VERSION1; flags += TDB_VERSION1) { - for (i = 0; i < sizeof(ops)/sizeof(ops[0]); i++) { - diag("Testing %s after death", operation_name(ops[i])); - ok1(test_death(ops[i], agent, flags)); - } - } - - free_external_agent(agent); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-64-bit-tdb.c b/ccan/tdb2/test/run-64-bit-tdb.c deleted file mode 100644 index 566f5465..00000000 --- a/ccan/tdb2/test/run-64-bit-tdb.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT }; - - if (sizeof(off_t) <= 4) { - plan_tests(1); - pass("No 64 bit off_t"); - return exit_status(); - } - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 14); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - off_t old_size; - TDB_DATA k, d; - struct hash_info h; - struct tdb_used_record rec; - tdb_off_t off; - - tdb = tdb_open("run-64-bit-tdb.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - old_size = tdb->file->map_size; - - /* This makes a sparse file */ - ok1(ftruncate(tdb->file->fd, 0xFFFFFFF0) == 0); - ok1(add_free_record(tdb, old_size, 0xFFFFFFF0 - old_size, - TDB_LOCK_WAIT, false) == TDB_SUCCESS); - - /* Now add a little record past the 4G barrier. */ - ok1(tdb_expand_file(tdb, 100) == TDB_SUCCESS); - ok1(add_free_record(tdb, 0xFFFFFFF0, 100, TDB_LOCK_WAIT, false) - == TDB_SUCCESS); - - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - - /* Test allocation path. */ - k = tdb_mkdata("key", 4); - d = tdb_mkdata("data", 5); - ok1(tdb_store(tdb, k, d, TDB_INSERT) == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - - /* Make sure it put it at end as we expected. */ - off = find_and_lock(tdb, k, F_RDLCK, &h, &rec, NULL); - ok1(off >= 0xFFFFFFF0); - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK); - - ok1(tdb_fetch(tdb, k, &d) == 0); - ok1(d.dsize == 5); - ok1(strcmp((char *)d.dptr, "data") == 0); - free(d.dptr); - - ok1(tdb_delete(tdb, k) == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - - tdb_close(tdb); - } - - /* We might get messages about mmap failing, so don't test - * tap_log_messages */ - return exit_status(); -} diff --git a/ccan/tdb2/test/run-90-get-set-attributes.c b/ccan/tdb2/test/run-90-get-set-attributes.c deleted file mode 100644 index 4cbbda03..00000000 --- a/ccan/tdb2/test/run-90-get-set-attributes.c +++ /dev/null @@ -1,186 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -static int mylock(int fd, int rw, off_t off, off_t len, bool waitflag, - void *unused) -{ - return 0; -} - -static int myunlock(int fd, int rw, off_t off, off_t len, void *unused) -{ - return 0; -} - -static uint64_t hash_fn(const void *key, size_t len, uint64_t seed, - void *priv) -{ - return 0; -} - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - union tdb_attribute seed_attr; - union tdb_attribute hash_attr; - union tdb_attribute lock_attr; - - seed_attr.base.attr = TDB_ATTRIBUTE_SEED; - seed_attr.base.next = &hash_attr; - seed_attr.seed.seed = 100; - - hash_attr.base.attr = TDB_ATTRIBUTE_HASH; - hash_attr.base.next = &lock_attr; - hash_attr.hash.fn = hash_fn; - hash_attr.hash.data = &hash_attr; - - lock_attr.base.attr = TDB_ATTRIBUTE_FLOCK; - lock_attr.base.next = &tap_log_attr; - lock_attr.flock.lock = mylock; - lock_attr.flock.unlock = myunlock; - lock_attr.flock.data = &lock_attr; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 49 - + sizeof(flags) / sizeof(flags[0]) / 2); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - union tdb_attribute attr; - - /* First open with no attributes. */ - tdb = tdb_open("run-90-get-set-attributes.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, NULL); - ok1(tdb); - - /* Get log on no attributes will fail */ - attr.base.attr = TDB_ATTRIBUTE_LOG; - ok1(tdb_get_attribute(tdb, &attr) == TDB_ERR_NOEXIST); - /* These always work. */ - attr.base.attr = TDB_ATTRIBUTE_HASH; - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_HASH); - if (flags[i] & TDB_VERSION1) { - ok1(attr.hash.fn == tdb1_old_hash); - } else { - ok1(attr.hash.fn == tdb_jenkins_hash); - } - attr.base.attr = TDB_ATTRIBUTE_FLOCK; - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_FLOCK); - ok1(attr.flock.lock == tdb_fcntl_lock); - ok1(attr.flock.unlock == tdb_fcntl_unlock); - attr.base.attr = TDB_ATTRIBUTE_SEED; - if (flags[i] & TDB_VERSION1) { - ok1(tdb_get_attribute(tdb, &attr) == TDB_ERR_EINVAL); - tap_log_messages = 0; - } else { - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_SEED); - /* This is possible, just astronomically unlikely. */ - ok1(attr.seed.seed != 0); - } - - /* Unset attributes. */ - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_LOG); - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_FLOCK); - - /* Set them. */ - ok1(tdb_set_attribute(tdb, &tap_log_attr) == 0); - ok1(tdb_set_attribute(tdb, &lock_attr) == 0); - /* These should fail. */ - ok1(tdb_set_attribute(tdb, &seed_attr) == TDB_ERR_EINVAL); - ok1(tap_log_messages == 1); - ok1(tdb_set_attribute(tdb, &hash_attr) == TDB_ERR_EINVAL); - ok1(tap_log_messages == 2); - tap_log_messages = 0; - - /* Getting them should work as expected. */ - attr.base.attr = TDB_ATTRIBUTE_LOG; - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_LOG); - ok1(attr.log.fn == tap_log_attr.log.fn); - ok1(attr.log.data == tap_log_attr.log.data); - - attr.base.attr = TDB_ATTRIBUTE_FLOCK; - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_FLOCK); - ok1(attr.flock.lock == mylock); - ok1(attr.flock.unlock == myunlock); - ok1(attr.flock.data == &lock_attr); - - /* Unset them again. */ - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_FLOCK); - ok1(tap_log_messages == 0); - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_LOG); - ok1(tap_log_messages == 0); - - tdb_close(tdb); - ok1(tap_log_messages == 0); - - /* Now open with all attributes. */ - tdb = tdb_open("run-90-get-set-attributes.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, - &seed_attr); - - if (flags[i] & TDB_VERSION1) { - ok1(!tdb); - ok1(tap_log_messages == 1); - tap_log_messages = 0; - tdb = tdb_open("run-90-get-set-attributes.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, - &hash_attr); - } - ok1(tdb); - - /* Get will succeed */ - attr.base.attr = TDB_ATTRIBUTE_LOG; - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_LOG); - ok1(attr.log.fn == tap_log_attr.log.fn); - ok1(attr.log.data == tap_log_attr.log.data); - - attr.base.attr = TDB_ATTRIBUTE_HASH; - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_HASH); - ok1(attr.hash.fn == hash_fn); - ok1(attr.hash.data == &hash_attr); - - attr.base.attr = TDB_ATTRIBUTE_FLOCK; - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_FLOCK); - ok1(attr.flock.lock == mylock); - ok1(attr.flock.unlock == myunlock); - ok1(attr.flock.data == &lock_attr); - - attr.base.attr = TDB_ATTRIBUTE_SEED; - if (flags[i] & TDB_VERSION1) { - ok1(tdb_get_attribute(tdb, &attr) == TDB_ERR_EINVAL); - ok1(tap_log_messages == 1); - tap_log_messages = 0; - } else { - ok1(tdb_get_attribute(tdb, &attr) == 0); - ok1(attr.base.attr == TDB_ATTRIBUTE_SEED); - ok1(attr.seed.seed == seed_attr.seed.seed); - } - - /* Unset attributes. */ - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_HASH); - ok1(tap_log_messages == 1); - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_SEED); - ok1(tap_log_messages == 2); - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_FLOCK); - tdb_unset_attribute(tdb, TDB_ATTRIBUTE_LOG); - ok1(tap_log_messages == 2); - tap_log_messages = 0; - - tdb_close(tdb); - - } - return exit_status(); -} diff --git a/ccan/tdb2/test/run-capabilities.c b/ccan/tdb2/test/run-capabilities.c deleted file mode 100644 index 4b25f9c5..00000000 --- a/ccan/tdb2/test/run-capabilities.c +++ /dev/null @@ -1,272 +0,0 @@ -#include -#include "tdb2-source.h" -#include -#include "logging.h" -#include "layout.h" -#include "failtest_helper.h" -#include -#include - -static size_t len_of(bool breaks_check, bool breaks_write, bool breaks_open) -{ - size_t len = 0; - if (breaks_check) - len += 8; - if (breaks_write) - len += 16; - if (breaks_open) - len += 32; - return len; -} - -/* Creates a TDB with various capabilities. */ -static void create_tdb(const char *name, - unsigned int cap, - bool breaks_check, - bool breaks_write, - bool breaks_open, ...) -{ - TDB_DATA key, data; - va_list ap; - struct tdb_layout *layout; - struct tdb_context *tdb; - int fd; - - key = tdb_mkdata("Hello", 5); - data = tdb_mkdata("world", 5); - - /* Create a TDB with some data, and some capabilities */ - layout = new_tdb_layout(); - tdb_layout_add_freetable(layout); - tdb_layout_add_used(layout, key, data, 6); - tdb_layout_add_free(layout, 80, 0); - tdb_layout_add_capability(layout, cap, - breaks_write, breaks_check, breaks_open, - len_of(breaks_check, breaks_write, breaks_open)); - - va_start(ap, breaks_open); - while ((cap = va_arg(ap, int)) != 0) { - breaks_check = va_arg(ap, int); - breaks_write = va_arg(ap, int); - breaks_open = va_arg(ap, int); - - key.dsize--; - tdb_layout_add_used(layout, key, data, 11 - key.dsize); - tdb_layout_add_free(layout, 80, 0); - tdb_layout_add_capability(layout, cap, - breaks_write, breaks_check, - breaks_open, - len_of(breaks_check, breaks_write, - breaks_open)); - } - va_end(ap); - - /* We open-code this, because we need to use the failtest write. */ - tdb = tdb_layout_get(layout, failtest_free, &tap_log_attr); - - fd = open(name, O_RDWR|O_TRUNC|O_CREAT, 0600); - if (fd < 0) - err(1, "opening %s for writing", name); - if (write(fd, tdb->file->map_ptr, tdb->file->map_size) - != tdb->file->map_size) - err(1, "writing %s", name); - close(fd); - tdb_close(tdb); - tdb_layout_free(layout); -} - -/* Note all the "goto out" early exits: they're to shorten failtest time. */ -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - char *summary; - - failtest_init(argc, argv); - failtest_hook = block_repeat_failures; - failtest_exit_check = exit_check_log; - plan_tests(60); - - failtest_suppress = true; - /* Capability says you can ignore it? */ - create_tdb("run-capabilities.tdb", 1, false, false, false, 0); - - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0, - &tap_log_attr); - failtest_suppress = true; - if (!ok1(tdb)) - goto out; - ok1(tap_log_messages == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - ok1(tap_log_messages == 0); - tdb_close(tdb); - - /* Two capabilitues say you can ignore them? */ - create_tdb("run-capabilities.tdb", - 1, false, false, false, - 2, false, false, false, 0); - - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0, - &tap_log_attr); - failtest_suppress = true; - if (!ok1(tdb)) - goto out; - ok1(tap_log_messages == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - ok1(tap_log_messages == 0); - ok1(tdb_summary(tdb, 0, &summary) == TDB_SUCCESS); - ok1(strstr(summary, "Capability 1\n")); - free(summary); - tdb_close(tdb); - - /* Capability says you can't check. */ - create_tdb("run-capabilities.tdb", - 1, false, false, false, - 2, true, false, false, 0); - - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0, - &tap_log_attr); - failtest_suppress = true; - if (!ok1(tdb)) - goto out; - ok1(tap_log_messages == 0); - ok1(tdb_get_flags(tdb) & TDB_CANT_CHECK); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - /* We expect a warning! */ - ok1(tap_log_messages == 1); - ok1(strstr(log_last, "capabilit")); - ok1(tdb_summary(tdb, 0, &summary) == TDB_SUCCESS); - ok1(strstr(summary, "Capability 1\n")); - ok1(strstr(summary, "Capability 2 (uncheckable)\n")); - free(summary); - tdb_close(tdb); - - /* Capability says you can't write. */ - create_tdb("run-capabilities.tdb", - 1, false, false, false, - 2, false, true, false, 0); - - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0, - &tap_log_attr); - failtest_suppress = true; - /* We expect a message. */ - ok1(!tdb); - if (!ok1(tap_log_messages == 2)) - goto out; - if (!ok1(strstr(log_last, "unknown"))) - goto out; - ok1(strstr(log_last, "write")); - - /* We can open it read-only though! */ - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDONLY, 0, - &tap_log_attr); - failtest_suppress = true; - if (!ok1(tdb)) - goto out; - ok1(tap_log_messages == 2); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - ok1(tap_log_messages == 2); - ok1(tdb_summary(tdb, 0, &summary) == TDB_SUCCESS); - ok1(strstr(summary, "Capability 1\n")); - ok1(strstr(summary, "Capability 2 (read-only)\n")); - free(summary); - tdb_close(tdb); - - /* Capability says you can't open. */ - create_tdb("run-capabilities.tdb", - 1, false, false, false, - 2, false, false, true, 0); - - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0, - &tap_log_attr); - failtest_suppress = true; - /* We expect a message. */ - ok1(!tdb); - if (!ok1(tap_log_messages == 3)) - goto out; - if (!ok1(strstr(log_last, "unknown"))) - goto out; - - /* Combine capabilities correctly. */ - create_tdb("run-capabilities.tdb", - 1, false, false, false, - 2, true, false, false, - 3, false, true, false, 0); - - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0, - &tap_log_attr); - failtest_suppress = true; - /* We expect a message. */ - ok1(!tdb); - if (!ok1(tap_log_messages == 4)) - goto out; - if (!ok1(strstr(log_last, "unknown"))) - goto out; - ok1(strstr(log_last, "write")); - - /* We can open it read-only though! */ - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDONLY, 0, - &tap_log_attr); - failtest_suppress = true; - if (!ok1(tdb)) - goto out; - ok1(tap_log_messages == 4); - ok1(tdb_get_flags(tdb) & TDB_CANT_CHECK); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - /* We expect a warning! */ - ok1(tap_log_messages == 5); - ok1(strstr(log_last, "unknown")); - ok1(tdb_summary(tdb, 0, &summary) == TDB_SUCCESS); - ok1(strstr(summary, "Capability 1\n")); - ok1(strstr(summary, "Capability 2 (uncheckable)\n")); - ok1(strstr(summary, "Capability 3 (read-only)\n")); - free(summary); - tdb_close(tdb); - - /* Two capability flags in one. */ - create_tdb("run-capabilities.tdb", - 1, false, false, false, - 2, true, true, false, - 0); - - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDWR, 0, - &tap_log_attr); - failtest_suppress = true; - /* We expect a message. */ - ok1(!tdb); - if (!ok1(tap_log_messages == 6)) - goto out; - if (!ok1(strstr(log_last, "unknown"))) - goto out; - ok1(strstr(log_last, "write")); - - /* We can open it read-only though! */ - failtest_suppress = false; - tdb = tdb_open("run-capabilities.tdb", TDB_DEFAULT, O_RDONLY, 0, - &tap_log_attr); - failtest_suppress = true; - if (!ok1(tdb)) - goto out; - ok1(tap_log_messages == 6); - ok1(tdb_get_flags(tdb) & TDB_CANT_CHECK); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - /* We expect a warning! */ - ok1(tap_log_messages == 7); - ok1(strstr(log_last, "unknown")); - ok1(tdb_summary(tdb, 0, &summary) == TDB_SUCCESS); - ok1(strstr(summary, "Capability 1\n")); - ok1(strstr(summary, "Capability 2 (uncheckable,read-only)\n")); - free(summary); - tdb_close(tdb); - -out: - failtest_exit(exit_status()); -} diff --git a/ccan/tdb2/test/run-expand-in-transaction.c b/ccan/tdb2/test/run-expand-in-transaction.c deleted file mode 100644 index 0fa2a57f..00000000 --- a/ccan/tdb2/test/run-expand-in-transaction.c +++ /dev/null @@ -1,39 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - struct tdb_data key = tdb_mkdata("key", 3); - struct tdb_data data = tdb_mkdata("data", 4); - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 7 + 1); - - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - size_t size; - tdb = tdb_open("run-expand-in-transaction.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - size = tdb->file->map_size; - ok1(tdb_transaction_start(tdb) == 0); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == 0); - ok1(tdb->file->map_size > size); - ok1(tdb_transaction_commit(tdb) == 0); - ok1(tdb->file->map_size > size); - ok1(tdb_check(tdb, NULL, NULL) == 0); - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-features.c b/ccan/tdb2/test/run-features.c deleted file mode 100644 index b086869d..00000000 --- a/ccan/tdb2/test/run-features.c +++ /dev/null @@ -1,64 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT }; - struct tdb_data key = { (unsigned char *)&j, sizeof(j) }; - struct tdb_data data = { (unsigned char *)&j, sizeof(j) }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 8 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - uint64_t features; - tdb = tdb_open("run-features.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - /* Put some stuff in there. */ - for (j = 0; j < 100; j++) { - if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) - fail("Storing in tdb"); - } - - /* Mess with features fields in hdr. */ - features = (~TDB_FEATURE_MASK ^ 1); - ok1(tdb_write_convert(tdb, offsetof(struct tdb_header, - features_used), - &features, sizeof(features)) == 0); - ok1(tdb_write_convert(tdb, offsetof(struct tdb_header, - features_offered), - &features, sizeof(features)) == 0); - tdb_close(tdb); - - tdb = tdb_open("run-features.tdb", flags[i], O_RDWR, 0, - &tap_log_attr); - ok1(tdb); - if (!tdb) - continue; - - /* Should not have changed features offered. */ - ok1(tdb_read_convert(tdb, offsetof(struct tdb_header, - features_offered), - &features, sizeof(features)) == 0); - ok1(features == (~TDB_FEATURE_MASK ^ 1)); - - /* Should have cleared unknown bits in features_used. */ - ok1(tdb_read_convert(tdb, offsetof(struct tdb_header, - features_used), - &features, sizeof(features)) == 0); - ok1(features == (1 & TDB_FEATURE_MASK)); - - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} - - diff --git a/ccan/tdb2/test/run-lockall.c b/ccan/tdb2/test/run-lockall.c deleted file mode 100644 index 7cd9b849..00000000 --- a/ccan/tdb2/test/run-lockall.c +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include "lock-tracking.h" - -#define fcntl fcntl_with_lockcheck -#include "tdb2-source.h" - -#include -#include -#include -#include -#include -#include "external-agent.h" -#include "logging.h" - -#define TEST_DBNAME "run-lockall.tdb" - -#undef fcntl - -int main(int argc, char *argv[]) -{ - struct agent *agent; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT, - TDB_VERSION1, TDB_NOMMAP|TDB_VERSION1, - TDB_CONVERT|TDB_VERSION1, - TDB_NOMMAP|TDB_CONVERT|TDB_VERSION1 }; - int i; - - plan_tests(13 * sizeof(flags)/sizeof(flags[0]) + 1); - agent = prepare_external_agent(); - if (!agent) - err(1, "preparing agent"); - - for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) { - enum agent_return ret; - struct tdb_context *tdb; - - tdb = tdb_open(TEST_DBNAME, flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - - ret = external_agent_operation(agent, OPEN, TEST_DBNAME); - ok1(ret == SUCCESS); - - ok1(tdb_lockall(tdb) == TDB_SUCCESS); - ok1(external_agent_operation(agent, STORE, "key") - == WOULD_HAVE_BLOCKED); - ok1(external_agent_operation(agent, FETCH, "key") - == WOULD_HAVE_BLOCKED); - /* Test nesting. */ - ok1(tdb_lockall(tdb) == TDB_SUCCESS); - tdb_unlockall(tdb); - tdb_unlockall(tdb); - - ok1(external_agent_operation(agent, STORE, "key") == SUCCESS); - - ok1(tdb_lockall_read(tdb) == TDB_SUCCESS); - ok1(external_agent_operation(agent, STORE, "key") - == WOULD_HAVE_BLOCKED); - ok1(external_agent_operation(agent, FETCH, "key") == SUCCESS); - ok1(tdb_lockall_read(tdb) == TDB_SUCCESS); - tdb_unlockall_read(tdb); - tdb_unlockall_read(tdb); - - ok1(external_agent_operation(agent, STORE, "key") == SUCCESS); - ok1(external_agent_operation(agent, CLOSE, NULL) == SUCCESS); - tdb_close(tdb); - } - - free_external_agent(agent); - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-remap-in-read_traverse.c b/ccan/tdb2/test/run-remap-in-read_traverse.c deleted file mode 100644 index b70a841a..00000000 --- a/ccan/tdb2/test/run-remap-in-read_traverse.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "tdb2-source.h" -/* We had a bug where we marked the tdb read-only for a tdb_traverse_read. - * If we then expanded the tdb, we would remap read-only, and later SEGV. */ -#include -#include "external-agent.h" -#include "logging.h" - -static bool file_larger(int fd, tdb_len_t size) -{ - struct stat st; - - fstat(fd, &st); - return st.st_size != size; -} - -static unsigned add_records_to_grow(struct agent *agent, int fd, tdb_len_t size) -{ - unsigned int i; - - for (i = 0; !file_larger(fd, size); i++) { - char data[20]; - sprintf(data, "%i", i); - if (external_agent_operation(agent, STORE, data) != SUCCESS) - return 0; - } - diag("Added %u records to grow file", i); - return i; -} - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct agent *agent; - struct tdb_context *tdb; - struct tdb_data d = tdb_mkdata("hello", 5); - const char filename[] = "run-remap-in-read_traverse.tdb"; - - plan_tests(4); - - agent = prepare_external_agent(); - - tdb = tdb_open(filename, TDB_DEFAULT, - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - - ok1(external_agent_operation(agent, OPEN, filename) == SUCCESS); - i = add_records_to_grow(agent, tdb->file->fd, tdb->file->map_size); - - /* Do a traverse. */ - ok1(tdb_traverse(tdb, NULL, NULL) == i); - - /* Now store something! */ - ok1(tdb_store(tdb, d, d, TDB_INSERT) == 0); - ok1(tap_log_messages == 0); - tdb_close(tdb); - free_external_agent(agent); - return exit_status(); -} diff --git a/ccan/tdb2/test/run-seed.c b/ccan/tdb2/test/run-seed.c deleted file mode 100644 index 09193964..00000000 --- a/ccan/tdb2/test/run-seed.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -static int log_count = 0; - -/* Normally we get a log when setting random seed. */ -static void my_log_fn(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, void *priv) -{ - log_count++; -} - -static union tdb_attribute log_attr = { - .log = { .base = { .attr = TDB_ATTRIBUTE_LOG }, - .fn = my_log_fn } -}; - -int main(int argc, char *argv[]) -{ - unsigned int i; - struct tdb_context *tdb; - union tdb_attribute attr; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT }; - - attr.seed.base.attr = TDB_ATTRIBUTE_SEED; - attr.seed.base.next = &log_attr; - attr.seed.seed = 42; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 4 + 4 * 3); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - struct tdb_header hdr; - int fd; - tdb = tdb_open("run-seed.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &attr); - ok1(tdb); - if (!tdb) - continue; - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tdb->hash_seed == 42); - ok1(log_count == 0); - tdb_close(tdb); - - if (flags[i] & TDB_INTERNAL) - continue; - - fd = open("run-seed.tdb", O_RDONLY); - ok1(fd >= 0); - ok1(read(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); - if (flags[i] & TDB_CONVERT) - ok1(bswap_64(hdr.hash_seed) == 42); - else - ok1(hdr.hash_seed == 42); - close(fd); - } - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-3G-file.c b/ccan/tdb2/test/run-tdb1-3G-file.c deleted file mode 100644 index f3403dd1..00000000 --- a/ccan/tdb2/test/run-tdb1-3G-file.c +++ /dev/null @@ -1,125 +0,0 @@ -/* We need this otherwise fcntl locking fails. */ -#define _FILE_OFFSET_BITS 64 -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -static int tdb1_expand_file_sparse(struct tdb_context *tdb, - tdb1_off_t size, - tdb1_off_t addition) -{ - if ((tdb->flags & TDB_RDONLY) || tdb->tdb1.traverse_read) { - tdb->last_error = TDB_ERR_RDONLY; - return -1; - } - - if (ftruncate(tdb->file->fd, size+addition) == -1) { - char b = 0; - ssize_t written = pwrite(tdb->file->fd, &b, 1, (size+addition) - 1); - if (written == 0) { - /* try once more, potentially revealing errno */ - written = pwrite(tdb->file->fd, &b, 1, (size+addition) - 1); - } - if (written == 0) { - /* again - give up, guessing errno */ - errno = ENOSPC; - } - if (written != 1) { - tdb->last_error = tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "expand_file to %d failed (%s)", - size+addition, - strerror(errno)); - return -1; - } - } - - return 0; -} - -static const struct tdb1_methods large_io_methods = { - tdb1_read, - tdb1_write, - tdb1_next_hash_chain, - tdb1_oob, - tdb1_expand_file_sparse -}; - -static int test_traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, - void *_data) -{ - TDB_DATA *expect = _data; - ok1(key.dsize == strlen("hi")); - ok1(memcmp(key.dptr, "hi", strlen("hi")) == 0); - ok1(data.dsize == expect->dsize); - ok1(memcmp(data.dptr, expect->dptr, data.dsize) == 0); - return 0; -} - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, orig_data, data; - uint32_t hash; - tdb1_off_t rec_ptr; - struct tdb1_record rec; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(26); - tdb = tdb_open("run-36-file.tdb1", TDB_VERSION1, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - - ok1(tdb); - tdb->tdb1.io = &large_io_methods; - - /* Enlarge the file (internally multiplies by 2). */ - ok1(tdb1_expand(tdb, 1500000000) == 0); - - /* Put an entry in, and check it. */ - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - orig_data.dsize = strlen("world"); - orig_data.dptr = (void *)"world"; - - ok1(tdb_store(tdb, key, orig_data, TDB_INSERT) == TDB_SUCCESS); - - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == strlen("world")); - ok1(memcmp(data.dptr, "world", strlen("world")) == 0); - free(data.dptr); - - /* That currently fills at the end, make sure that's true. */ - hash = tdb_hash(tdb, key.dptr, key.dsize); - rec_ptr = tdb1_find_lock_hash(tdb, key, hash, F_RDLCK, &rec); - ok1(rec_ptr); - ok1(rec_ptr > 2U*1024*1024*1024); - tdb1_unlock(tdb, TDB1_BUCKET(rec.full_hash), F_RDLCK); - - /* Traverse must work. */ - ok1(tdb_traverse(tdb, test_traverse, &orig_data) == 1); - - /* Delete should work. */ - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - - ok1(tdb_traverse(tdb, test_traverse, NULL) == 0); - - /* Transactions should work. */ - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_store(tdb, key, orig_data, TDB_INSERT) == TDB_SUCCESS); - - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == strlen("world")); - ok1(memcmp(data.dptr, "world", strlen("world")) == 0); - free(data.dptr); - ok1(tdb_transaction_commit(tdb) == TDB_SUCCESS); - - ok1(tdb_traverse(tdb, test_traverse, &orig_data) == 1); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-bad-tdb-header.c b/ccan/tdb2/test/run-tdb1-bad-tdb-header.c deleted file mode 100644 index c1d76740..00000000 --- a/ccan/tdb2/test/run-tdb1-bad-tdb-header.c +++ /dev/null @@ -1,52 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - struct tdb1_header hdr; - int fd; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(11); - /* Cannot open fine if complete crap, even with O_CREAT. */ - fd = open("run-bad-tdb-header.tdb1", O_RDWR|O_CREAT|O_TRUNC, 0600); - ok1(fd >= 0); - ok1(write(fd, "hello world", 11) == 11); - close(fd); - tdb = tdb_open("run-bad-tdb-header.tdb1", 0, O_RDWR, 0, &tap_log_attr); - ok1(!tdb); - tdb = tdb_open("run-bad-tdb-header.tdb1", 0, O_CREAT|O_RDWR, - 0600, &hsize); - ok1(!tdb); - - /* With truncate, will be fine. */ - tdb = tdb_open("run-bad-tdb-header.tdb1", TDB_VERSION1, - O_RDWR|O_CREAT|O_TRUNC, 0600, &hsize); - ok1(tdb); - tdb_close(tdb); - - /* Now, with wrong version it should *not* overwrite. */ - fd = open("run-bad-tdb-header.tdb1", O_RDWR); - ok1(fd >= 0); - ok1(read(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); - ok1(hdr.version == TDB1_VERSION); - hdr.version++; - lseek(fd, 0, SEEK_SET); - ok1(write(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); - close(fd); - - tdb = tdb_open("run-bad-tdb-header.tdb1", TDB_VERSION1, O_RDWR|O_CREAT, - 0600, &hsize); - ok1(errno == EIO); - ok1(!tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-check.c b/ccan/tdb2/test/run-tdb1-check.c deleted file mode 100644 index e939d040..00000000 --- a/ccan/tdb2/test/run-tdb1-check.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, data; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1; - - plan_tests(13); - tdb = tdb_open("run-check.tdb1", TDB_VERSION1, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - data.dsize = strlen("world"); - data.dptr = (void *)"world"; - - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - tdb = tdb_open("run-check.tdb1", TDB_VERSION1, O_RDWR, 0, &tap_log_attr); - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - tdb = tdb_open("test/tdb1.corrupt", TDB_VERSION1, O_RDWR, 0, - &tap_log_attr); - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_ERR_CORRUPT); - ok1(tdb_error(tdb) == TDB_ERR_CORRUPT); - tdb_close(tdb); - - /* Big and little endian should work! */ - tdb = tdb_open("test/old-nohash-le.tdb1", TDB_VERSION1, O_RDWR, 0, - &tap_log_attr); - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - tdb = tdb_open("test/old-nohash-be.tdb1", TDB_VERSION1, O_RDWR, 0, - &tap_log_attr); - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-corrupt.c b/ccan/tdb2/test/run-tdb1-corrupt.c deleted file mode 100644 index 35bc4c3f..00000000 --- a/ccan/tdb2/test/run-tdb1-corrupt.c +++ /dev/null @@ -1,123 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -static int check(TDB_DATA key, TDB_DATA data, void *private) -{ - unsigned int *sizes = private; - - if (key.dsize > strlen("hello")) - return -1; - if (memcmp(key.dptr, "hello", key.dsize) != 0) - return -1; - - if (data.dsize != strlen("world")) - return -1; - if (memcmp(data.dptr, "world", data.dsize) != 0) - return -1; - - sizes[0] += key.dsize; - sizes[1] += data.dsize; - return 0; -} - -static void tdb1_flip_bit(struct tdb_context *tdb, unsigned int bit) -{ - unsigned int off = bit / CHAR_BIT; - unsigned char mask = (1 << (bit % CHAR_BIT)); - - if (tdb->file->map_ptr) - ((unsigned char *)tdb->file->map_ptr)[off] ^= mask; - else { - unsigned char c; - if (pread(tdb->file->fd, &c, 1, off) != 1) - err(1, "pread"); - c ^= mask; - if (pwrite(tdb->file->fd, &c, 1, off) != 1) - err(1, "pwrite"); - } -} - -static void check_test(struct tdb_context *tdb) -{ - TDB_DATA key, data; - unsigned int i, verifiable, corrupt, sizes[2], dsize, ksize; - - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - - key.dptr = (void *)"hello"; - data.dsize = strlen("world"); - data.dptr = (void *)"world"; - - /* Key and data size respectively. */ - dsize = ksize = 0; - - /* 5 keys in hash size 2 means we'll have multichains. */ - for (key.dsize = 1; key.dsize <= 5; key.dsize++) { - ksize += key.dsize; - dsize += data.dsize; - if (tdb_store(tdb, key, data, TDB_INSERT) != TDB_SUCCESS) - abort(); - } - - /* This is how many bytes we expect to be verifiable. */ - /* From the file header. */ - verifiable = strlen(TDB_MAGIC_FOOD) + 1 - + 2 * sizeof(uint32_t) + 2 * sizeof(tdb1_off_t) - + 2 * sizeof(uint32_t); - /* From the free list chain and hash chains. */ - verifiable += 3 * sizeof(tdb1_off_t); - /* From the record headers & tailer */ - verifiable += 5 * (sizeof(struct tdb1_record) + sizeof(uint32_t)); - /* The free block: we ignore datalen, keylen, full_hash. */ - verifiable += sizeof(struct tdb1_record) - 3*sizeof(uint32_t) + - sizeof(uint32_t); - /* Our check function verifies the key and data. */ - verifiable += ksize + dsize; - - /* Flip one bit at a time, make sure it detects verifiable bytes. */ - for (i = 0, corrupt = 0; i < tdb->file->map_size * CHAR_BIT; i++) { - tdb1_flip_bit(tdb, i); - memset(sizes, 0, sizeof(sizes)); - if (tdb_check(tdb, check, sizes) == TDB_ERR_CORRUPT) - corrupt++; - else if (sizes[0] != ksize || sizes[1] != dsize) - corrupt++; - tdb1_flip_bit(tdb, i); - } - ok(corrupt == verifiable * CHAR_BIT, "corrupt %u should be %u", - corrupt, verifiable * CHAR_BIT); -} - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 2; - - plan_tests(4); - /* This should use mmap. */ - tdb = tdb_open("run-corrupt.tdb1", TDB_VERSION1, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - - if (!tdb) - abort(); - check_test(tdb); - tdb_close(tdb); - - /* This should not. */ - tdb = tdb_open("run-corrupt.tdb1", TDB_VERSION1|TDB_NOMMAP, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - - if (!tdb) - abort(); - check_test(tdb); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-endian.c b/ccan/tdb2/test/run-tdb1-endian.c deleted file mode 100644 index 3b91d45b..00000000 --- a/ccan/tdb2/test/run-tdb1-endian.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, data; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(14); - tdb = tdb_open("run-endian.tdb1", - TDB_VERSION1|TDB_CONVERT, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - - ok1(tdb); - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - data.dsize = strlen("world"); - data.dptr = (void *)"world"; - - ok1(tdb_store(tdb, key, data, TDB_MODIFY) == TDB_ERR_NOEXIST); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_ERR_EXISTS); - ok1(tdb_store(tdb, key, data, TDB_MODIFY) == TDB_SUCCESS); - - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == strlen("world")); - ok1(memcmp(data.dptr, "world", strlen("world")) == 0); - free(data.dptr); - - key.dsize++; - ok1(tdb_fetch(tdb, key, &data) == TDB_ERR_NOEXIST); - ok1(data.dptr == NULL); - tdb_close(tdb); - - /* Reopen: should read it */ - tdb = tdb_open("run-endian.tdb1", 0, O_RDWR, 0, NULL); - ok1(tdb); - - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == strlen("world")); - ok1(memcmp(data.dptr, "world", strlen("world")) == 0); - free(data.dptr); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-hashsize.c b/ccan/tdb2/test/run-tdb1-hashsize.c deleted file mode 100644 index 8a78196c..00000000 --- a/ccan/tdb2/test/run-tdb1-hashsize.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - union tdb_attribute hsize, h2; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(14); - tdb = tdb_open("run-tdb1-hashsize.tdb1", TDB_VERSION1, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - ok1(tdb); - h2.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - ok1(tdb_get_attribute(tdb, &h2) == TDB_SUCCESS); - ok1(h2.tdb1_hashsize.hsize == hsize.tdb1_hashsize.hsize); - tdb_close(tdb); - - /* Can't specify TDB_ATTRIBUTE_TDB1_HASHSIZE without O_CREAT */ - tdb = tdb_open("run-tdb1-hashsize.tdb1", TDB_VERSION1, - O_RDWR, 0600, &hsize); - ok1(!tdb); - ok1(tap_log_messages == 1); - - /* Can't specify TDB_ATTRIBUTE_TDB1_HASHSIZE for version2. */ - tdb = tdb_open("run-tdb1-hashsize.tdb", TDB_DEFAULT, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - ok1(!tdb); - ok1(tap_log_messages == 2); - - /* We can get attribute even if we didn't set it though. */ - tdb = tdb_open("run-tdb1-hashsize.tdb1", TDB_DEFAULT, - O_RDWR, 0600, &tap_log_attr); - - ok1(tdb); - memset(&h2, 0, sizeof(h2)); - h2.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - ok1(tdb_get_attribute(tdb, &h2) == TDB_SUCCESS); - ok1(h2.tdb1_hashsize.hsize == hsize.tdb1_hashsize.hsize); - tdb_close(tdb); - - /* Check for default hash size. */ - tdb = tdb_open("run-tdb1-hashsize.tdb1", TDB_VERSION1, - O_CREAT|O_TRUNC|O_RDWR, 0600, &tap_log_attr); - - ok1(tdb); - memset(&h2, 0, sizeof(h2)); - h2.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - ok1(tdb_get_attribute(tdb, &h2) == TDB_SUCCESS); - ok1(h2.tdb1_hashsize.hsize == TDB1_DEFAULT_HASH_SIZE); - tdb_close(tdb); - ok1(tap_log_messages == 2); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-incompatible.c b/ccan/tdb2/test/run-tdb1-incompatible.c deleted file mode 100644 index 46ab5669..00000000 --- a/ccan/tdb2/test/run-tdb1-incompatible.c +++ /dev/null @@ -1,213 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include - -static uint64_t tdb1_dumb_hash(const void *key, size_t len, uint64_t seed, - void *unused) -{ - return len; -} - -static void log_fn(struct tdb_context *tdb, enum tdb_log_level level, - enum TDB_ERROR ecode, const char *message, void *priv) -{ - unsigned int *count = priv; - if (strstr(message, "hash")) - (*count)++; -} - -static unsigned int hdr_rwlocks(const char *fname) -{ - struct tdb1_header hdr; - - int fd = open(fname, O_RDONLY); - if (fd == -1) - return -1; - - if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) - return -1; - - close(fd); - return hdr.rwlocks; -} - -static uint64_t jenkins_hashfn(const void *key, size_t len, uint64_t seed, - void *unused) -{ - return hashlittle(key, len); -} - -static uint64_t old_hash(const void *key, size_t len, uint64_t seed, - void *unused) -{ - return tdb1_old_hash(key, len, seed, unused); -} - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - unsigned int log_count, flags; - TDB_DATA d; - union tdb_attribute log_attr, jhash_attr, ohash_attr, - incompat_hash_attr, dumbhash_attr; - - log_attr.base.attr = TDB_ATTRIBUTE_LOG; - log_attr.base.next = NULL; - log_attr.log.fn = log_fn; - log_attr.log.data = &log_count; - - jhash_attr.base.attr = TDB_ATTRIBUTE_HASH; - jhash_attr.base.next = &log_attr; - jhash_attr.hash.fn = jenkins_hashfn; - - ohash_attr.base.attr = TDB_ATTRIBUTE_HASH; - ohash_attr.base.next = &log_attr; - ohash_attr.hash.fn = old_hash; - - incompat_hash_attr.base.attr = TDB_ATTRIBUTE_HASH; - incompat_hash_attr.base.next = &log_attr; - incompat_hash_attr.hash.fn = tdb1_incompatible_hash; - - dumbhash_attr.base.attr = TDB_ATTRIBUTE_HASH; - dumbhash_attr.base.next = &log_attr; - dumbhash_attr.hash.fn = tdb1_dumb_hash; - - plan_tests(42 * 2); - - for (flags = 0; flags <= TDB_CONVERT; flags += TDB_CONVERT) { - unsigned int rwmagic = TDB1_HASH_RWLOCK_MAGIC; - - if (flags & TDB_CONVERT) - tdb1_convert(&rwmagic, sizeof(rwmagic)); - - /* Create an old-style hash. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", flags|TDB_VERSION1, - O_CREAT|O_RDWR|O_TRUNC, 0600, &log_attr); - ok1(tdb); - ok1(log_count == 0); - d.dptr = (void *)"Hello"; - d.dsize = 5; - ok1(tdb_store(tdb, d, d, TDB_INSERT) == TDB_SUCCESS); - tdb_close(tdb); - - /* Should not have marked rwlocks field. */ - ok1(hdr_rwlocks("run-incompatible.tdb1") == 0); - - /* We can still open any old-style with incompat hash. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", - TDB_VERSION1, - O_RDWR, 0600, &incompat_hash_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_fetch(tdb, d, &d) == TDB_SUCCESS); - ok1(d.dsize == 5); - free(d.dptr); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - log_count = 0; - tdb = tdb_open("test/jenkins-le-hash.tdb1", - TDB_VERSION1, O_RDONLY, 0, &jhash_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - log_count = 0; - tdb = tdb_open("test/jenkins-be-hash.tdb1", - TDB_VERSION1, O_RDONLY, 0, &jhash_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - /* OK, now create with incompatible hash. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", - flags|TDB_VERSION1, - O_CREAT|O_RDWR|O_TRUNC, 0600, - &incompat_hash_attr); - ok1(tdb); - ok1(log_count == 0); - d.dptr = (void *)"Hello"; - d.dsize = 5; - ok1(tdb_store(tdb, d, d, TDB_INSERT) == TDB_SUCCESS); - tdb_close(tdb); - - /* Should have marked rwlocks field. */ - ok1(hdr_rwlocks("run-incompatible.tdb1") == rwmagic); - - /* Cannot open with old hash. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", TDB_VERSION1, - O_RDWR, 0600, &ohash_attr); - ok1(!tdb); - ok1(log_count == 1); - - /* Can open with jenkins hash. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", TDB_VERSION1, - O_RDWR, 0600, &jhash_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_fetch(tdb, d, &d) == TDB_SUCCESS); - ok1(d.dsize == 5); - free(d.dptr); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - /* Can open by letting it figure it out itself. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", TDB_VERSION1, - O_RDWR, 0600, &log_attr); - ok1(tdb); - ok1(log_count == 0); - d.dptr = (void *)"Hello"; - d.dsize = 5; - ok1(tdb_fetch(tdb, d, &d) == TDB_SUCCESS); - ok1(d.dsize == 5); - free(d.dptr); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - /* FIXME: Not possible with TDB2 :( */ - /* We can also use incompatible hash with other hashes. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", - flags|TDB_VERSION1, - O_CREAT|O_RDWR|O_TRUNC, 0600, &dumbhash_attr); - ok1(tdb); - ok1(log_count == 0); - d.dptr = (void *)"Hello"; - d.dsize = 5; - ok1(tdb_store(tdb, d, d, TDB_INSERT) == TDB_SUCCESS); - tdb_close(tdb); - - /* FIXME: Should have marked rwlocks field. */ - ok1(hdr_rwlocks("run-incompatible.tdb1") != rwmagic); - - /* It should not open if we don't specify. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", TDB_VERSION1, O_RDWR, 0, - &log_attr); - ok1(!tdb); - ok1(log_count == 1); - - /* Should reopen with correct hash. */ - log_count = 0; - tdb = tdb_open("run-incompatible.tdb1", TDB_VERSION1, O_RDWR, 0, - &dumbhash_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_fetch(tdb, d, &d) == TDB_SUCCESS); - ok1(d.dsize == 5); - free(d.dptr); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - } - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-nested-transactions.c b/ccan/tdb2/test/run-tdb1-nested-transactions.c deleted file mode 100644 index 149e5318..00000000 --- a/ccan/tdb2/test/run-tdb1-nested-transactions.c +++ /dev/null @@ -1,73 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, data; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(30); - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - - tdb = tdb_open("run-nested-transactions.tdb1", - TDB_VERSION1, O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - ok1(tdb); - - /* No nesting by default. */ - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - data.dptr = (void *)"world"; - data.dsize = strlen("world"); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == strlen("world")); - ok1(memcmp(data.dptr, "world", strlen("world")) == 0); - free(data.dptr); - ok1(tdb_transaction_start(tdb) == TDB_ERR_EINVAL); - ok1(tap_log_messages == 1); - - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == strlen("world")); - ok1(memcmp(data.dptr, "world", strlen("world")) == 0); - free(data.dptr); - ok1(tdb_transaction_commit(tdb) == TDB_SUCCESS); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == strlen("world")); - ok1(memcmp(data.dptr, "world", strlen("world")) == 0); - free(data.dptr); - tdb_close(tdb); - - tdb = tdb_open("run-nested-transactions.tdb1", - TDB_ALLOW_NESTING, O_RDWR, 0, &tap_log_attr); - ok1(tdb); - - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - ok1(tdb_transaction_commit(tdb) == TDB_SUCCESS); - ok1(!tdb_exists(tdb, key)); - tdb_transaction_cancel(tdb); - ok1(tap_log_messages == 1); - /* Surprise! Kills inner "committed" transaction. */ - ok1(tdb_exists(tdb, key)); - - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - ok1(tdb_transaction_commit(tdb) == TDB_SUCCESS); - ok1(!tdb_exists(tdb, key)); - ok1(tdb_transaction_commit(tdb) == TDB_SUCCESS); - ok1(!tdb_exists(tdb, key)); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-nested-traverse.c b/ccan/tdb2/test/run-tdb1-nested-traverse.c deleted file mode 100644 index cf5aa4a2..00000000 --- a/ccan/tdb2/test/run-tdb1-nested-traverse.c +++ /dev/null @@ -1,87 +0,0 @@ -#include "tdb1-lock-tracking.h" -#define fcntl fcntl_with_lockcheck1 -#include "tdb2-source.h" -#include -#undef fcntl -#include -#include -#include -#include "tdb1-external-agent.h" -#include "logging.h" - -static struct agent *agent; - -static bool correct_key(TDB_DATA key) -{ - return key.dsize == strlen("hi") - && memcmp(key.dptr, "hi", key.dsize) == 0; -} - -static bool correct_data(TDB_DATA data) -{ - return data.dsize == strlen("world") - && memcmp(data.dptr, "world", data.dsize) == 0; -} - -static int traverse2(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, - void *p) -{ - ok1(correct_key(key)); - ok1(correct_data(data)); - return 0; -} - -static int traverse1(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, - void *p) -{ - ok1(correct_key(key)); - ok1(correct_data(data)); - ok1(external_agent_operation1(agent, TRANSACTION_START, tdb->name) - == WOULD_HAVE_BLOCKED); - tdb_traverse(tdb, traverse2, NULL); - - /* That should *not* release the transaction lock! */ - ok1(external_agent_operation1(agent, TRANSACTION_START, tdb->name) - == WOULD_HAVE_BLOCKED); - return 0; -} - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, data; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(17); - agent = prepare_external_agent1(); - if (!agent) - err(1, "preparing agent"); - - tdb = tdb_open("run-nested-traverse.tdb1", TDB_VERSION1, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - ok1(tdb); - - ok1(external_agent_operation1(agent, OPEN, tdb->name) == SUCCESS); - ok1(external_agent_operation1(agent, TRANSACTION_START, tdb->name) - == SUCCESS); - ok1(external_agent_operation1(agent, TRANSACTION_COMMIT, tdb->name) - == SUCCESS); - - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - data.dptr = (void *)"world"; - data.dsize = strlen("world"); - - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - tdb_traverse(tdb, traverse1, NULL); - tdb_add_flag(tdb, TDB_RDONLY); - tdb_traverse(tdb, traverse1, NULL); - tdb_remove_flag(tdb, TDB_RDONLY); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-no-lock-during-traverse.c b/ccan/tdb2/test/run-tdb1-no-lock-during-traverse.c deleted file mode 100644 index b2b7a781..00000000 --- a/ccan/tdb2/test/run-tdb1-no-lock-during-traverse.c +++ /dev/null @@ -1,111 +0,0 @@ -#include -#include -#include "tdb1-lock-tracking.h" - -#define fcntl fcntl_with_lockcheck1 - -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -#undef fcntl - -#define NUM_ENTRIES 10 - -static bool prepare_entries(struct tdb_context *tdb) -{ - unsigned int i; - TDB_DATA key, data; - - for (i = 0; i < NUM_ENTRIES; i++) { - key.dsize = sizeof(i); - key.dptr = (void *)&i; - data.dsize = strlen("world"); - data.dptr = (void *)"world"; - - if (tdb_store(tdb, key, data, 0) != TDB_SUCCESS) - return false; - } - return true; -} - -static void delete_entries(struct tdb_context *tdb) -{ - unsigned int i; - TDB_DATA key; - - for (i = 0; i < NUM_ENTRIES; i++) { - key.dsize = sizeof(i); - key.dptr = (void *)&i; - - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - } -} - -/* We don't know how many times this will run. */ -static int delete_other(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, - void *private_data) -{ - unsigned int i; - memcpy(&i, key.dptr, 4); - i = (i + 1) % NUM_ENTRIES; - key.dptr = (void *)&i; - if (tdb_delete(tdb, key) != TDB_SUCCESS) - (*(int *)private_data)++; - return 0; -} - -static int delete_self(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, - void *private_data) -{ - ok1(tdb_delete(tdb, key) == TDB_SUCCESS); - return 0; -} - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - int errors = 0; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(40); - tdb = tdb_open("run-no-lock-during-traverse.tdb1", - TDB_VERSION1, O_CREAT|O_TRUNC|O_RDWR, - 0600, &hsize); - - ok1(tdb); - ok1(prepare_entries(tdb)); - ok1(locking_errors1 == 0); - ok1(tdb_lockall(tdb) == 0); - ok1(locking_errors1 == 0); - ok1(tdb_traverse(tdb, delete_other, &errors) >= 0); - ok1(errors == 0); - ok1(locking_errors1 == 0); - tdb_unlockall(tdb); - - ok1(prepare_entries(tdb)); - ok1(locking_errors1 == 0); - ok1(tdb_lockall(tdb) == 0); - ok1(locking_errors1 == 0); - ok1(tdb_traverse(tdb, delete_self, NULL) == NUM_ENTRIES); - ok1(locking_errors1 == 0); - tdb_unlockall(tdb); - - ok1(prepare_entries(tdb)); - ok1(locking_errors1 == 0); - ok1(tdb_lockall(tdb) == 0); - ok1(locking_errors1 == 0); - delete_entries(tdb); - ok1(locking_errors1 == 0); - tdb_unlockall(tdb); - - ok1(tdb_close(tdb) == 0); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-oldhash.c b/ccan/tdb2/test/run-tdb1-oldhash.c deleted file mode 100644 index f9cffa25..00000000 --- a/ccan/tdb2/test/run-tdb1-oldhash.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - union tdb_attribute incompat_hash_attr; - - incompat_hash_attr.base.attr = TDB_ATTRIBUTE_HASH; - incompat_hash_attr.base.next = &tap_log_attr; - incompat_hash_attr.hash.fn = tdb1_incompatible_hash; - - plan_tests(8); - - /* Old format (with zeroes in the hash magic fields) should - * open with any hash (since we don't know what hash they used). */ - tdb = tdb_open("test/old-nohash-le.tdb1", TDB_VERSION1, O_RDWR, 0, - &tap_log_attr); - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - tdb = tdb_open("test/old-nohash-be.tdb1", TDB_VERSION1, O_RDWR, 0, - &tap_log_attr); - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - tdb = tdb_open("test/old-nohash-le.tdb1", TDB_VERSION1, O_RDWR, 0, - &incompat_hash_attr); - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - tdb = tdb_open("test/old-nohash-be.tdb1", TDB_VERSION1, O_RDWR, 0, - &incompat_hash_attr); - ok1(tdb); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-readonly-check.c b/ccan/tdb2/test/run-tdb1-readonly-check.c deleted file mode 100644 index f42a8f5e..00000000 --- a/ccan/tdb2/test/run-tdb1-readonly-check.c +++ /dev/null @@ -1,47 +0,0 @@ -/* We should be able to tdb_check a O_RDONLY tdb, and we were previously allowed - * to tdb_check() inside a transaction (though that's paranoia!). */ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, data; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(10); - tdb = tdb_open("run-readonly-check.tdb1", - TDB_VERSION1, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - - ok1(tdb); - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - data.dsize = strlen("world"); - data.dptr = (void *)"world"; - - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - - /* We are also allowed to do a check inside a transaction. */ - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - ok1(tdb_close(tdb) == 0); - - tdb = tdb_open("run-readonly-check.tdb1", - TDB_DEFAULT, O_RDONLY, 0, &tap_log_attr); - - ok1(tdb); - ok1(tdb_store(tdb, key, data, TDB_MODIFY) == TDB_ERR_RDONLY); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - ok1(tdb_close(tdb) == 0); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-rwlock-check.c b/ccan/tdb2/test/run-tdb1-rwlock-check.c deleted file mode 100644 index 44a2eeb8..00000000 --- a/ccan/tdb2/test/run-tdb1-rwlock-check.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include - -static void log_fn(struct tdb_context *tdb, enum tdb_log_level level, - enum TDB_ERROR ecode, const char *message, void *priv) -{ - unsigned int *count = priv; - if (strstr(message, "spinlocks")) - (*count)++; -} - -/* The code should barf on TDBs created with rwlocks. */ -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - unsigned int log_count; - union tdb_attribute log_attr; - - log_attr.base.attr = TDB_ATTRIBUTE_LOG; - log_attr.base.next = NULL; - log_attr.log.fn = log_fn; - log_attr.log.data = &log_count; - - plan_tests(4); - - /* We should fail to open rwlock-using tdbs of either endian. */ - log_count = 0; - tdb = tdb_open("test/rwlock-le.tdb1", TDB_VERSION1, O_RDWR, 0, - &log_attr); - ok1(!tdb); - ok1(log_count == 1); - - log_count = 0; - tdb = tdb_open("test/rwlock-be.tdb1", TDB_VERSION1, O_RDWR, 0, - &log_attr); - ok1(!tdb); - ok1(log_count == 1); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-seqnum-wrap.c b/ccan/tdb2/test/run-tdb1-seqnum-wrap.c deleted file mode 100644 index c3eb278e..00000000 --- a/ccan/tdb2/test/run-tdb1-seqnum-wrap.c +++ /dev/null @@ -1,39 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - unsigned int i; - struct tdb1_header hdr; - struct tdb_data key = { (unsigned char *)&hdr, sizeof(hdr) }; - struct tdb_data data = { (unsigned char *)&hdr, sizeof(hdr) }; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 7); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-tdb1-seqnum-wrap.tdb1", - flags[i]|TDB_VERSION1|TDB_SEQNUM, - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - ok1(tdb); - if (!tdb) - break; - ok1(pread(tdb->file->fd, &hdr, sizeof(hdr), 0) == sizeof(hdr)); - hdr.sequence_number = 0xFFFFFFFF; - ok1(pwrite(tdb->file->fd, &hdr, sizeof(hdr), 0) == sizeof(hdr)); - - /* Must not be negative: that would mean an error! */ - ok1(tdb_get_seqnum(tdb) == 0xFFFFFFFF); - - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - ok1(tdb_get_seqnum(tdb) == 0); - tdb_close(tdb); - ok1(tap_log_messages == 0); - } - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-summary.c b/ccan/tdb2/test/run-tdb1-summary.c deleted file mode 100644 index 5107b8e4..00000000 --- a/ccan/tdb2/test/run-tdb1-summary.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include - -int main(int argc, char *argv[]) -{ - unsigned int i, j; - struct tdb_context *tdb; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT }; - TDB_DATA key = { (unsigned char *)&j, sizeof(j) }; - TDB_DATA data = { (unsigned char *)&j, sizeof(j) }; - char *summary; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 14); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-summary.tdb1", flags[i]|TDB_VERSION1, - O_RDWR|O_CREAT|O_TRUNC, 0600, NULL); - ok1(tdb); - if (!tdb) - continue; - - /* Put some stuff in there. */ - for (j = 0; j < 500; j++) { - /* Make sure padding varies to we get some graphs! */ - data.dsize = j % (sizeof(j) + 1); - if (tdb_store(tdb, key, data, TDB_REPLACE) - != TDB_SUCCESS) { - fail("Storing in tdb"); - } - } - - summary = tdb1_summary(tdb); - diag("%s", summary); - ok1(strstr(summary, "Size of file/data: ")); - ok1(strstr(summary, "Number of records: 500\n")); - ok1(strstr(summary, "Smallest/average/largest keys: 4/4/4\n")); - ok1(strstr(summary, "Smallest/average/largest data: 0/2/4\n")); - ok1(strstr(summary, "Smallest/average/largest padding: ")); - ok1(strstr(summary, "Number of dead records: 0\n")); - ok1(strstr(summary, "Number of free records: 1\n")); - ok1(strstr(summary, "Smallest/average/largest free records: ")); - ok1(strstr(summary, "Number of hash chains: 131\n")); - ok1(strstr(summary, "Smallest/average/largest hash chains: ")); - ok1(strstr(summary, "Number of uncoalesced records: 0\n")); - ok1(strstr(summary, "Smallest/average/largest uncoalesced runs: 0/0/0\n")); - ok1(strstr(summary, "Percentage keys/data/padding/free/dead/rechdrs&tailers/hashes: ")); - - free(summary); - tdb_close(tdb); - } - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-traverse-in-transaction.c b/ccan/tdb2/test/run-tdb1-traverse-in-transaction.c deleted file mode 100644 index 691aaf94..00000000 --- a/ccan/tdb2/test/run-tdb1-traverse-in-transaction.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "config.h" -#include "tdb1-lock-tracking.h" -#define fcntl fcntl_with_lockcheck1 -#include "tdb2-source.h" -#include -#undef fcntl_with_lockcheck -#include -#include -#include -#include "tdb1-external-agent.h" -#include "logging.h" - -static struct agent *agent; - -static bool correct_key(TDB_DATA key) -{ - return key.dsize == strlen("hi") - && memcmp(key.dptr, "hi", key.dsize) == 0; -} - -static bool correct_data(TDB_DATA data) -{ - return data.dsize == strlen("world") - && memcmp(data.dptr, "world", data.dsize) == 0; -} - -static int traverse(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, - void *p) -{ - ok1(correct_key(key)); - ok1(correct_data(data)); - return 0; -} - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, data; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(13); - agent = prepare_external_agent1(); - if (!agent) - err(1, "preparing agent"); - - tdb = tdb_open("run-traverse-in-transaction.tdb1", - TDB_VERSION1, O_CREAT|O_TRUNC|O_RDWR, - 0600, &hsize); - ok1(tdb); - - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - data.dptr = (void *)"world"; - data.dsize = strlen("world"); - - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - - ok1(external_agent_operation1(agent, OPEN, tdb->name) == SUCCESS); - - ok1(tdb_transaction_start(tdb) == TDB_SUCCESS); - ok1(external_agent_operation1(agent, TRANSACTION_START, tdb->name) - == WOULD_HAVE_BLOCKED); - tdb_traverse(tdb, traverse, NULL); - - /* That should *not* release the transaction lock! */ - ok1(external_agent_operation1(agent, TRANSACTION_START, tdb->name) - == WOULD_HAVE_BLOCKED); - tdb_traverse(tdb, traverse, NULL); - - /* That should *not* release the transaction lock! */ - ok1(external_agent_operation1(agent, TRANSACTION_START, tdb->name) - == WOULD_HAVE_BLOCKED); - ok1(tdb_transaction_commit(tdb) == TDB_SUCCESS); - /* Now we should be fine. */ - ok1(external_agent_operation1(agent, TRANSACTION_START, tdb->name) - == SUCCESS); - - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-wronghash-fail.c b/ccan/tdb2/test/run-tdb1-wronghash-fail.c deleted file mode 100644 index 63c1bdf1..00000000 --- a/ccan/tdb2/test/run-tdb1-wronghash-fail.c +++ /dev/null @@ -1,143 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include - -static void log_fn(struct tdb_context *tdb, enum tdb_log_level level, - enum TDB_ERROR ecode, const char *message, void *priv) -{ - unsigned int *count = priv; - if (strstr(message, "hash")) - (*count)++; -} - -static uint64_t jenkins_hashfn(const void *key, size_t len, uint64_t seed, - void *unused) -{ - return hashlittle(key, len); -} - -/* the tdb1_old_hash function is "magic" as it automatically makes us test the - * tdb1_incompatible_hash as well, so use this wrapper. */ -static uint64_t old_hash(const void *key, size_t len, uint64_t seed, - void *unused) -{ - return tdb1_old_hash(key, len, seed, unused); -} - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - unsigned int log_count; - TDB_DATA d; - union tdb_attribute log_attr, jhash_attr, ohash_attr, - incompat_hash_attr; - - log_attr.base.attr = TDB_ATTRIBUTE_LOG; - log_attr.base.next = NULL; - log_attr.log.fn = log_fn; - log_attr.log.data = &log_count; - - jhash_attr.base.attr = TDB_ATTRIBUTE_HASH; - jhash_attr.base.next = &log_attr; - jhash_attr.hash.fn = jenkins_hashfn; - - ohash_attr.base.attr = TDB_ATTRIBUTE_HASH; - ohash_attr.base.next = &log_attr; - ohash_attr.hash.fn = old_hash; - - incompat_hash_attr.base.attr = TDB_ATTRIBUTE_HASH; - incompat_hash_attr.base.next = &log_attr; - incompat_hash_attr.hash.fn = tdb1_incompatible_hash; - - plan_tests(28); - - /* Create with default hash. */ - log_count = 0; - tdb = tdb_open("run-wronghash-fail.tdb1", TDB_VERSION1, - O_CREAT|O_RDWR|O_TRUNC, 0600, &log_attr); - ok1(tdb); - ok1(log_count == 0); - d.dptr = (void *)"Hello"; - d.dsize = 5; - ok1(tdb_store(tdb, d, d, TDB_INSERT) == TDB_SUCCESS); - tdb_close(tdb); - - /* Fail to open with different hash. */ - tdb = tdb_open("run-wronghash-fail.tdb1", TDB_VERSION1, O_RDWR, 0, - &jhash_attr); - ok1(!tdb); - ok1(log_count == 1); - - /* Create with different hash. */ - log_count = 0; - tdb = tdb_open("run-wronghash-fail.tdb1", TDB_VERSION1, - O_CREAT|O_RDWR|O_TRUNC, 0600, &jhash_attr); - ok1(tdb); - ok1(log_count == 0); - tdb_close(tdb); - - /* Endian should be no problem. */ - log_count = 0; - tdb = tdb_open("test/jenkins-le-hash.tdb1", TDB_VERSION1, O_RDWR, 0, - &ohash_attr); - ok1(!tdb); - ok1(log_count == 1); - - log_count = 0; - tdb = tdb_open("test/jenkins-be-hash.tdb1", TDB_VERSION1, O_RDWR, 0, - &ohash_attr); - ok1(!tdb); - ok1(log_count == 1); - - log_count = 0; - /* Fail to open with old default hash. */ - tdb = tdb_open("run-wronghash-fail.tdb1", TDB_VERSION1, O_RDWR, 0, - &ohash_attr); - ok1(!tdb); - ok1(log_count == 1); - - log_count = 0; - tdb = tdb_open("test/jenkins-le-hash.tdb1", TDB_VERSION1, O_RDONLY, - 0, &incompat_hash_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - log_count = 0; - tdb = tdb_open("test/jenkins-be-hash.tdb1", TDB_VERSION1, O_RDONLY, - 0, &incompat_hash_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - /* It should open with jenkins hash if we don't specify. */ - log_count = 0; - tdb = tdb_open("test/jenkins-le-hash.tdb1", TDB_VERSION1, O_RDWR, 0, - &log_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - log_count = 0; - tdb = tdb_open("test/jenkins-be-hash.tdb1", TDB_VERSION1, O_RDWR, 0, - &log_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - log_count = 0; - tdb = tdb_open("run-wronghash-fail.tdb1", TDB_VERSION1, O_RDONLY, - 0, &log_attr); - ok1(tdb); - ok1(log_count == 0); - ok1(tdb_check(tdb, NULL, NULL) == TDB_SUCCESS); - tdb_close(tdb); - - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1-zero-append.c b/ccan/tdb2/test/run-tdb1-zero-append.c deleted file mode 100644 index fdc9cdce..00000000 --- a/ccan/tdb2/test/run-tdb1-zero-append.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, data; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(5); - tdb = tdb_open(NULL, TDB_INTERNAL|TDB_VERSION1, O_CREAT|O_TRUNC|O_RDWR, - 0600, &hsize); - ok1(tdb); - - /* Tickle bug on appending zero length buffer to zero length buffer. */ - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - data.dptr = (void *)"world"; - data.dsize = 0; - - ok1(tdb_append(tdb, key, data) == TDB_SUCCESS); - ok1(tdb_append(tdb, key, data) == TDB_SUCCESS); - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == 0); - free(data.dptr); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb1.c b/ccan/tdb2/test/run-tdb1.c deleted file mode 100644 index dca6473b..00000000 --- a/ccan/tdb2/test/run-tdb1.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "tdb2-source.h" -#include -#include -#include -#include "logging.h" - -int main(int argc, char *argv[]) -{ - struct tdb_context *tdb; - TDB_DATA key, data; - union tdb_attribute hsize; - - hsize.base.attr = TDB_ATTRIBUTE_TDB1_HASHSIZE; - hsize.base.next = &tap_log_attr; - hsize.tdb1_hashsize.hsize = 1024; - - plan_tests(9); - tdb = tdb_open("run.tdb1", TDB_VERSION1, - O_CREAT|O_TRUNC|O_RDWR, 0600, &hsize); - - ok1(tdb); - key.dsize = strlen("hi"); - key.dptr = (void *)"hi"; - data.dsize = strlen("world"); - data.dptr = (void *)"world"; - - ok1(tdb_store(tdb, key, data, TDB_MODIFY) == TDB_ERR_NOEXIST); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_SUCCESS); - ok1(tdb_store(tdb, key, data, TDB_INSERT) == TDB_ERR_EXISTS); - ok1(tdb_store(tdb, key, data, TDB_MODIFY) == TDB_SUCCESS); - - ok1(tdb_fetch(tdb, key, &data) == TDB_SUCCESS); - ok1(data.dsize == strlen("world")); - ok1(memcmp(data.dptr, "world", strlen("world")) == 0); - free(data.dptr); - - key.dsize++; - ok1(tdb_fetch(tdb, key, &data) == TDB_ERR_NOEXIST); - tdb_close(tdb); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb_errorstr.c b/ccan/tdb2/test/run-tdb_errorstr.c deleted file mode 100644 index 742fb891..00000000 --- a/ccan/tdb2/test/run-tdb_errorstr.c +++ /dev/null @@ -1,52 +0,0 @@ -#include "tdb2-source.h" -#include - -int main(int argc, char *argv[]) -{ - enum TDB_ERROR err; - plan_tests(TDB_ERR_RDONLY*-1 + 2); - - for (err = TDB_SUCCESS; err >= TDB_ERR_RDONLY; err--) { - switch (err) { - case TDB_SUCCESS: - ok1(!strcmp(tdb_errorstr(err), - "Success")); - break; - case TDB_ERR_IO: - ok1(!strcmp(tdb_errorstr(err), - "IO Error")); - break; - case TDB_ERR_LOCK: - ok1(!strcmp(tdb_errorstr(err), - "Locking error")); - break; - case TDB_ERR_OOM: - ok1(!strcmp(tdb_errorstr(err), - "Out of memory")); - break; - case TDB_ERR_EXISTS: - ok1(!strcmp(tdb_errorstr(err), - "Record exists")); - break; - case TDB_ERR_EINVAL: - ok1(!strcmp(tdb_errorstr(err), - "Invalid parameter")); - break; - case TDB_ERR_NOEXIST: - ok1(!strcmp(tdb_errorstr(err), - "Record does not exist")); - break; - case TDB_ERR_RDONLY: - ok1(!strcmp(tdb_errorstr(err), - "write not permitted")); - break; - case TDB_ERR_CORRUPT: - ok1(!strcmp(tdb_errorstr(err), - "Corrupt database")); - break; - } - } - ok1(!strcmp(tdb_errorstr(err), "Invalid error code")); - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-tdb_foreach.c b/ccan/tdb2/test/run-tdb_foreach.c deleted file mode 100644 index b17f0780..00000000 --- a/ccan/tdb2/test/run-tdb_foreach.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -static int drop_count(struct tdb_context *tdb, unsigned int *count) -{ - if (--(*count) == 0) - return 1; - return 0; -} - -static int set_found(struct tdb_context *tdb, bool found[3]) -{ - unsigned int idx; - - if (strcmp(tdb_name(tdb), "run-tdb_foreach0.tdb") == 0) - idx = 0; - else if (strcmp(tdb_name(tdb), "run-tdb_foreach1.tdb") == 0) - idx = 1; - else if (strcmp(tdb_name(tdb), "run-tdb_foreach2.tdb") == 0) - idx = 2; - else - abort(); - - if (found[idx]) - abort(); - found[idx] = true; - return 0; -} - -int main(int argc, char *argv[]) -{ - unsigned int i, count; - bool found[3]; - struct tdb_context *tdb0, *tdb1, *tdb2; - int flags[] = { TDB_DEFAULT, TDB_NOMMAP, - TDB_CONVERT, TDB_NOMMAP|TDB_CONVERT }; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 8); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb0 = tdb_open("run-tdb_foreach0.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - tdb1 = tdb_open("run-tdb_foreach1.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - tdb2 = tdb_open("run-tdb_foreach2.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr); - - memset(found, 0, sizeof(found)); - tdb_foreach(set_found, found); - ok1(found[0] && found[1] && found[2]); - - /* Test premature iteration termination */ - count = 1; - tdb_foreach(drop_count, &count); - ok1(count == 0); - - tdb_close(tdb1); - memset(found, 0, sizeof(found)); - tdb_foreach(set_found, found); - ok1(found[0] && !found[1] && found[2]); - - tdb_close(tdb2); - memset(found, 0, sizeof(found)); - tdb_foreach(set_found, found); - ok1(found[0] && !found[1] && !found[2]); - - tdb1 = tdb_open("run-tdb_foreach1.tdb", flags[i], - O_RDWR, 0600, &tap_log_attr); - memset(found, 0, sizeof(found)); - tdb_foreach(set_found, found); - ok1(found[0] && found[1] && !found[2]); - - tdb_close(tdb0); - memset(found, 0, sizeof(found)); - tdb_foreach(set_found, found); - ok1(!found[0] && found[1] && !found[2]); - - tdb_close(tdb1); - memset(found, 0, sizeof(found)); - tdb_foreach(set_found, found); - ok1(!found[0] && !found[1] && !found[2]); - ok1(tap_log_messages == 0); - } - - return exit_status(); -} diff --git a/ccan/tdb2/test/run-traverse.c b/ccan/tdb2/test/run-traverse.c deleted file mode 100644 index 4de0ebde..00000000 --- a/ccan/tdb2/test/run-traverse.c +++ /dev/null @@ -1,203 +0,0 @@ -#include "tdb2-source.h" -#include -#include "logging.h" - -#define NUM_RECORDS 1000 - -/* We use the same seed which we saw a failure on. */ -static uint64_t fixedhash(const void *key, size_t len, uint64_t seed, void *p) -{ - return hash64_stable((const unsigned char *)key, len, - *(uint64_t *)p); -} - -static bool store_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 < NUM_RECORDS; i++) - if (tdb_store(tdb, key, data, TDB_REPLACE) != 0) - return false; - return true; -} - -struct trav_data { - unsigned int calls, call_limit; - int low, high; - bool mismatch; - bool delete; - enum TDB_ERROR delete_error; -}; - -static int trav(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, - struct trav_data *td) -{ - int val; - - td->calls++; - if (key.dsize != sizeof(val) || dbuf.dsize != sizeof(val) - || memcmp(key.dptr, dbuf.dptr, key.dsize) != 0) { - td->mismatch = true; - return -1; - } - memcpy(&val, dbuf.dptr, dbuf.dsize); - if (val < td->low) - td->low = val; - if (val > td->high) - td->high = val; - - if (td->delete) { - td->delete_error = tdb_delete(tdb, key); - if (td->delete_error != TDB_SUCCESS) { - return -1; - } - } - - if (td->calls == td->call_limit) - return 1; - return 0; -} - -struct trav_grow_data { - unsigned int calls; - unsigned int num_large; - bool mismatch; - enum TDB_ERROR error; -}; - -static int trav_grow(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, - struct trav_grow_data *tgd) -{ - int val; - unsigned char buffer[128] = { 0 }; - - tgd->calls++; - if (key.dsize != sizeof(val) || dbuf.dsize < sizeof(val) - || memcmp(key.dptr, dbuf.dptr, key.dsize) != 0) { - tgd->mismatch = true; - return -1; - } - - if (dbuf.dsize > sizeof(val)) - /* We must have seen this before! */ - tgd->num_large++; - - /* Make a big difference to the database. */ - dbuf.dptr = buffer; - dbuf.dsize = sizeof(buffer); - tgd->error = tdb_append(tdb, key, dbuf); - if (tgd->error != TDB_SUCCESS) { - return -1; - } - return 0; -} - -int main(int argc, char *argv[]) -{ - unsigned int i; - int num; - struct trav_data td; - struct trav_grow_data tgd; - struct tdb_context *tdb; - uint64_t seed = 16014841315512641303ULL; - int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP, - TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT, - TDB_NOMMAP|TDB_CONVERT }; - union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH }, - .fn = fixedhash, - .data = &seed } }; - - hattr.base.next = &tap_log_attr; - - plan_tests(sizeof(flags) / sizeof(flags[0]) * 32 + 1); - for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { - tdb = tdb_open("run-traverse.tdb", flags[i], - O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr); - ok1(tdb); - if (!tdb) - continue; - - ok1(tdb_traverse(tdb, NULL, NULL) == 0); - - ok1(store_records(tdb)); - num = tdb_traverse(tdb, NULL, NULL); - ok1(num == NUM_RECORDS); - - /* Full traverse. */ - td.calls = 0; - td.call_limit = UINT_MAX; - td.low = INT_MAX; - td.high = INT_MIN; - td.mismatch = false; - td.delete = false; - - num = tdb_traverse(tdb, trav, &td); - ok1(num == NUM_RECORDS); - ok1(!td.mismatch); - ok1(td.calls == NUM_RECORDS); - ok1(td.low == 0); - ok1(td.high == NUM_RECORDS-1); - - /* Short traverse. */ - td.calls = 0; - td.call_limit = NUM_RECORDS / 2; - td.low = INT_MAX; - td.high = INT_MIN; - td.mismatch = false; - td.delete = false; - - num = tdb_traverse(tdb, trav, &td); - ok1(num == NUM_RECORDS / 2); - ok1(!td.mismatch); - ok1(td.calls == NUM_RECORDS / 2); - ok1(td.low <= NUM_RECORDS / 2); - ok1(td.high > NUM_RECORDS / 2); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tap_log_messages == 0); - - /* Deleting traverse (delete everything). */ - td.calls = 0; - td.call_limit = UINT_MAX; - td.low = INT_MAX; - td.high = INT_MIN; - td.mismatch = false; - td.delete = true; - td.delete_error = TDB_SUCCESS; - num = tdb_traverse(tdb, trav, &td); - ok1(num == NUM_RECORDS); - ok1(td.delete_error == TDB_SUCCESS); - ok1(!td.mismatch); - ok1(td.calls == NUM_RECORDS); - ok1(td.low == 0); - ok1(td.high == NUM_RECORDS - 1); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Now it's empty! */ - ok1(tdb_traverse(tdb, NULL, NULL) == 0); - - /* Re-add. */ - ok1(store_records(tdb)); - ok1(tdb_traverse(tdb, NULL, NULL) == NUM_RECORDS); - ok1(tdb_check(tdb, NULL, NULL) == 0); - - /* Grow. This will cause us to be reshuffled. */ - tgd.calls = 0; - tgd.num_large = 0; - tgd.mismatch = false; - tgd.error = TDB_SUCCESS; - ok1(tdb_traverse(tdb, trav_grow, &tgd) > 1); - ok1(tgd.error == 0); - ok1(!tgd.mismatch); - ok1(tdb_check(tdb, NULL, NULL) == 0); - ok1(tgd.num_large < tgd.calls); - diag("growing db: %u calls, %u repeats", - tgd.calls, tgd.num_large); - - tdb_close(tdb); - } - - ok1(tap_log_messages == 0); - return exit_status(); -} diff --git a/ccan/tdb2/test/rwlock-be.tdb1 b/ccan/tdb2/test/rwlock-be.tdb1 deleted file mode 100644 index 45b5f09a1b619b4f79201057dd811781d4151d33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 696 ncmWG>aZ*Uj%t_^9zz%XH8P%GBQt3za#j&dx6&(!$`iB4j>yiaW diff --git a/ccan/tdb2/test/rwlock-le.tdb1 b/ccan/tdb2/test/rwlock-le.tdb1 deleted file mode 100644 index 45b5f09a1b619b4f79201057dd811781d4151d33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 696 ncmWG>aZ*Uj%t_^9zz%XH8P%GBQt3za#j&dx6&(!$`iB4j>yiaW diff --git a/ccan/tdb2/test/tdb1-external-agent.c b/ccan/tdb2/test/tdb1-external-agent.c deleted file mode 100644 index ffde0770..00000000 --- a/ccan/tdb2/test/tdb1-external-agent.c +++ /dev/null @@ -1,188 +0,0 @@ -#include "tdb1-external-agent.h" -#include "tdb1-lock-tracking.h" -#include "logging.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static struct tdb_context *tdb; - -static enum agent_return do_operation(enum operation op, const char *name) -{ - TDB_DATA k; - enum agent_return ret; - TDB_DATA data; - - if (op != OPEN && !tdb) { - diag("external: No tdb open!"); - return OTHER_FAILURE; - } - - k.dptr = (void *)name; - k.dsize = strlen(name); - - locking_would_block1 = 0; - switch (op) { - case OPEN: - if (tdb) { - diag("Already have tdb %s open", tdb->name); - return OTHER_FAILURE; - } - tdb = tdb_open(name, TDB_VERSION1, O_RDWR, 0, &tap_log_attr); - if (!tdb) { - if (!locking_would_block1) - diag("Opening tdb gave %s", strerror(errno)); - ret = OTHER_FAILURE; - } else - ret = SUCCESS; - break; - case TRANSACTION_START: - ret = tdb_transaction_start(tdb) == TDB_SUCCESS ? SUCCESS : OTHER_FAILURE; - break; - case FETCH: - if (tdb_fetch(tdb, k, &data) != TDB_SUCCESS) { - if (tdb->last_error == TDB_ERR_NOEXIST) - ret = FAILED; - else - ret = OTHER_FAILURE; - } else if (data.dsize != k.dsize - || memcmp(data.dptr, k.dptr, k.dsize) != 0) { - ret = OTHER_FAILURE; - } else { - ret = SUCCESS; - } - free(data.dptr); - break; - case STORE: - if (tdb_store(tdb, k, k, 0) == TDB_SUCCESS) - ret = SUCCESS; - else - ret = OTHER_FAILURE; - break; - case TRANSACTION_COMMIT: - ret = tdb_transaction_commit(tdb) == TDB_SUCCESS ? SUCCESS : OTHER_FAILURE; - break; - case CHECK: - ret = tdb_check(tdb, NULL, NULL) == TDB_SUCCESS ? SUCCESS : OTHER_FAILURE; - break; - case NEEDS_RECOVERY: - ret = tdb1_needs_recovery(tdb) ? SUCCESS : FAILED; - break; - case CLOSE: - ret = tdb_close(tdb) == 0 ? SUCCESS : OTHER_FAILURE; - tdb = NULL; - break; - default: - ret = OTHER_FAILURE; - } - - if (locking_would_block1) - ret = WOULD_HAVE_BLOCKED; - - return ret; -} - -struct agent { - int cmdfd, responsefd; -}; - -/* Do this before doing any tdb stuff. Return handle, or NULL. */ -struct agent *prepare_external_agent1(void) -{ - int pid, ret; - int command[2], response[2]; - char name[1+PATH_MAX]; - - if (pipe(command) != 0 || pipe(response) != 0) - return NULL; - - pid = fork(); - if (pid < 0) - return NULL; - - if (pid != 0) { - struct agent *agent = malloc(sizeof(*agent)); - - close(command[0]); - close(response[1]); - agent->cmdfd = command[1]; - agent->responsefd = response[0]; - return agent; - } - - close(command[1]); - close(response[0]); - - /* We want to fail, not block. */ - nonblocking_locks1 = true; - log_prefix = "external: "; - while ((ret = read(command[0], name, sizeof(name))) > 0) { - enum agent_return result; - - result = do_operation(name[0], name+1); - if (write(response[1], &result, sizeof(result)) - != sizeof(result)) - err(1, "Writing response"); - } - exit(0); -} - -/* Ask the external agent to try to do an operation. */ -enum agent_return external_agent_operation1(struct agent *agent, - enum operation op, - const char *name) -{ - enum agent_return res; - unsigned int len; - char *string; - - if (!name) - name = ""; - len = 1 + strlen(name) + 1; - string = malloc(len); - - string[0] = op; - strcpy(string+1, name); - - if (write(agent->cmdfd, string, len) != len - || read(agent->responsefd, &res, sizeof(res)) != sizeof(res)) - res = AGENT_DIED; - - free(string); - return res; -} - -const char *agent_return_name1(enum agent_return ret) -{ - return ret == SUCCESS ? "SUCCESS" - : ret == WOULD_HAVE_BLOCKED ? "WOULD_HAVE_BLOCKED" - : ret == AGENT_DIED ? "AGENT_DIED" - : ret == FAILED ? "FAILED" - : ret == OTHER_FAILURE ? "OTHER_FAILURE" - : "**INVALID**"; -} - -const char *operation_name1(enum operation op) -{ - switch (op) { - case OPEN: return "OPEN"; - case TRANSACTION_START: return "TRANSACTION_START"; - case FETCH: return "FETCH"; - case STORE: return "STORE"; - case TRANSACTION_COMMIT: return "TRANSACTION_COMMIT"; - case CHECK: return "CHECK"; - case NEEDS_RECOVERY: return "NEEDS_RECOVERY"; - case CLOSE: return "CLOSE"; - } - return "**INVALID**"; -} diff --git a/ccan/tdb2/test/tdb1-external-agent.h b/ccan/tdb2/test/tdb1-external-agent.h deleted file mode 100644 index ee903b65..00000000 --- a/ccan/tdb2/test/tdb1-external-agent.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef TDB_TEST_EXTERNAL_AGENT_H -#define TDB_TEST_EXTERNAL_AGENT_H - -/* For locking tests, we need a different process to try things at - * various times. */ -enum operation { - OPEN, - TRANSACTION_START, - FETCH, - STORE, - TRANSACTION_COMMIT, - CHECK, - NEEDS_RECOVERY, - CLOSE, -}; - -/* Do this before doing any tdb stuff. Return handle, or -1. */ -struct agent *prepare_external_agent1(void); - -enum agent_return { - SUCCESS, - WOULD_HAVE_BLOCKED, - AGENT_DIED, - FAILED, /* For fetch, or NEEDS_RECOVERY */ - OTHER_FAILURE, -}; - -/* Ask the external agent to try to do an operation. - * name == tdb name for OPEN/OPEN_WITH_CLEAR_IF_FIRST, - * record name for FETCH/STORE (store stores name as data too) - */ -enum agent_return external_agent_operation1(struct agent *handle, - enum operation op, - const char *name); - -/* Mapping enum -> string. */ -const char *agent_return_name1(enum agent_return ret); -const char *operation_name1(enum operation op); - -#endif /* TDB_TEST_EXTERNAL_AGENT_H */ diff --git a/ccan/tdb2/test/tdb1-lock-tracking.c b/ccan/tdb2/test/tdb1-lock-tracking.c deleted file mode 100644 index 197b1f07..00000000 --- a/ccan/tdb2/test/tdb1-lock-tracking.c +++ /dev/null @@ -1,146 +0,0 @@ -/* We save the locks so we can reaquire them. */ -#include -#include -#include -#include -#include -#include -#include "tdb1-lock-tracking.h" - -struct lock { - struct lock *next; - unsigned int off; - unsigned int len; - int type; -}; -static struct lock *locks; -int locking_errors1 = 0; -bool suppress_lockcheck1 = false; -bool nonblocking_locks1; -int locking_would_block1 = 0; -void (*unlock_callback1)(int fd); - -int fcntl_with_lockcheck1(int fd, int cmd, ... /* arg */ ) -{ - va_list ap; - int ret, arg3; - struct flock *fl; - bool may_block = false; - - if (cmd != F_SETLK && cmd != F_SETLKW) { - /* This may be totally bogus, but we don't know in general. */ - va_start(ap, cmd); - arg3 = va_arg(ap, int); - va_end(ap); - - return fcntl(fd, cmd, arg3); - } - - va_start(ap, cmd); - fl = va_arg(ap, struct flock *); - va_end(ap); - - if (cmd == F_SETLKW && nonblocking_locks1) { - cmd = F_SETLK; - may_block = true; - } - ret = fcntl(fd, cmd, fl); - - /* Detect when we failed, but might have been OK if we waited. */ - if (may_block && ret == -1 && (errno == EAGAIN || errno == EACCES)) { - locking_would_block1++; - } - - if (fl->l_type == F_UNLCK) { - struct lock **l; - struct lock *old = NULL; - - for (l = &locks; *l; l = &(*l)->next) { - if ((*l)->off == fl->l_start - && (*l)->len == fl->l_len) { - if (ret == 0) { - old = *l; - *l = (*l)->next; - free(old); - } - break; - } - } - if (!old && !suppress_lockcheck1) { - diag("Unknown unlock %u@%u - %i", - (int)fl->l_len, (int)fl->l_start, ret); - locking_errors1++; - } - } else { - struct lock *new, *i; - unsigned int fl_end = fl->l_start + fl->l_len; - if (fl->l_len == 0) - fl_end = (unsigned int)-1; - - /* Check for overlaps: we shouldn't do this. */ - for (i = locks; i; i = i->next) { - unsigned int i_end = i->off + i->len; - if (i->len == 0) - i_end = (unsigned int)-1; - - if (fl->l_start >= i->off && fl->l_start < i_end) - break; - if (fl_end >= i->off && fl_end < i_end) - break; - - /* tdb_allrecord_lock does this, handle adjacent: */ - if (fl->l_start == i_end && fl->l_type == i->type) { - if (ret == 0) { - i->len = fl->l_len - ? i->len + fl->l_len - : 0; - } - goto done; - } - } - if (i) { - /* Special case: upgrade of allrecord lock. */ - if (i->type == F_RDLCK && fl->l_type == F_WRLCK - && i->off == TDB1_FREELIST_TOP - && fl->l_start == TDB1_FREELIST_TOP - && i->len == 0 - && fl->l_len == 0) { - if (ret == 0) - i->type = F_WRLCK; - goto done; - } - if (!suppress_lockcheck1) { - diag("%s lock %u@%u overlaps %u@%u", - fl->l_type == F_WRLCK ? "write" : "read", - (int)fl->l_len, (int)fl->l_start, - i->len, (int)i->off); - locking_errors1++; - } - } - - if (ret == 0) { - new = malloc(sizeof *new); - new->off = fl->l_start; - new->len = fl->l_len; - new->type = fl->l_type; - new->next = locks; - locks = new; - } - } -done: - if (ret == 0 && fl->l_type == F_UNLCK && unlock_callback1) - unlock_callback1(fd); - return ret; -} - -unsigned int forget_locking1(void) -{ - unsigned int num = 0; - while (locks) { - struct lock *next = locks->next; - free(locks); - locks = next; - num++; - } - return num; -} diff --git a/ccan/tdb2/test/tdb1-lock-tracking.h b/ccan/tdb2/test/tdb1-lock-tracking.h deleted file mode 100644 index cb8c2f12..00000000 --- a/ccan/tdb2/test/tdb1-lock-tracking.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef TDB1_LOCK_TRACKING_H -#define TDB1_LOCK_TRACKING_H -#include -#include - -/* Set this if you want a callback after fnctl unlock. */ -extern void (*unlock_callback1)(int fd); - -/* Replacement fcntl. */ -int fcntl_with_lockcheck1(int fd, int cmd, ... /* arg */ ); - -/* Discard locking info: returns number of locks outstanding. */ -unsigned int forget_locking1(void); - -/* Number of errors in locking. */ -extern int locking_errors1; - -/* Suppress lock checking. */ -extern bool suppress_lockcheck1; - -/* Make all locks non-blocking. */ -extern bool nonblocking_locks1; - -/* Number of times we failed a lock because we made it non-blocking. */ -extern int locking_would_block1; -#endif /* LOCK_TRACKING_H */ diff --git a/ccan/tdb2/test/tdb1.corrupt b/ccan/tdb2/test/tdb1.corrupt deleted file mode 100644 index 83d6677454300a5173f80dcbcfec15fc636aed24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192512 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
  • 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%0D;B{e9TTu0g|3*k?HPjWZaHd%ZIR!~gXDzVJoq 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 OTV%~k$Hy(v-~S&FX3gOM diff --git a/ccan/tdb2/test/tdb2-source.h b/ccan/tdb2/test/tdb2-source.h deleted file mode 100644 index 28ab3513..00000000 --- a/ccan/tdb2/test/tdb2-source.h +++ /dev/null @@ -1,21 +0,0 @@ -#include "config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/ccan/tdb2/tools/Makefile b/ccan/tdb2/tools/Makefile deleted file mode 100644 index 11188c3b..00000000 --- a/ccan/tdb2/tools/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -OBJS:=../../tdb2.o ../../hash.o ../../tally.o -CFLAGS:=-I../../.. -I.. -Wall -g -O3 #-g -pg -LDFLAGS:=-L../../.. - -default: tdb2torture tdb2tool tdb2dump tdb2restore mktdb2 speed growtdb-bench - -tdb2dump: tdb2dump.c $(OBJS) -tdb2restore: tdb2restore.c $(OBJS) -tdb2torture: tdb2torture.c $(OBJS) -tdb2tool: tdb2tool.c $(OBJS) -mktdb2: mktdb2.c $(OBJS) -speed: speed.c $(OBJS) -growtdb-bench: growtdb-bench.c $(OBJS) - -clean: - rm -f tdb2torture tdb2dump tdb2restore tdb2tool mktdb2 speed growtdb-bench diff --git a/ccan/tdb2/tools/growtdb-bench.c b/ccan/tdb2/tools/growtdb-bench.c deleted file mode 100644 index 205ff86e..00000000 --- a/ccan/tdb2/tools/growtdb-bench.c +++ /dev/null @@ -1,114 +0,0 @@ -#include "tdb2.h" -#include -#include -#include -#include -#include -#include -#include -#include - -static void logfn(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, - void *data) -{ - fprintf(stderr, "tdb:%s:%s:%s\n", - tdb_name(tdb), tdb_errorstr(ecode), message); -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j, users, groups; - TDB_DATA idxkey, idxdata; - TDB_DATA k, d, gk; - char cmd[100]; - struct tdb_context *tdb; - enum TDB_ERROR ecode; - union tdb_attribute log; - - if (argc != 3) { - printf("Usage: growtdb-bench \n"); - exit(1); - } - users = atoi(argv[1]); - groups = atoi(argv[2]); - - sprintf(cmd, "cat /proc/%i/statm", getpid()); - - log.base.attr = TDB_ATTRIBUTE_LOG; - log.base.next = NULL; - log.log.fn = logfn; - - tdb = tdb_open("/tmp/growtdb.tdb", TDB_DEFAULT, - O_RDWR|O_CREAT|O_TRUNC, 0600, &log); - - idxkey.dptr = (unsigned char *)"User index"; - idxkey.dsize = strlen("User index"); - idxdata.dsize = 51; - idxdata.dptr = calloc(idxdata.dsize, 1); - - /* Create users. */ - k.dsize = 48; - k.dptr = calloc(k.dsize, 1); - d.dsize = 64; - d.dptr = calloc(d.dsize, 1); - - tdb_transaction_start(tdb); - for (i = 0; i < users; i++) { - memcpy(k.dptr, &i, sizeof(i)); - ecode = tdb_store(tdb, k, d, TDB_INSERT); - if (ecode != TDB_SUCCESS) - errx(1, "tdb insert failed: %s", tdb_errorstr(ecode)); - - /* This simulates a growing index record. */ - ecode = tdb_append(tdb, idxkey, idxdata); - if (ecode != TDB_SUCCESS) - errx(1, "tdb append failed: %s", tdb_errorstr(ecode)); - } - if ((ecode = tdb_transaction_commit(tdb)) != 0) - errx(1, "tdb commit1 failed: %s", tdb_errorstr(ecode)); - - if ((ecode = tdb_check(tdb, NULL, NULL)) != 0) - errx(1, "tdb_check failed after initial insert!"); - - system(cmd); - - /* Now put them all in groups: add 32 bytes to each record for - * a group. */ - gk.dsize = 48; - gk.dptr = calloc(k.dsize, 1); - gk.dptr[gk.dsize-1] = 1; - - d.dsize = 32; - for (i = 0; i < groups; i++) { - tdb_transaction_start(tdb); - /* Create the "group". */ - memcpy(gk.dptr, &i, sizeof(i)); - ecode = tdb_store(tdb, gk, d, TDB_INSERT); - if (ecode != TDB_SUCCESS) - errx(1, "tdb insert failed: %s", tdb_errorstr(ecode)); - - /* Now populate it. */ - for (j = 0; j < users; j++) { - /* Append to the user. */ - memcpy(k.dptr, &j, sizeof(j)); - if ((ecode = tdb_append(tdb, k, d)) != 0) - errx(1, "tdb append failed: %s", - tdb_errorstr(ecode)); - - /* Append to the group. */ - if ((ecode = tdb_append(tdb, gk, d)) != 0) - errx(1, "tdb append failed: %s", - tdb_errorstr(ecode)); - } - if ((ecode = tdb_transaction_commit(tdb)) != 0) - errx(1, "tdb commit2 failed: %s", tdb_errorstr(ecode)); - if ((ecode = tdb_check(tdb, NULL, NULL)) != 0) - errx(1, "tdb_check failed after iteration %i!", i); - system(cmd); - } - - return 0; -} diff --git a/ccan/tdb2/tools/mktdb2.c b/ccan/tdb2/tools/mktdb2.c deleted file mode 100644 index c8c28034..00000000 --- a/ccan/tdb2/tools/mktdb2.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "tdb2.h" -#include -#include -#include -#include - -int main(int argc, char *argv[]) -{ - unsigned int i, num_recs; - struct tdb_context *tdb; - - if (argc != 3 || (num_recs = atoi(argv[2])) == 0) - errx(1, "Usage: mktdb "); - - tdb = tdb_open(argv[1], TDB_DEFAULT, O_CREAT|O_TRUNC|O_RDWR, 0600,NULL); - if (!tdb) - err(1, "Opening %s", argv[1]); - - for (i = 0; i < num_recs; i++) { - TDB_DATA d; - - d.dptr = (void *)&i; - d.dsize = sizeof(i); - if (tdb_store(tdb, d, d, TDB_INSERT) != 0) - err(1, "Failed to store record %i", i); - } - printf("Done\n"); - return 0; -} diff --git a/ccan/tdb2/tools/speed.c b/ccan/tdb2/tools/speed.c deleted file mode 100644 index ccb5ae34..00000000 --- a/ccan/tdb2/tools/speed.c +++ /dev/null @@ -1,443 +0,0 @@ -/* Simple speed test for TDB */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "tdb2.h" - -/* Nanoseconds per operation */ -static size_t normalize(const struct timeval *start, - const struct timeval *stop, - unsigned int num) -{ - struct timeval diff; - - timersub(stop, start, &diff); - - /* Floating point is more accurate here. */ - return (double)(diff.tv_sec * 1000000 + diff.tv_usec) - / num * 1000; -} - -static size_t file_size(void) -{ - struct stat st; - - if (stat("/tmp/speed.tdb", &st) != 0) - return -1; - return st.st_size; -} - -static int count_record(struct tdb_context *tdb, - TDB_DATA key, TDB_DATA data, void *p) -{ - int *total = p; - *total += *(int *)data.dptr; - return 0; -} - -static void dump_and_clear_stats(struct tdb_context **tdb, - int flags, - union tdb_attribute *attr) -{ - union tdb_attribute stats; - enum TDB_ERROR ecode; - - stats.base.attr = TDB_ATTRIBUTE_STATS; - stats.stats.size = sizeof(stats.stats); - ecode = tdb_get_attribute(*tdb, &stats); - if (ecode != TDB_SUCCESS) - errx(1, "Getting stats: %s", tdb_errorstr(ecode)); - - printf("allocs = %llu\n", - (unsigned long long)stats.stats.allocs); - printf(" alloc_subhash = %llu\n", - (unsigned long long)stats.stats.alloc_subhash); - printf(" alloc_chain = %llu\n", - (unsigned long long)stats.stats.alloc_chain); - printf(" alloc_bucket_exact = %llu\n", - (unsigned long long)stats.stats.alloc_bucket_exact); - printf(" alloc_bucket_max = %llu\n", - (unsigned long long)stats.stats.alloc_bucket_max); - printf(" alloc_leftover = %llu\n", - (unsigned long long)stats.stats.alloc_leftover); - printf(" alloc_coalesce_tried = %llu\n", - (unsigned long long)stats.stats.alloc_coalesce_tried); - printf(" alloc_coalesce_iterate_clash = %llu\n", - (unsigned long long)stats.stats.alloc_coalesce_iterate_clash); - printf(" alloc_coalesce_lockfail = %llu\n", - (unsigned long long)stats.stats.alloc_coalesce_lockfail); - printf(" alloc_coalesce_race = %llu\n", - (unsigned long long)stats.stats.alloc_coalesce_race); - printf(" alloc_coalesce_succeeded = %llu\n", - (unsigned long long)stats.stats.alloc_coalesce_succeeded); - printf(" alloc_coalesce_num_merged = %llu\n", - (unsigned long long)stats.stats.alloc_coalesce_num_merged); - printf("compares = %llu\n", - (unsigned long long)stats.stats.compares); - printf(" compare_wrong_bucket = %llu\n", - (unsigned long long)stats.stats.compare_wrong_bucket); - printf(" compare_wrong_offsetbits = %llu\n", - (unsigned long long)stats.stats.compare_wrong_offsetbits); - printf(" compare_wrong_keylen = %llu\n", - (unsigned long long)stats.stats.compare_wrong_keylen); - printf(" compare_wrong_rechash = %llu\n", - (unsigned long long)stats.stats.compare_wrong_rechash); - printf(" compare_wrong_keycmp = %llu\n", - (unsigned long long)stats.stats.compare_wrong_keycmp); - printf("transactions = %llu\n", - (unsigned long long)stats.stats.transactions); - printf(" transaction_cancel = %llu\n", - (unsigned long long)stats.stats.transaction_cancel); - printf(" transaction_nest = %llu\n", - (unsigned long long)stats.stats.transaction_nest); - printf(" transaction_expand_file = %llu\n", - (unsigned long long)stats.stats.transaction_expand_file); - printf(" transaction_read_direct = %llu\n", - (unsigned long long)stats.stats.transaction_read_direct); - printf(" transaction_read_direct_fail = %llu\n", - (unsigned long long)stats.stats.transaction_read_direct_fail); - printf(" transaction_write_direct = %llu\n", - (unsigned long long)stats.stats.transaction_write_direct); - printf(" transaction_write_direct_fail = %llu\n", - (unsigned long long)stats.stats.transaction_write_direct_fail); - printf("expands = %llu\n", - (unsigned long long)stats.stats.expands); - printf("frees = %llu\n", - (unsigned long long)stats.stats.frees); - printf("locks = %llu\n", - (unsigned long long)stats.stats.locks); - printf(" lock_lowlevel = %llu\n", - (unsigned long long)stats.stats.lock_lowlevel); - printf(" lock_nonblock = %llu\n", - (unsigned long long)stats.stats.lock_nonblock); - printf(" lock_nonblock_fail = %llu\n", - (unsigned long long)stats.stats.lock_nonblock_fail); - - /* Now clear. */ - tdb_close(*tdb); - *tdb = tdb_open("/tmp/speed.tdb", flags, O_RDWR, 0, attr); -} - -static void tdb_log(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, - void *data) -{ - fprintf(stderr, "tdb:%s:%s:%s\n", - tdb_name(tdb), tdb_errorstr(ecode), message); -} - -int main(int argc, char *argv[]) -{ - unsigned int i, j, num = 1000, stage = 0, stopat = -1; - int flags = TDB_DEFAULT; - bool transaction = false, summary = false; - TDB_DATA key, data; - struct tdb_context *tdb; - struct timeval start, stop; - union tdb_attribute seed, log; - bool do_stats = false; - enum TDB_ERROR ecode; - - /* Try to keep benchmarks even. */ - seed.base.attr = TDB_ATTRIBUTE_SEED; - seed.base.next = NULL; - seed.seed.seed = 0; - - log.base.attr = TDB_ATTRIBUTE_LOG; - log.base.next = &seed; - log.log.fn = tdb_log; - - if (argv[1] && strcmp(argv[1], "--internal") == 0) { - flags = TDB_INTERNAL; - argc--; - argv++; - } - if (argv[1] && strcmp(argv[1], "--transaction") == 0) { - transaction = true; - argc--; - argv++; - } - if (argv[1] && strcmp(argv[1], "--no-sync") == 0) { - flags |= TDB_NOSYNC; - argc--; - argv++; - } - if (argv[1] && strcmp(argv[1], "--summary") == 0) { - summary = true; - argc--; - argv++; - } - if (argv[1] && strcmp(argv[1], "--stats") == 0) { - do_stats = true; - argc--; - argv++; - } - - tdb = tdb_open("/tmp/speed.tdb", flags, O_RDWR|O_CREAT|O_TRUNC, - 0600, &log); - if (!tdb) - err(1, "Opening /tmp/speed.tdb"); - - key.dptr = (void *)&i; - key.dsize = sizeof(i); - data = key; - - if (argv[1]) { - num = atoi(argv[1]); - argv++; - argc--; - } - - if (argv[1]) { - stopat = atoi(argv[1]); - argv++; - argc--; - } - - /* Add 1000 records. */ - printf("Adding %u records: ", num); fflush(stdout); - if (transaction && (ecode = tdb_transaction_start(tdb))) - errx(1, "starting transaction: %s", tdb_errorstr(ecode)); - gettimeofday(&start, NULL); - for (i = 0; i < num; i++) - if ((ecode = tdb_store(tdb, key, data, TDB_INSERT)) != 0) - errx(1, "Inserting key %u in tdb: %s", - i, tdb_errorstr(ecode)); - gettimeofday(&stop, NULL); - if (transaction && (ecode = tdb_transaction_commit(tdb))) - errx(1, "committing transaction: %s", tdb_errorstr(ecode)); - printf(" %zu ns (%zu bytes)\n", - normalize(&start, &stop, num), file_size()); - - if (tdb_check(tdb, NULL, NULL)) - errx(1, "tdb_check failed!"); - if (summary) { - char *sumstr = NULL; - tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &sumstr); - printf("%s\n", sumstr); - free(sumstr); - } - if (do_stats) - dump_and_clear_stats(&tdb, flags, &log); - - if (++stage == stopat) - exit(0); - - /* Finding 1000 records. */ - printf("Finding %u records: ", num); fflush(stdout); - if (transaction && (ecode = tdb_transaction_start(tdb))) - errx(1, "starting transaction: %s", tdb_errorstr(ecode)); - gettimeofday(&start, NULL); - for (i = 0; i < num; i++) { - struct tdb_data dbuf; - if ((ecode = tdb_fetch(tdb, key, &dbuf)) != TDB_SUCCESS - || *(int *)dbuf.dptr != i) { - errx(1, "Fetching key %u in tdb gave %u", - i, ecode ? ecode : *(int *)dbuf.dptr); - } - } - gettimeofday(&stop, NULL); - if (transaction && (ecode = tdb_transaction_commit(tdb))) - errx(1, "committing transaction: %s", tdb_errorstr(ecode)); - printf(" %zu ns (%zu bytes)\n", - normalize(&start, &stop, num), file_size()); - if (tdb_check(tdb, NULL, NULL)) - errx(1, "tdb_check failed!"); - if (summary) { - char *sumstr = NULL; - tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &sumstr); - printf("%s\n", sumstr); - free(sumstr); - } - if (do_stats) - dump_and_clear_stats(&tdb, flags, &log); - if (++stage == stopat) - exit(0); - - /* Missing 1000 records. */ - printf("Missing %u records: ", num); fflush(stdout); - if (transaction && (ecode = tdb_transaction_start(tdb))) - errx(1, "starting transaction: %s", tdb_errorstr(ecode)); - gettimeofday(&start, NULL); - for (i = num; i < num*2; i++) { - struct tdb_data dbuf; - ecode = tdb_fetch(tdb, key, &dbuf); - if (ecode != TDB_ERR_NOEXIST) - errx(1, "Fetching key %u in tdb gave %s", - i, tdb_errorstr(ecode)); - } - gettimeofday(&stop, NULL); - if (transaction && (ecode = tdb_transaction_commit(tdb))) - errx(1, "committing transaction: %s", tdb_errorstr(ecode)); - printf(" %zu ns (%zu bytes)\n", - normalize(&start, &stop, num), file_size()); - if (tdb_check(tdb, NULL, NULL)) - errx(1, "tdb_check failed!"); - if (summary) { - char *sumstr = NULL; - tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &sumstr); - printf("%s\n", sumstr); - free(sumstr); - } - if (do_stats) - dump_and_clear_stats(&tdb, flags, &log); - if (++stage == stopat) - exit(0); - - /* Traverse 1000 records. */ - printf("Traversing %u records: ", num); fflush(stdout); - if (transaction && (ecode = tdb_transaction_start(tdb))) - errx(1, "starting transaction: %s", tdb_errorstr(ecode)); - i = 0; - gettimeofday(&start, NULL); - if (tdb_traverse(tdb, count_record, &i) != num) - errx(1, "Traverse returned wrong number of records"); - if (i != (num - 1) * (num / 2)) - errx(1, "Traverse tallied to %u", i); - gettimeofday(&stop, NULL); - if (transaction && (ecode = tdb_transaction_commit(tdb))) - errx(1, "committing transaction: %s", tdb_errorstr(ecode)); - printf(" %zu ns (%zu bytes)\n", - normalize(&start, &stop, num), file_size()); - if (tdb_check(tdb, NULL, NULL)) - errx(1, "tdb_check failed!"); - if (summary) { - char *sumstr = NULL; - tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &sumstr); - printf("%s\n", sumstr); - free(sumstr); - } - if (do_stats) - dump_and_clear_stats(&tdb, flags, &log); - if (++stage == stopat) - exit(0); - - /* Delete 1000 records (not in order). */ - printf("Deleting %u records: ", num); fflush(stdout); - if (transaction && (ecode = tdb_transaction_start(tdb))) - errx(1, "starting transaction: %s", tdb_errorstr(ecode)); - gettimeofday(&start, NULL); - for (j = 0; j < num; j++) { - i = (j + 100003) % num; - if ((ecode = tdb_delete(tdb, key)) != TDB_SUCCESS) - errx(1, "Deleting key %u in tdb: %s", - i, tdb_errorstr(ecode)); - } - gettimeofday(&stop, NULL); - if (transaction && (ecode = tdb_transaction_commit(tdb))) - errx(1, "committing transaction: %s", tdb_errorstr(ecode)); - printf(" %zu ns (%zu bytes)\n", - normalize(&start, &stop, num), file_size()); - if (tdb_check(tdb, NULL, NULL)) - errx(1, "tdb_check failed!"); - if (summary) { - char *sumstr = NULL; - tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &sumstr); - printf("%s\n", sumstr); - free(sumstr); - } - if (do_stats) - dump_and_clear_stats(&tdb, flags, &log); - if (++stage == stopat) - exit(0); - - /* Re-add 1000 records (not in order). */ - printf("Re-adding %u records: ", num); fflush(stdout); - if (transaction && (ecode = tdb_transaction_start(tdb))) - errx(1, "starting transaction: %s", tdb_errorstr(ecode)); - gettimeofday(&start, NULL); - for (j = 0; j < num; j++) { - i = (j + 100003) % num; - if ((ecode = tdb_store(tdb, key, data, TDB_INSERT)) != 0) - errx(1, "Inserting key %u in tdb: %s", - i, tdb_errorstr(ecode)); - } - gettimeofday(&stop, NULL); - if (transaction && (ecode = tdb_transaction_commit(tdb))) - errx(1, "committing transaction: %s", tdb_errorstr(ecode)); - printf(" %zu ns (%zu bytes)\n", - normalize(&start, &stop, num), file_size()); - if (tdb_check(tdb, NULL, NULL)) - errx(1, "tdb_check failed!"); - if (summary) { - char *sumstr = NULL; - tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &sumstr); - printf("%s\n", sumstr); - free(sumstr); - } - if (do_stats) - dump_and_clear_stats(&tdb, flags, &log); - if (++stage == stopat) - exit(0); - - /* Append 1000 records. */ - if (transaction && (ecode = tdb_transaction_start(tdb))) - errx(1, "starting transaction: %s", tdb_errorstr(ecode)); - printf("Appending %u records: ", num); fflush(stdout); - gettimeofday(&start, NULL); - for (i = 0; i < num; i++) - if ((ecode = tdb_append(tdb, key, data)) != TDB_SUCCESS) - errx(1, "Appending key %u in tdb: %s", - i, tdb_errorstr(ecode)); - gettimeofday(&stop, NULL); - if (transaction && (ecode = tdb_transaction_commit(tdb))) - errx(1, "committing transaction: %s", tdb_errorstr(ecode)); - printf(" %zu ns (%zu bytes)\n", - normalize(&start, &stop, num), file_size()); - if (tdb_check(tdb, NULL, NULL)) - errx(1, "tdb_check failed!"); - if (summary) { - char *sumstr = NULL; - tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &sumstr); - printf("%s\n", sumstr); - free(sumstr); - } - if (++stage == stopat) - exit(0); - - /* Churn 1000 records: not in order! */ - if (transaction && (ecode = tdb_transaction_start(tdb))) - errx(1, "starting transaction: %s", tdb_errorstr(ecode)); - printf("Churning %u records: ", num); fflush(stdout); - gettimeofday(&start, NULL); - for (j = 0; j < num; j++) { - i = (j + 1000019) % num; - if ((ecode = tdb_delete(tdb, key)) != TDB_SUCCESS) - errx(1, "Deleting key %u in tdb: %s", - i, tdb_errorstr(ecode)); - i += num; - if ((ecode = tdb_store(tdb, key, data, TDB_INSERT)) != 0) - errx(1, "Inserting key %u in tdb: %s", - i, tdb_errorstr(ecode)); - } - gettimeofday(&stop, NULL); - if (transaction && (ecode = tdb_transaction_commit(tdb))) - errx(1, "committing transaction: %s", tdb_errorstr(ecode)); - printf(" %zu ns (%zu bytes)\n", - normalize(&start, &stop, num), file_size()); - - if (tdb_check(tdb, NULL, NULL)) - errx(1, "tdb_check failed!"); - if (summary) { - char *sumstr = NULL; - tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &sumstr); - printf("%s\n", sumstr); - free(sumstr); - } - if (do_stats) - dump_and_clear_stats(&tdb, flags, &log); - if (++stage == stopat) - exit(0); - - return 0; -} diff --git a/ccan/tdb2/tools/tdb2dump.c b/ccan/tdb2/tools/tdb2dump.c deleted file mode 100644 index bf9216f7..00000000 --- a/ccan/tdb2/tools/tdb2dump.c +++ /dev/null @@ -1,115 +0,0 @@ -/* - simple tdb2 dump util - Copyright (C) Andrew Tridgell 2001 - Copyright (C) Rusty Russell 2011 - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ -#include "tdb2.h" -#include -#include -#include -#include -#include -#include -#include - -static void print_data(TDB_DATA d) -{ - unsigned char *p = (unsigned char *)d.dptr; - int len = d.dsize; - while (len--) { - if (isprint(*p) && !strchr("\"\\", *p)) { - fputc(*p, stdout); - } else { - printf("\\%02X", *p); - } - p++; - } -} - -static int traverse_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf, void *state) -{ - printf("{\n"); - printf("key(%d) = \"", (int)key.dsize); - print_data(key); - printf("\"\n"); - printf("data(%d) = \"", (int)dbuf.dsize); - print_data(dbuf); - printf("\"\n"); - printf("}\n"); - return 0; -} - -static int dump_tdb(const char *fname, const char *keyname) -{ - struct tdb_context *tdb; - TDB_DATA key, value; - - tdb = tdb_open(fname, 0, O_RDONLY, 0, NULL); - if (!tdb) { - printf("Failed to open %s\n", fname); - return 1; - } - - if (!keyname) { - tdb_traverse(tdb, traverse_fn, NULL); - } else { - key = tdb_mkdata(keyname, strlen(keyname)); - if (tdb_fetch(tdb, key, &value) != 0) { - return 1; - } else { - print_data(value); - free(value.dptr); - } - } - - return 0; -} - -static void usage( void) -{ - printf( "Usage: tdb2dump [options] \n\n"); - printf( " -h this help message\n"); - printf( " -k keyname dumps value of keyname\n"); -} - - int main(int argc, char *argv[]) -{ - char *fname, *keyname=NULL; - int c; - - if (argc < 2) { - printf("Usage: tdb2dump \n"); - exit(1); - } - - while ((c = getopt( argc, argv, "hk:")) != -1) { - switch (c) { - case 'h': - usage(); - exit( 0); - case 'k': - keyname = optarg; - break; - default: - usage(); - exit( 1); - } - } - - fname = argv[optind]; - - return dump_tdb(fname, keyname); -} diff --git a/ccan/tdb2/tools/tdb2restore.c b/ccan/tdb2/tools/tdb2restore.c deleted file mode 100644 index 658215a1..00000000 --- a/ccan/tdb2/tools/tdb2restore.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - tdb2restore -- construct a tdb from tdbdump output. - Copyright (C) Volker Lendecke 2010 - Copyright (C) Simon McVittie 2005 - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include "tdb2.h" -#include -#include -#include -#include -#include -#include - -#define debug_fprintf(file, fmt, ...) do {/*nothing*/} while (0) - -static int read_linehead(FILE *f) -{ - int i, c; - int num_bytes; - char prefix[128]; - - while (1) { - c = getc(f); - if (c == EOF) { - return -1; - } - if (c == '(') { - break; - } - } - for (i=0; idptr = (unsigned char *)malloc(size); - if (d->dptr == NULL) { - return -1; - } - d->dsize = size; - - for (i=0; idptr[i] = (low|high); - } else { - d->dptr[i] = c; - } - } - return 0; -} - -static int swallow(FILE *f, const char *s, int *eof) -{ - char line[128]; - - if (fgets(line, sizeof(line), f) == NULL) { - if (eof != NULL) { - *eof = 1; - } - return -1; - } - if (strcmp(line, s) != 0) { - return -1; - } - return 0; -} - -static bool read_rec(FILE *f, struct tdb_context *tdb, int *eof) -{ - int length; - struct tdb_data key, data; - bool ret = false; - enum TDB_ERROR e; - - key.dptr = NULL; - data.dptr = NULL; - - if (swallow(f, "{\n", eof) == -1) { - goto fail; - } - length = read_linehead(f); - if (length == -1) { - goto fail; - } - if (read_data(f, &key, length) == -1) { - goto fail; - } - if (swallow(f, "\"\n", NULL) == -1) { - goto fail; - } - length = read_linehead(f); - if (length == -1) { - goto fail; - } - if (read_data(f, &data, length) == -1) { - goto fail; - } - if ((swallow(f, "\"\n", NULL) == -1) - || (swallow(f, "}\n", NULL) == -1)) { - goto fail; - } - e = tdb_store(tdb, key, data, TDB_INSERT); - if (e != TDB_SUCCESS) { - fprintf(stderr, "TDB error: %s\n", tdb_errorstr(e)); - goto fail; - } - - ret = true; -fail: - free(key.dptr); - free(data.dptr); - return ret; -} - -static int restore_tdb(const char *fname) -{ - struct tdb_context *tdb; - - tdb = tdb_open(fname, 0, O_RDWR|O_CREAT|O_EXCL, 0666, NULL); - if (!tdb) { - perror("tdb_open"); - fprintf(stderr, "Failed to open %s\n", fname); - return 1; - } - - while (1) { - int eof = 0; - if (!read_rec(stdin, tdb, &eof)) { - if (eof) { - break; - } - return 1; - } - } - if (tdb_close(tdb)) { - fprintf(stderr, "Error closing tdb\n"); - return 1; - } - fprintf(stderr, "EOF\n"); - return 0; -} - -int main(int argc, char *argv[]) -{ - char *fname; - - if (argc < 2) { - printf("Usage: %s dbname < tdbdump_output\n", argv[0]); - exit(1); - } - - fname = argv[1]; - - return restore_tdb(fname); -} diff --git a/ccan/tdb2/tools/tdb2tool.c b/ccan/tdb2/tools/tdb2tool.c deleted file mode 100644 index 8073561b..00000000 --- a/ccan/tdb2/tools/tdb2tool.c +++ /dev/null @@ -1,802 +0,0 @@ -/* - Unix SMB/CIFS implementation. - Samba database functions - Copyright (C) Andrew Tridgell 1999-2000 - Copyright (C) Paul `Rusty' Russell 2000 - Copyright (C) Jeremy Allison 2000 - Copyright (C) Andrew Esh 2001 - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include "tdb2.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int do_command(void); -const char *cmdname; -char *arg1, *arg2; -size_t arg1len, arg2len; -int bIterate = 0; -char *line; -TDB_DATA iterate_kbuf; -char cmdline[1024]; -static int disable_mmap; - -enum commands { - CMD_CREATE_TDB, - CMD_OPEN_TDB, - CMD_TRANSACTION_START, - CMD_TRANSACTION_COMMIT, - CMD_TRANSACTION_CANCEL, - CMD_ERASE, - CMD_DUMP, - CMD_INSERT, - CMD_MOVE, - CMD_STORE, - CMD_SHOW, - CMD_KEYS, - CMD_HEXKEYS, - CMD_DELETE, -#if 0 - CMD_LIST_HASH_FREE, - CMD_LIST_FREE, -#endif - CMD_INFO, - CMD_MMAP, - CMD_SPEED, - CMD_FIRST, - CMD_NEXT, - CMD_SYSTEM, - CMD_CHECK, - CMD_QUIT, - CMD_HELP -}; - -typedef struct { - const char *name; - enum commands cmd; -} COMMAND_TABLE; - -COMMAND_TABLE cmd_table[] = { - {"create", CMD_CREATE_TDB}, - {"open", CMD_OPEN_TDB}, -#if 0 - {"transaction_start", CMD_TRANSACTION_START}, - {"transaction_commit", CMD_TRANSACTION_COMMIT}, - {"transaction_cancel", CMD_TRANSACTION_CANCEL}, -#endif - {"erase", CMD_ERASE}, - {"dump", CMD_DUMP}, - {"insert", CMD_INSERT}, - {"move", CMD_MOVE}, - {"store", CMD_STORE}, - {"show", CMD_SHOW}, - {"keys", CMD_KEYS}, - {"hexkeys", CMD_HEXKEYS}, - {"delete", CMD_DELETE}, -#if 0 - {"list", CMD_LIST_HASH_FREE}, - {"free", CMD_LIST_FREE}, -#endif - {"info", CMD_INFO}, - {"speed", CMD_SPEED}, - {"mmap", CMD_MMAP}, - {"first", CMD_FIRST}, - {"1", CMD_FIRST}, - {"next", CMD_NEXT}, - {"n", CMD_NEXT}, - {"check", CMD_CHECK}, - {"quit", CMD_QUIT}, - {"q", CMD_QUIT}, - {"!", CMD_SYSTEM}, - {NULL, CMD_HELP} -}; - -struct timeval tp1,tp2; - -static void _start_timer(void) -{ - gettimeofday(&tp1,NULL); -} - -static double _end_timer(void) -{ - gettimeofday(&tp2,NULL); - return((tp2.tv_sec - tp1.tv_sec) + - (tp2.tv_usec - tp1.tv_usec)*1.0e-6); -} - -static void tdb_log(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, - void *data) -{ - fprintf(stderr, "tdb:%s:%s:%s\n", - tdb_name(tdb), tdb_errorstr(ecode), message); -} - -/* a tdb tool for manipulating a tdb database */ - -static struct tdb_context *tdb; - -static int print_rec(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state); -static int print_key(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state); -static int print_hexkey(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state); - -static void print_asc(const char *buf,int len) -{ - int i; - - /* We're probably printing ASCII strings so don't try to display - the trailing NULL character. */ - - if (buf[len - 1] == 0) - len--; - - for (i=0;i8) printf(" "); - while (n--) printf(" "); - - n = i%16; - if (n > 8) n = 8; - print_asc(&buf[i-(i%16)],n); printf(" "); - n = (i%16) - n; - if (n>0) print_asc(&buf[i-n],n); - printf("\n"); - } -} - -static void help(void) -{ - printf("\n" -"tdbtool: \n" -" create dbname : create a database\n" -" open dbname : open an existing database\n" -" openjh dbname : open an existing database (jenkins hash)\n" -" transaction_start : start a transaction\n" -" transaction_commit : commit a transaction\n" -" transaction_cancel : cancel a transaction\n" -" erase : erase the database\n" -" dump : dump the database as strings\n" -" keys : dump the database keys as strings\n" -" hexkeys : dump the database keys as hex values\n" -" info : print summary info about the database\n" -" insert key data : insert a record\n" -" move key file : move a record to a destination tdb\n" -" store key data : store a record (replace)\n" -" show key : show a record by key\n" -" delete key : delete a record by key\n" -#if 0 -" list : print the database hash table and freelist\n" -" free : print the database freelist\n" -#endif -" check : check the integrity of an opened database\n" -" speed : perform speed tests on the database\n" -" ! command : execute system command\n" -" 1 | first : print the first record\n" -" n | next : print the next record\n" -" q | quit : terminate\n" -" \\n : repeat 'next' command\n" -"\n"); -} - -static void terror(enum TDB_ERROR err, const char *why) -{ - if (err != TDB_SUCCESS) - printf("%s:%s\n", tdb_errorstr(err), why); - else - printf("%s\n", why); -} - -static void create_tdb(const char *tdbname) -{ - union tdb_attribute log_attr; - log_attr.base.attr = TDB_ATTRIBUTE_LOG; - log_attr.base.next = NULL; - log_attr.log.fn = tdb_log; - - if (tdb) tdb_close(tdb); - tdb = tdb_open(tdbname, (disable_mmap?TDB_NOMMAP:0), - O_RDWR | O_CREAT | O_TRUNC, 0600, &log_attr); - if (!tdb) { - printf("Could not create %s: %s\n", tdbname, strerror(errno)); - } -} - -static void open_tdb(const char *tdbname) -{ - union tdb_attribute log_attr; - log_attr.base.attr = TDB_ATTRIBUTE_LOG; - log_attr.base.next = NULL; - log_attr.log.fn = tdb_log; - - if (tdb) tdb_close(tdb); - tdb = tdb_open(tdbname, disable_mmap?TDB_NOMMAP:0, O_RDWR, 0600, - &log_attr); - if (!tdb) { - printf("Could not open %s: %s\n", tdbname, strerror(errno)); - } -} - -static void insert_tdb(char *keyname, size_t keylen, char* data, size_t datalen) -{ - TDB_DATA key, dbuf; - enum TDB_ERROR ecode; - - if ((keyname == NULL) || (keylen == 0)) { - terror(TDB_SUCCESS, "need key"); - return; - } - - key.dptr = (unsigned char *)keyname; - key.dsize = keylen; - dbuf.dptr = (unsigned char *)data; - dbuf.dsize = datalen; - - ecode = tdb_store(tdb, key, dbuf, TDB_INSERT); - if (ecode) { - terror(ecode, "insert failed"); - } -} - -static void store_tdb(char *keyname, size_t keylen, char* data, size_t datalen) -{ - TDB_DATA key, dbuf; - enum TDB_ERROR ecode; - - if ((keyname == NULL) || (keylen == 0)) { - terror(TDB_SUCCESS, "need key"); - return; - } - - if ((data == NULL) || (datalen == 0)) { - terror(TDB_SUCCESS, "need data"); - return; - } - - key.dptr = (unsigned char *)keyname; - key.dsize = keylen; - dbuf.dptr = (unsigned char *)data; - dbuf.dsize = datalen; - - printf("Storing key:\n"); - print_rec(tdb, key, dbuf, NULL); - - ecode = tdb_store(tdb, key, dbuf, TDB_REPLACE); - if (ecode) { - terror(ecode, "store failed"); - } -} - -static void show_tdb(char *keyname, size_t keylen) -{ - TDB_DATA key, dbuf; - enum TDB_ERROR ecode; - - if ((keyname == NULL) || (keylen == 0)) { - terror(TDB_SUCCESS, "need key"); - return; - } - - key.dptr = (unsigned char *)keyname; - key.dsize = keylen; - - ecode = tdb_fetch(tdb, key, &dbuf); - if (ecode) { - terror(ecode, "fetch failed"); - return; - } - - print_rec(tdb, key, dbuf, NULL); - - free( dbuf.dptr ); -} - -static void delete_tdb(char *keyname, size_t keylen) -{ - TDB_DATA key; - enum TDB_ERROR ecode; - - if ((keyname == NULL) || (keylen == 0)) { - terror(TDB_SUCCESS, "need key"); - return; - } - - key.dptr = (unsigned char *)keyname; - key.dsize = keylen; - - ecode = tdb_delete(tdb, key); - if (ecode) { - terror(ecode, "delete failed"); - } -} - -static void move_rec(char *keyname, size_t keylen, char* tdbname) -{ - TDB_DATA key, dbuf; - struct tdb_context *dst_tdb; - enum TDB_ERROR ecode; - - if ((keyname == NULL) || (keylen == 0)) { - terror(TDB_SUCCESS, "need key"); - return; - } - - if ( !tdbname ) { - terror(TDB_SUCCESS, "need destination tdb name"); - return; - } - - key.dptr = (unsigned char *)keyname; - key.dsize = keylen; - - ecode = tdb_fetch(tdb, key, &dbuf); - if (ecode) { - terror(ecode, "fetch failed"); - return; - } - - print_rec(tdb, key, dbuf, NULL); - - dst_tdb = tdb_open(tdbname, 0, O_RDWR, 0600, NULL); - if ( !dst_tdb ) { - terror(TDB_SUCCESS, "unable to open destination tdb"); - return; - } - - ecode = tdb_store( dst_tdb, key, dbuf, TDB_REPLACE); - if (ecode) - terror(ecode, "failed to move record"); - else - printf("record moved\n"); - - tdb_close( dst_tdb ); -} - -static int print_rec(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state) -{ - printf("\nkey %d bytes\n", (int)key.dsize); - print_asc((const char *)key.dptr, key.dsize); - printf("\ndata %d bytes\n", (int)dbuf.dsize); - print_data((const char *)dbuf.dptr, dbuf.dsize); - return 0; -} - -static int print_key(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state) -{ - printf("key %d bytes: ", (int)key.dsize); - print_asc((const char *)key.dptr, key.dsize); - printf("\n"); - return 0; -} - -static int print_hexkey(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state) -{ - printf("key %d bytes\n", (int)key.dsize); - print_data((const char *)key.dptr, key.dsize); - printf("\n"); - return 0; -} - -static int total_bytes; - -static int traverse_fn(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, void *state) -{ - total_bytes += dbuf.dsize; - return 0; -} - -static void info_tdb(void) -{ - enum TDB_ERROR ecode; - char *summary; - - ecode = tdb_summary(tdb, TDB_SUMMARY_HISTOGRAMS, &summary); - - if (ecode) { - terror(ecode, "Getting summary"); - } else { - printf("%s", summary); - free(summary); - } -} - -static void speed_tdb(const char *tlimit) -{ - unsigned timelimit = tlimit?atoi(tlimit):0; - double t; - int ops; - if (timelimit == 0) timelimit = 5; - - ops = 0; - printf("Testing store speed for %u seconds\n", timelimit); - _start_timer(); - do { - long int r = random(); - TDB_DATA key, dbuf; - key = tdb_mkdata("store test", strlen("store test")); - dbuf.dptr = (unsigned char *)&r; - dbuf.dsize = sizeof(r); - tdb_store(tdb, key, dbuf, TDB_REPLACE); - t = _end_timer(); - ops++; - } while (t < timelimit); - printf("%10.3f ops/sec\n", ops/t); - - ops = 0; - printf("Testing fetch speed for %u seconds\n", timelimit); - _start_timer(); - do { - long int r = random(); - TDB_DATA key, dbuf; - key = tdb_mkdata("store test", strlen("store test")); - dbuf.dptr = (unsigned char *)&r; - dbuf.dsize = sizeof(r); - tdb_fetch(tdb, key, &dbuf); - t = _end_timer(); - ops++; - } while (t < timelimit); - printf("%10.3f ops/sec\n", ops/t); - - ops = 0; - printf("Testing transaction speed for %u seconds\n", timelimit); - _start_timer(); - do { - long int r = random(); - TDB_DATA key, dbuf; - key = tdb_mkdata("transaction test", strlen("transaction test")); - dbuf.dptr = (unsigned char *)&r; - dbuf.dsize = sizeof(r); - tdb_transaction_start(tdb); - tdb_store(tdb, key, dbuf, TDB_REPLACE); - tdb_transaction_commit(tdb); - t = _end_timer(); - ops++; - } while (t < timelimit); - printf("%10.3f ops/sec\n", ops/t); - - ops = 0; - printf("Testing traverse speed for %u seconds\n", timelimit); - _start_timer(); - do { - tdb_traverse(tdb, traverse_fn, NULL); - t = _end_timer(); - ops++; - } while (t < timelimit); - printf("%10.3f ops/sec\n", ops/t); -} - -static void toggle_mmap(void) -{ - disable_mmap = !disable_mmap; - if (disable_mmap) { - printf("mmap is disabled\n"); - } else { - printf("mmap is enabled\n"); - } -} - -static char *tdb_getline(const char *prompt) -{ - static char thisline[1024]; - char *p; - fputs(prompt, stdout); - thisline[0] = 0; - p = fgets(thisline, sizeof(thisline)-1, stdin); - if (p) p = strchr(p, '\n'); - if (p) *p = 0; - return p?thisline:NULL; -} - -static int do_delete_fn(struct tdb_context *the_tdb, TDB_DATA key, TDB_DATA dbuf, - void *state) -{ - return tdb_delete(the_tdb, key); -} - -static void first_record(struct tdb_context *the_tdb, TDB_DATA *pkey) -{ - TDB_DATA dbuf; - enum TDB_ERROR ecode; - ecode = tdb_firstkey(the_tdb, pkey); - if (!ecode) - ecode = tdb_fetch(the_tdb, *pkey, &dbuf); - if (ecode) terror(ecode, "fetch failed"); - else { - print_rec(the_tdb, *pkey, dbuf, NULL); - } -} - -static void next_record(struct tdb_context *the_tdb, TDB_DATA *pkey) -{ - TDB_DATA dbuf; - enum TDB_ERROR ecode; - ecode = tdb_nextkey(the_tdb, pkey); - - if (!ecode) - ecode = tdb_fetch(the_tdb, *pkey, &dbuf); - if (ecode) - terror(ecode, "fetch failed"); - else - print_rec(the_tdb, *pkey, dbuf, NULL); -} - -static void check_db(struct tdb_context *the_tdb) -{ - if (!the_tdb) { - printf("Error: No database opened!\n"); - } else { - if (tdb_check(the_tdb, NULL, NULL) != 0) - printf("Integrity check for the opened database failed.\n"); - else - printf("Database integrity is OK.\n"); - } -} - -static int do_command(void) -{ - COMMAND_TABLE *ctp = cmd_table; - enum commands mycmd = CMD_HELP; - int cmd_len; - - if (cmdname && strlen(cmdname) == 0) { - mycmd = CMD_NEXT; - } else { - while (ctp->name) { - cmd_len = strlen(ctp->name); - if (strncmp(ctp->name,cmdname,cmd_len) == 0) { - mycmd = ctp->cmd; - break; - } - ctp++; - } - } - - switch (mycmd) { - case CMD_CREATE_TDB: - bIterate = 0; - create_tdb(arg1); - return 0; - case CMD_OPEN_TDB: - bIterate = 0; - open_tdb(arg1); - return 0; - case CMD_SYSTEM: - /* Shell command */ - if (system(arg1) == -1) { - terror(TDB_SUCCESS, "system() call failed\n"); - } - return 0; - case CMD_QUIT: - return 1; - default: - /* all the rest require a open database */ - if (!tdb) { - bIterate = 0; - terror(TDB_SUCCESS, "database not open"); - help(); - return 0; - } - switch (mycmd) { - case CMD_TRANSACTION_START: - bIterate = 0; - tdb_transaction_start(tdb); - return 0; - case CMD_TRANSACTION_COMMIT: - bIterate = 0; - tdb_transaction_commit(tdb); - return 0; - case CMD_TRANSACTION_CANCEL: - bIterate = 0; - tdb_transaction_cancel(tdb); - return 0; - case CMD_ERASE: - bIterate = 0; - tdb_traverse(tdb, do_delete_fn, NULL); - return 0; - case CMD_DUMP: - bIterate = 0; - tdb_traverse(tdb, print_rec, NULL); - return 0; - case CMD_INSERT: - bIterate = 0; - insert_tdb(arg1, arg1len,arg2,arg2len); - return 0; - case CMD_MOVE: - bIterate = 0; - move_rec(arg1,arg1len,arg2); - return 0; - case CMD_STORE: - bIterate = 0; - store_tdb(arg1,arg1len,arg2,arg2len); - return 0; - case CMD_SHOW: - bIterate = 0; - show_tdb(arg1, arg1len); - return 0; - case CMD_KEYS: - tdb_traverse(tdb, print_key, NULL); - return 0; - case CMD_HEXKEYS: - tdb_traverse(tdb, print_hexkey, NULL); - return 0; - case CMD_DELETE: - bIterate = 0; - delete_tdb(arg1,arg1len); - return 0; -#if 0 - case CMD_LIST_HASH_FREE: - tdb_dump_all(tdb); - return 0; - case CMD_LIST_FREE: - tdb_printfreelist(tdb); - return 0; -#endif - case CMD_INFO: - info_tdb(); - return 0; - case CMD_SPEED: - speed_tdb(arg1); - return 0; - case CMD_MMAP: - toggle_mmap(); - return 0; - case CMD_FIRST: - bIterate = 1; - first_record(tdb, &iterate_kbuf); - return 0; - case CMD_NEXT: - if (bIterate) - next_record(tdb, &iterate_kbuf); - return 0; - case CMD_CHECK: - check_db(tdb); - return 0; - case CMD_HELP: - help(); - return 0; - case CMD_CREATE_TDB: - case CMD_OPEN_TDB: - case CMD_SYSTEM: - case CMD_QUIT: - /* - * unhandled commands. cases included here to avoid compiler - * warnings. - */ - return 0; - } - } - - return 0; -} - -static char *convert_string(char *instring, size_t *sizep) -{ - size_t length = 0; - char *outp, *inp; - char temp[3]; - - outp = inp = instring; - - while (*inp) { - if (*inp == '\\') { - inp++; - if (*inp && strchr("0123456789abcdefABCDEF",(int)*inp)) { - temp[0] = *inp++; - temp[1] = '\0'; - if (*inp && strchr("0123456789abcdefABCDEF",(int)*inp)) { - temp[1] = *inp++; - temp[2] = '\0'; - } - *outp++ = (char)strtol((const char *)temp,NULL,16); - } else { - *outp++ = *inp++; - } - } else { - *outp++ = *inp++; - } - length++; - } - *sizep = length; - return instring; -} - -int main(int argc, char *argv[]) -{ - cmdname = ""; - arg1 = NULL; - arg1len = 0; - arg2 = NULL; - arg2len = 0; - - if (argv[1]) { - cmdname = "open"; - arg1 = argv[1]; - do_command(); - cmdname = ""; - arg1 = NULL; - } - - switch (argc) { - case 1: - case 2: - /* Interactive mode */ - while ((cmdname = tdb_getline("tdb> "))) { - arg2 = arg1 = NULL; - if ((arg1 = strchr((const char *)cmdname,' ')) != NULL) { - arg1++; - arg2 = arg1; - while (*arg2) { - if (*arg2 == ' ') { - *arg2++ = '\0'; - break; - } - if ((*arg2++ == '\\') && (*arg2 == ' ')) { - arg2++; - } - } - } - if (arg1) arg1 = convert_string(arg1,&arg1len); - if (arg2) arg2 = convert_string(arg2,&arg2len); - if (do_command()) break; - } - break; - case 5: - arg2 = convert_string(argv[4],&arg2len); - case 4: - arg1 = convert_string(argv[3],&arg1len); - case 3: - cmdname = argv[2]; - default: - do_command(); - break; - } - - if (tdb) tdb_close(tdb); - - return 0; -} diff --git a/ccan/tdb2/tools/tdb2torture.c b/ccan/tdb2/tools/tdb2torture.c deleted file mode 100644 index 29ecb6af..00000000 --- a/ccan/tdb2/tools/tdb2torture.c +++ /dev/null @@ -1,498 +0,0 @@ -/* this tests tdb by doing lots of ops from several simultaneous - writers - that stresses the locking code. -*/ - -#include "tdb2.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//#define REOPEN_PROB 30 -#define DELETE_PROB 8 -#define STORE_PROB 4 -#define APPEND_PROB 6 -#define TRANSACTION_PROB 10 -#define TRANSACTION_PREPARE_PROB 2 -#define LOCKSTORE_PROB 5 -#define TRAVERSE_PROB 20 -#define TRAVERSE_MOD_PROB 100 -#define TRAVERSE_ABORT_PROB 500 -#define CULL_PROB 100 -#define KEYLEN 3 -#define DATALEN 100 - -static struct tdb_context *db; -static int in_transaction; -static int in_traverse; -static int error_count; -#if TRANSACTION_PROB -static int always_transaction = 0; -#endif -static int loopnum; -static int count_pipe; -static union tdb_attribute log_attr; -static union tdb_attribute seed_attr; - -static void tdb_log(struct tdb_context *tdb, - enum tdb_log_level level, - enum TDB_ERROR ecode, - const char *message, - void *data) -{ - printf("tdb:%s:%s:%s\n", - tdb_name(tdb), tdb_errorstr(ecode), message); - fflush(stdout); -#if 0 - { - char str[200]; - signal(SIGUSR1, SIG_IGN); - sprintf(str,"xterm -e gdb /proc/%d/exe %d", getpid(), getpid()); - system(str); - } -#endif -} - -#include "../private.h" - -static void segv_handler(int sig, siginfo_t *info, void *p) -{ - char string[100]; - - sprintf(string, "%u: death at %p (map_ptr %p, map_size %zu)\n", - getpid(), info->si_addr, db->file->map_ptr, - (size_t)db->file->map_size); - if (write(2, string, strlen(string)) > 0) - sleep(60); - _exit(11); -} - -static void fatal(struct tdb_context *tdb, const char *why) -{ - fprintf(stderr, "%u:%s:%s\n", getpid(), why, - tdb ? tdb_errorstr(tdb_error(tdb)) : "(no tdb)"); - error_count++; -} - -static char *randbuf(int len) -{ - char *buf; - int i; - buf = (char *)malloc(len+1); - - for (i=0;i. -*/ - -#include "private.h" -#define SAFE_FREE(x) do { if ((x) != NULL) {free((void *)x); (x)=NULL;} } while(0) - -/* - transaction design: - - - only allow a single transaction at a time per database. This makes - using the transaction API simpler, as otherwise the caller would - have to cope with temporary failures in transactions that conflict - with other current transactions - - - keep the transaction recovery information in the same file as the - database, using a special 'transaction recovery' record pointed at - by the header. This removes the need for extra journal files as - used by some other databases - - - dynamically allocated the transaction recover record, re-using it - for subsequent transactions. If a larger record is needed then - tdb_free() the old record to place it on the normal tdb freelist - before allocating the new record - - - during transactions, keep a linked list of writes all that have - been performed by intercepting all tdb_write() calls. The hooked - transaction versions of tdb_read() and tdb_write() check this - linked list and try to use the elements of the list in preference - to the real database. - - - don't allow any locks to be held when a transaction starts, - otherwise we can end up with deadlock (plus lack of lock nesting - in POSIX locks would mean the lock is lost) - - - if the caller gains a lock during the transaction but doesn't - release it then fail the commit - - - allow for nested calls to tdb_transaction_start(), re-using the - existing transaction record. If the inner transaction is canceled - then a subsequent commit will fail - - - keep a mirrored copy of the tdb hash chain heads to allow for the - fast hash heads scan on traverse, updating the mirrored copy in - the transaction version of tdb_write - - - allow callers to mix transaction and non-transaction use of tdb, - although once a transaction is started then an exclusive lock is - gained until the transaction is committed or canceled - - - the commit stategy involves first saving away all modified data - into a linearised buffer in the transaction recovery area, then - marking the transaction recovery area with a magic value to - indicate a valid recovery record. In total 4 fsync/msync calls are - needed per commit to prevent race conditions. It might be possible - to reduce this to 3 or even 2 with some more work. - - - check for a valid recovery record on open of the tdb, while the - open lock is held. Automatically recover from the transaction - recovery area if needed, then continue with the open as - usual. This allows for smooth crash recovery with no administrator - intervention. - - - if TDB_NOSYNC is passed to flags in tdb_open then transactions are - still available, but no transaction recovery area is used and no - fsync/msync calls are made. -*/ - -/* - hold the context of any current transaction -*/ -struct tdb_transaction { - /* the original io methods - used to do IOs to the real db */ - const struct tdb_methods *io_methods; - - /* the list of transaction blocks. When a block is first - written to, it gets created in this list */ - uint8_t **blocks; - size_t num_blocks; - size_t last_block_size; /* number of valid bytes in the last block */ - - /* non-zero when an internal transaction error has - occurred. All write operations will then fail until the - transaction is ended */ - int transaction_error; - - /* when inside a transaction we need to keep track of any - nested tdb_transaction_start() calls, as these are allowed, - but don't create a new transaction */ - unsigned int nesting; - - /* set when a prepare has already occurred */ - bool prepared; - tdb_off_t magic_offset; - - /* old file size before transaction */ - tdb_len_t old_map_size; -}; - -/* This doesn't really need to be pagesize, but we use it for similar reasons. */ -#define PAGESIZE 65536 - -/* - read while in a transaction. We need to check first if the data is in our list - of transaction elements, then if not do a real read -*/ -static enum TDB_ERROR transaction_read(struct tdb_context *tdb, tdb_off_t off, - void *buf, tdb_len_t len) -{ - size_t blk; - enum TDB_ERROR ecode; - - /* break it down into block sized ops */ - while (len + (off % PAGESIZE) > PAGESIZE) { - tdb_len_t len2 = PAGESIZE - (off % PAGESIZE); - ecode = transaction_read(tdb, off, buf, len2); - if (ecode != TDB_SUCCESS) { - return ecode; - } - len -= len2; - off += len2; - buf = (void *)(len2 + (char *)buf); - } - - if (len == 0) { - return TDB_SUCCESS; - } - - blk = off / PAGESIZE; - - /* see if we have it in the block list */ - if (tdb->tdb2.transaction->num_blocks <= blk || - tdb->tdb2.transaction->blocks[blk] == NULL) { - /* nope, do a real read */ - ecode = tdb->tdb2.transaction->io_methods->tread(tdb, off, buf, len); - if (ecode != TDB_SUCCESS) { - goto fail; - } - return 0; - } - - /* it is in the block list. Now check for the last block */ - if (blk == tdb->tdb2.transaction->num_blocks-1) { - if (len > tdb->tdb2.transaction->last_block_size) { - ecode = TDB_ERR_IO; - goto fail; - } - } - - /* now copy it out of this block */ - memcpy(buf, tdb->tdb2.transaction->blocks[blk] + (off % PAGESIZE), len); - return TDB_SUCCESS; - -fail: - tdb->tdb2.transaction->transaction_error = 1; - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "transaction_read: failed at off=%zu len=%zu", - (size_t)off, (size_t)len); -} - - -/* - write while in a transaction -*/ -static enum TDB_ERROR transaction_write(struct tdb_context *tdb, tdb_off_t off, - const void *buf, tdb_len_t len) -{ - size_t blk; - enum TDB_ERROR ecode; - - /* Only a commit is allowed on a prepared transaction */ - if (tdb->tdb2.transaction->prepared) { - ecode = tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_ERROR, - "transaction_write: transaction already" - " prepared, write not allowed"); - goto fail; - } - - /* break it up into block sized chunks */ - while (len + (off % PAGESIZE) > PAGESIZE) { - tdb_len_t len2 = PAGESIZE - (off % PAGESIZE); - ecode = transaction_write(tdb, off, buf, len2); - if (ecode != TDB_SUCCESS) { - return ecode; - } - len -= len2; - off += len2; - if (buf != NULL) { - buf = (const void *)(len2 + (const char *)buf); - } - } - - if (len == 0) { - return TDB_SUCCESS; - } - - blk = off / PAGESIZE; - off = off % PAGESIZE; - - if (tdb->tdb2.transaction->num_blocks <= blk) { - uint8_t **new_blocks; - /* expand the blocks array */ - if (tdb->tdb2.transaction->blocks == NULL) { - new_blocks = (uint8_t **)malloc( - (blk+1)*sizeof(uint8_t *)); - } else { - new_blocks = (uint8_t **)realloc( - tdb->tdb2.transaction->blocks, - (blk+1)*sizeof(uint8_t *)); - } - if (new_blocks == NULL) { - ecode = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "transaction_write:" - " failed to allocate"); - goto fail; - } - memset(&new_blocks[tdb->tdb2.transaction->num_blocks], 0, - (1+(blk - tdb->tdb2.transaction->num_blocks))*sizeof(uint8_t *)); - tdb->tdb2.transaction->blocks = new_blocks; - tdb->tdb2.transaction->num_blocks = blk+1; - tdb->tdb2.transaction->last_block_size = 0; - } - - /* allocate and fill a block? */ - if (tdb->tdb2.transaction->blocks[blk] == NULL) { - tdb->tdb2.transaction->blocks[blk] = (uint8_t *)calloc(PAGESIZE, 1); - if (tdb->tdb2.transaction->blocks[blk] == NULL) { - ecode = tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "transaction_write:" - " failed to allocate"); - goto fail; - } - if (tdb->tdb2.transaction->old_map_size > blk * PAGESIZE) { - tdb_len_t len2 = PAGESIZE; - if (len2 + (blk * PAGESIZE) > tdb->tdb2.transaction->old_map_size) { - len2 = tdb->tdb2.transaction->old_map_size - (blk * PAGESIZE); - } - ecode = tdb->tdb2.transaction->io_methods->tread(tdb, - blk * PAGESIZE, - tdb->tdb2.transaction->blocks[blk], - len2); - if (ecode != TDB_SUCCESS) { - ecode = tdb_logerr(tdb, ecode, - TDB_LOG_ERROR, - "transaction_write:" - " failed to" - " read old block: %s", - strerror(errno)); - SAFE_FREE(tdb->tdb2.transaction->blocks[blk]); - goto fail; - } - if (blk == tdb->tdb2.transaction->num_blocks-1) { - tdb->tdb2.transaction->last_block_size = len2; - } - } - } - - /* overwrite part of an existing block */ - if (buf == NULL) { - memset(tdb->tdb2.transaction->blocks[blk] + off, 0, len); - } else { - memcpy(tdb->tdb2.transaction->blocks[blk] + off, buf, len); - } - if (blk == tdb->tdb2.transaction->num_blocks-1) { - if (len + off > tdb->tdb2.transaction->last_block_size) { - tdb->tdb2.transaction->last_block_size = len + off; - } - } - - return TDB_SUCCESS; - -fail: - tdb->tdb2.transaction->transaction_error = 1; - return ecode; -} - - -/* - write while in a transaction - this variant never expands the transaction blocks, it only - updates existing blocks. This means it cannot change the recovery size -*/ -static void transaction_write_existing(struct tdb_context *tdb, tdb_off_t off, - const void *buf, tdb_len_t len) -{ - size_t blk; - - /* break it up into block sized chunks */ - while (len + (off % PAGESIZE) > PAGESIZE) { - tdb_len_t len2 = PAGESIZE - (off % PAGESIZE); - transaction_write_existing(tdb, off, buf, len2); - len -= len2; - off += len2; - if (buf != NULL) { - buf = (const void *)(len2 + (const char *)buf); - } - } - - if (len == 0) { - return; - } - - blk = off / PAGESIZE; - off = off % PAGESIZE; - - if (tdb->tdb2.transaction->num_blocks <= blk || - tdb->tdb2.transaction->blocks[blk] == NULL) { - return; - } - - if (blk == tdb->tdb2.transaction->num_blocks-1 && - off + len > tdb->tdb2.transaction->last_block_size) { - if (off >= tdb->tdb2.transaction->last_block_size) { - return; - } - len = tdb->tdb2.transaction->last_block_size - off; - } - - /* overwrite part of an existing block */ - memcpy(tdb->tdb2.transaction->blocks[blk] + off, buf, len); -} - - -/* - out of bounds check during a transaction -*/ -static enum TDB_ERROR transaction_oob(struct tdb_context *tdb, - tdb_off_t off, tdb_len_t len, bool probe) -{ - if ((off + len >= off && off + len <= tdb->file->map_size) || probe) { - return TDB_SUCCESS; - } - - tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_oob len %lld beyond transaction size %lld", - (long long)(off + len), - (long long)tdb->file->map_size); - return TDB_ERR_IO; -} - -/* - transaction version of tdb_expand(). -*/ -static enum TDB_ERROR transaction_expand_file(struct tdb_context *tdb, - tdb_off_t addition) -{ - enum TDB_ERROR ecode; - - /* add a write to the transaction elements, so subsequent - reads see the zero data */ - ecode = transaction_write(tdb, tdb->file->map_size, NULL, addition); - if (ecode == TDB_SUCCESS) { - tdb->file->map_size += addition; - } - return ecode; -} - -static void *transaction_direct(struct tdb_context *tdb, tdb_off_t off, - size_t len, bool write_mode) -{ - size_t blk = off / PAGESIZE, end_blk; - - /* This is wrong for zero-length blocks, but will fail gracefully */ - end_blk = (off + len - 1) / PAGESIZE; - - /* Can only do direct if in single block and we've already copied. */ - if (write_mode) { - tdb->stats.transaction_write_direct++; - if (blk != end_blk - || blk >= tdb->tdb2.transaction->num_blocks - || tdb->tdb2.transaction->blocks[blk] == NULL) { - tdb->stats.transaction_write_direct_fail++; - return NULL; - } - return tdb->tdb2.transaction->blocks[blk] + off % PAGESIZE; - } - - tdb->stats.transaction_read_direct++; - /* Single which we have copied? */ - if (blk == end_blk - && blk < tdb->tdb2.transaction->num_blocks - && tdb->tdb2.transaction->blocks[blk]) - return tdb->tdb2.transaction->blocks[blk] + off % PAGESIZE; - - /* Otherwise must be all not copied. */ - while (blk <= end_blk) { - if (blk >= tdb->tdb2.transaction->num_blocks) - break; - if (tdb->tdb2.transaction->blocks[blk]) { - tdb->stats.transaction_read_direct_fail++; - return NULL; - } - blk++; - } - return tdb->tdb2.transaction->io_methods->direct(tdb, off, len, false); -} - -static const struct tdb_methods transaction_methods = { - transaction_read, - transaction_write, - transaction_oob, - transaction_expand_file, - transaction_direct, -}; - -/* - sync to disk -*/ -static enum TDB_ERROR transaction_sync(struct tdb_context *tdb, - tdb_off_t offset, tdb_len_t length) -{ - if (tdb->flags & TDB_NOSYNC) { - return TDB_SUCCESS; - } - - if (fsync(tdb->file->fd) != 0) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_transaction: fsync failed: %s", - strerror(errno)); - } -#ifdef MS_SYNC - if (tdb->file->map_ptr) { - tdb_off_t moffset = offset & ~(getpagesize()-1); - if (msync(moffset + (char *)tdb->file->map_ptr, - length + (offset - moffset), MS_SYNC) != 0) { - return tdb_logerr(tdb, TDB_ERR_IO, TDB_LOG_ERROR, - "tdb_transaction: msync failed: %s", - strerror(errno)); - } - } -#endif - return TDB_SUCCESS; -} - - -static void _tdb_transaction_cancel(struct tdb_context *tdb) -{ - int i; - enum TDB_ERROR ecode; - - if (tdb->tdb2.transaction == NULL) { - tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb_transaction_cancel: no transaction"); - return; - } - - if (tdb->tdb2.transaction->nesting != 0) { - tdb->tdb2.transaction->transaction_error = 1; - tdb->tdb2.transaction->nesting--; - return; - } - - tdb->file->map_size = tdb->tdb2.transaction->old_map_size; - - /* free all the transaction blocks */ - for (i=0;itdb2.transaction->num_blocks;i++) { - if (tdb->tdb2.transaction->blocks[i] != NULL) { - free(tdb->tdb2.transaction->blocks[i]); - } - } - SAFE_FREE(tdb->tdb2.transaction->blocks); - - if (tdb->tdb2.transaction->magic_offset) { - const struct tdb_methods *methods = tdb->tdb2.transaction->io_methods; - uint64_t invalid = TDB_RECOVERY_INVALID_MAGIC; - - /* remove the recovery marker */ - ecode = methods->twrite(tdb, tdb->tdb2.transaction->magic_offset, - &invalid, sizeof(invalid)); - if (ecode == TDB_SUCCESS) - ecode = transaction_sync(tdb, - tdb->tdb2.transaction->magic_offset, - sizeof(invalid)); - if (ecode != TDB_SUCCESS) { - tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_cancel: failed to remove" - " recovery magic"); - } - } - - if (tdb->file->allrecord_lock.count) - tdb_allrecord_unlock(tdb, tdb->file->allrecord_lock.ltype); - - /* restore the normal io methods */ - tdb->tdb2.io = tdb->tdb2.transaction->io_methods; - - tdb_transaction_unlock(tdb, F_WRLCK); - - if (tdb_has_open_lock(tdb)) - tdb_unlock_open(tdb, F_WRLCK); - - SAFE_FREE(tdb->tdb2.transaction); -} - -/* - start a tdb transaction. No token is returned, as only a single - transaction is allowed to be pending per tdb_context -*/ -enum TDB_ERROR tdb_transaction_start(struct tdb_context *tdb) -{ - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_VERSION1) { - if (tdb1_transaction_start(tdb) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - - tdb->stats.transactions++; - /* some sanity checks */ - if (tdb->flags & TDB_INTERNAL) { - return tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_transaction_start:" - " cannot start a" - " transaction on an" - " internal tdb"); - } - - if (tdb->flags & TDB_RDONLY) { - return tdb->last_error = tdb_logerr(tdb, TDB_ERR_RDONLY, - TDB_LOG_USE_ERROR, - "tdb_transaction_start:" - " cannot start a" - " transaction on a " - " read-only tdb"); - } - - /* cope with nested tdb_transaction_start() calls */ - if (tdb->tdb2.transaction != NULL) { - if (!(tdb->flags & TDB_ALLOW_NESTING)) { - return tdb->last_error - = tdb_logerr(tdb, TDB_ERR_IO, - TDB_LOG_USE_ERROR, - "tdb_transaction_start:" - " already inside transaction"); - } - tdb->tdb2.transaction->nesting++; - tdb->stats.transaction_nest++; - return 0; - } - - if (tdb_has_hash_locks(tdb)) { - /* the caller must not have any locks when starting a - transaction as otherwise we'll be screwed by lack - of nested locks in POSIX */ - return tdb->last_error = tdb_logerr(tdb, TDB_ERR_LOCK, - TDB_LOG_USE_ERROR, - "tdb_transaction_start:" - " cannot start a" - " transaction with locks" - " held"); - } - - tdb->tdb2.transaction = (struct tdb_transaction *) - calloc(sizeof(struct tdb_transaction), 1); - if (tdb->tdb2.transaction == NULL) { - return tdb->last_error = tdb_logerr(tdb, TDB_ERR_OOM, - TDB_LOG_ERROR, - "tdb_transaction_start:" - " cannot allocate"); - } - - /* get the transaction write lock. This is a blocking lock. As - discussed with Volker, there are a number of ways we could - make this async, which we will probably do in the future */ - ecode = tdb_transaction_lock(tdb, F_WRLCK); - if (ecode != TDB_SUCCESS) { - SAFE_FREE(tdb->tdb2.transaction->blocks); - SAFE_FREE(tdb->tdb2.transaction); - return tdb->last_error = ecode; - } - - /* get a read lock over entire file. This is upgraded to a write - lock during the commit */ - ecode = tdb_allrecord_lock(tdb, F_RDLCK, TDB_LOCK_WAIT, true); - if (ecode != TDB_SUCCESS) { - goto fail_allrecord_lock; - } - - /* make sure we know about any file expansions already done by - anyone else */ - tdb->tdb2.io->oob(tdb, tdb->file->map_size, 1, true); - tdb->tdb2.transaction->old_map_size = tdb->file->map_size; - - /* finally hook the io methods, replacing them with - transaction specific methods */ - tdb->tdb2.transaction->io_methods = tdb->tdb2.io; - tdb->tdb2.io = &transaction_methods; - return tdb->last_error = TDB_SUCCESS; - -fail_allrecord_lock: - tdb_transaction_unlock(tdb, F_WRLCK); - SAFE_FREE(tdb->tdb2.transaction->blocks); - SAFE_FREE(tdb->tdb2.transaction); - return tdb->last_error = ecode; -} - - -/* - cancel the current transaction -*/ -void tdb_transaction_cancel(struct tdb_context *tdb) -{ - if (tdb->flags & TDB_VERSION1) { - tdb1_transaction_cancel(tdb); - return; - } - tdb->stats.transaction_cancel++; - _tdb_transaction_cancel(tdb); -} - -/* - work out how much space the linearised recovery data will consume (worst case) -*/ -static tdb_len_t tdb_recovery_size(struct tdb_context *tdb) -{ - tdb_len_t recovery_size = 0; - int i; - - recovery_size = 0; - for (i=0;itdb2.transaction->num_blocks;i++) { - if (i * PAGESIZE >= tdb->tdb2.transaction->old_map_size) { - break; - } - if (tdb->tdb2.transaction->blocks[i] == NULL) { - continue; - } - recovery_size += 2*sizeof(tdb_off_t); - if (i == tdb->tdb2.transaction->num_blocks-1) { - recovery_size += tdb->tdb2.transaction->last_block_size; - } else { - recovery_size += PAGESIZE; - } - } - - return recovery_size; -} - -static enum TDB_ERROR tdb_recovery_area(struct tdb_context *tdb, - const struct tdb_methods *methods, - tdb_off_t *recovery_offset, - struct tdb_recovery_record *rec) -{ - enum TDB_ERROR ecode; - - *recovery_offset = tdb_read_off(tdb, - offsetof(struct tdb_header, recovery)); - if (TDB_OFF_IS_ERR(*recovery_offset)) { - return TDB_OFF_TO_ERR(*recovery_offset); - } - - if (*recovery_offset == 0) { - rec->max_len = 0; - return TDB_SUCCESS; - } - - ecode = methods->tread(tdb, *recovery_offset, rec, sizeof(*rec)); - if (ecode != TDB_SUCCESS) - return ecode; - - tdb_convert(tdb, rec, sizeof(*rec)); - /* ignore invalid recovery regions: can happen in crash */ - if (rec->magic != TDB_RECOVERY_MAGIC && - rec->magic != TDB_RECOVERY_INVALID_MAGIC) { - *recovery_offset = 0; - rec->max_len = 0; - } - return TDB_SUCCESS; -} - -static unsigned int same(const unsigned char *new, - const unsigned char *old, - unsigned int length) -{ - unsigned int i; - - for (i = 0; i < length; i++) { - if (new[i] != old[i]) - break; - } - return i; -} - -static unsigned int different(const unsigned char *new, - const unsigned char *old, - unsigned int length, - unsigned int min_same, - unsigned int *samelen) -{ - unsigned int i; - - *samelen = 0; - for (i = 0; i < length; i++) { - if (new[i] == old[i]) { - (*samelen)++; - } else { - if (*samelen >= min_same) { - return i - *samelen; - } - *samelen = 0; - } - } - - if (*samelen < min_same) - *samelen = 0; - return length - *samelen; -} - -/* Allocates recovery blob, without tdb_recovery_record at head set up. */ -static struct tdb_recovery_record *alloc_recovery(struct tdb_context *tdb, - tdb_len_t *len) -{ - struct tdb_recovery_record *rec; - size_t i; - enum TDB_ERROR ecode; - unsigned char *p; - const struct tdb_methods *old_methods = tdb->tdb2.io; - - rec = malloc(sizeof(*rec) + tdb_recovery_size(tdb)); - if (!rec) { - tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "transaction_setup_recovery:" - " cannot allocate"); - return TDB_ERR_PTR(TDB_ERR_OOM); - } - - /* We temporarily revert to the old I/O methods, so we can use - * tdb_access_read */ - tdb->tdb2.io = tdb->tdb2.transaction->io_methods; - - /* build the recovery data into a single blob to allow us to do a single - large write, which should be more efficient */ - p = (unsigned char *)(rec + 1); - for (i=0;itdb2.transaction->num_blocks;i++) { - tdb_off_t offset; - tdb_len_t length; - unsigned int off; - const unsigned char *buffer; - - if (tdb->tdb2.transaction->blocks[i] == NULL) { - continue; - } - - offset = i * PAGESIZE; - length = PAGESIZE; - if (i == tdb->tdb2.transaction->num_blocks-1) { - length = tdb->tdb2.transaction->last_block_size; - } - - if (offset >= tdb->tdb2.transaction->old_map_size) { - continue; - } - - if (offset + length > tdb->file->map_size) { - ecode = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_transaction_setup_recovery:" - " transaction data over new region" - " boundary"); - goto fail; - } - if (offset + length > tdb->tdb2.transaction->old_map_size) { - /* Short read at EOF. */ - length = tdb->tdb2.transaction->old_map_size - offset; - } - buffer = tdb_access_read(tdb, offset, length, false); - if (TDB_PTR_IS_ERR(buffer)) { - ecode = TDB_PTR_ERR(buffer); - goto fail; - } - - /* Skip over anything the same at the start. */ - off = same(tdb->tdb2.transaction->blocks[i], buffer, length); - offset += off; - - while (off < length) { - tdb_len_t len; - unsigned int samelen; - - len = different(tdb->tdb2.transaction->blocks[i] + off, - buffer + off, length - off, - sizeof(offset) + sizeof(len) + 1, - &samelen); - - memcpy(p, &offset, sizeof(offset)); - memcpy(p + sizeof(offset), &len, sizeof(len)); - tdb_convert(tdb, p, sizeof(offset) + sizeof(len)); - p += sizeof(offset) + sizeof(len); - memcpy(p, buffer + off, len); - p += len; - off += len + samelen; - offset += len + samelen; - } - tdb_access_release(tdb, buffer); - } - - *len = p - (unsigned char *)(rec + 1); - tdb->tdb2.io = old_methods; - return rec; - -fail: - free(rec); - tdb->tdb2.io = old_methods; - return TDB_ERR_PTR(ecode); -} - -static tdb_off_t create_recovery_area(struct tdb_context *tdb, - tdb_len_t rec_length, - struct tdb_recovery_record *rec) -{ - tdb_off_t off, recovery_off; - tdb_len_t addition; - enum TDB_ERROR ecode; - const struct tdb_methods *methods = tdb->tdb2.transaction->io_methods; - - /* round up to a multiple of page size. Overallocate, since each - * such allocation forces us to expand the file. */ - rec->max_len = tdb_expand_adjust(tdb->file->map_size, rec_length); - - /* Round up to a page. */ - rec->max_len = ((sizeof(*rec) + rec->max_len + PAGESIZE-1) - & ~(PAGESIZE-1)) - - sizeof(*rec); - - off = tdb->file->map_size; - - /* Restore ->map_size before calling underlying expand_file. - Also so that we don't try to expand the file again in the - transaction commit, which would destroy the recovery - area */ - addition = (tdb->file->map_size - tdb->tdb2.transaction->old_map_size) + - sizeof(*rec) + rec->max_len; - tdb->file->map_size = tdb->tdb2.transaction->old_map_size; - tdb->stats.transaction_expand_file++; - ecode = methods->expand_file(tdb, addition); - if (ecode != TDB_SUCCESS) { - tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_recovery_allocate:" - " failed to create recovery area"); - return TDB_ERR_TO_OFF(ecode); - } - - /* we have to reset the old map size so that we don't try to - expand the file again in the transaction commit, which - would destroy the recovery area */ - tdb->tdb2.transaction->old_map_size = tdb->file->map_size; - - /* write the recovery header offset and sync - we can sync without a race here - as the magic ptr in the recovery record has not been set */ - recovery_off = off; - tdb_convert(tdb, &recovery_off, sizeof(recovery_off)); - ecode = methods->twrite(tdb, offsetof(struct tdb_header, recovery), - &recovery_off, sizeof(tdb_off_t)); - if (ecode != TDB_SUCCESS) { - tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_recovery_allocate:" - " failed to write recovery head"); - return TDB_ERR_TO_OFF(ecode); - } - transaction_write_existing(tdb, offsetof(struct tdb_header, recovery), - &recovery_off, - sizeof(tdb_off_t)); - return off; -} - -/* - setup the recovery data that will be used on a crash during commit -*/ -static enum TDB_ERROR transaction_setup_recovery(struct tdb_context *tdb) -{ - tdb_len_t recovery_size = 0; - tdb_off_t recovery_off = 0; - tdb_off_t old_map_size = tdb->tdb2.transaction->old_map_size; - struct tdb_recovery_record *recovery; - const struct tdb_methods *methods = tdb->tdb2.transaction->io_methods; - uint64_t magic; - enum TDB_ERROR ecode; - - recovery = alloc_recovery(tdb, &recovery_size); - if (TDB_PTR_IS_ERR(recovery)) - return TDB_PTR_ERR(recovery); - - ecode = tdb_recovery_area(tdb, methods, &recovery_off, recovery); - if (ecode) { - free(recovery); - return ecode; - } - - if (recovery->max_len < recovery_size) { - /* Not large enough. Free up old recovery area. */ - if (recovery_off) { - tdb->stats.frees++; - ecode = add_free_record(tdb, recovery_off, - sizeof(*recovery) - + recovery->max_len, - TDB_LOCK_WAIT, true); - free(recovery); - if (ecode != TDB_SUCCESS) { - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_recovery_allocate:" - " failed to free previous" - " recovery area"); - } - - /* Refresh recovery after add_free_record above. */ - recovery = alloc_recovery(tdb, &recovery_size); - if (TDB_PTR_IS_ERR(recovery)) - return TDB_PTR_ERR(recovery); - } - - recovery_off = create_recovery_area(tdb, recovery_size, - recovery); - if (TDB_OFF_IS_ERR(recovery_off)) { - free(recovery); - return TDB_OFF_TO_ERR(recovery_off); - } - } - - /* Now we know size, convert rec header. */ - recovery->magic = TDB_RECOVERY_INVALID_MAGIC; - recovery->len = recovery_size; - recovery->eof = old_map_size; - tdb_convert(tdb, recovery, sizeof(*recovery)); - - /* write the recovery data to the recovery area */ - ecode = methods->twrite(tdb, recovery_off, recovery, recovery_size); - if (ecode != TDB_SUCCESS) { - free(recovery); - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_setup_recovery:" - " failed to write recovery data"); - } - transaction_write_existing(tdb, recovery_off, recovery, recovery_size); - - free(recovery); - - /* as we don't have ordered writes, we have to sync the recovery - data before we update the magic to indicate that the recovery - data is present */ - ecode = transaction_sync(tdb, recovery_off, recovery_size); - if (ecode != TDB_SUCCESS) - return ecode; - - magic = TDB_RECOVERY_MAGIC; - tdb_convert(tdb, &magic, sizeof(magic)); - - tdb->tdb2.transaction->magic_offset - = recovery_off + offsetof(struct tdb_recovery_record, magic); - - ecode = methods->twrite(tdb, tdb->tdb2.transaction->magic_offset, - &magic, sizeof(magic)); - if (ecode != TDB_SUCCESS) { - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_setup_recovery:" - " failed to write recovery magic"); - } - transaction_write_existing(tdb, tdb->tdb2.transaction->magic_offset, - &magic, sizeof(magic)); - - /* ensure the recovery magic marker is on disk */ - return transaction_sync(tdb, tdb->tdb2.transaction->magic_offset, - sizeof(magic)); -} - -static enum TDB_ERROR _tdb_transaction_prepare_commit(struct tdb_context *tdb) -{ - const struct tdb_methods *methods; - enum TDB_ERROR ecode; - - if (tdb->tdb2.transaction == NULL) { - return tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb_transaction_prepare_commit:" - " no transaction"); - } - - if (tdb->tdb2.transaction->prepared) { - _tdb_transaction_cancel(tdb); - return tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_USE_ERROR, - "tdb_transaction_prepare_commit:" - " transaction already prepared"); - } - - if (tdb->tdb2.transaction->transaction_error) { - _tdb_transaction_cancel(tdb); - return tdb_logerr(tdb, TDB_ERR_EINVAL, TDB_LOG_ERROR, - "tdb_transaction_prepare_commit:" - " transaction error pending"); - } - - - if (tdb->tdb2.transaction->nesting != 0) { - return TDB_SUCCESS; - } - - /* check for a null transaction */ - if (tdb->tdb2.transaction->blocks == NULL) { - return TDB_SUCCESS; - } - - methods = tdb->tdb2.transaction->io_methods; - - /* upgrade the main transaction lock region to a write lock */ - ecode = tdb_allrecord_upgrade(tdb, TDB_HASH_LOCK_START); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* get the open lock - this prevents new users attaching to the database - during the commit */ - ecode = tdb_lock_open(tdb, F_WRLCK, TDB_LOCK_WAIT|TDB_LOCK_NOCHECK); - if (ecode != TDB_SUCCESS) { - return ecode; - } - - /* Since we have whole db locked, we don't need the expansion lock. */ - if (!(tdb->flags & TDB_NOSYNC)) { - /* Sets up tdb->tdb2.transaction->recovery and - * tdb->tdb2.transaction->magic_offset. */ - ecode = transaction_setup_recovery(tdb); - if (ecode != TDB_SUCCESS) { - return ecode; - } - } - - tdb->tdb2.transaction->prepared = true; - - /* expand the file to the new size if needed */ - if (tdb->file->map_size != tdb->tdb2.transaction->old_map_size) { - tdb_len_t add; - - add = tdb->file->map_size - tdb->tdb2.transaction->old_map_size; - /* Restore original map size for tdb_expand_file */ - tdb->file->map_size = tdb->tdb2.transaction->old_map_size; - ecode = methods->expand_file(tdb, add); - if (ecode != TDB_SUCCESS) { - return ecode; - } - } - - /* Keep the open lock until the actual commit */ - return TDB_SUCCESS; -} - -/* - prepare to commit the current transaction -*/ -enum TDB_ERROR tdb_transaction_prepare_commit(struct tdb_context *tdb) -{ - if (tdb->flags & TDB_VERSION1) { - if (tdb1_transaction_prepare_commit(tdb) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - return tdb->last_error = _tdb_transaction_prepare_commit(tdb); -} - -/* - commit the current transaction -*/ -enum TDB_ERROR tdb_transaction_commit(struct tdb_context *tdb) -{ - const struct tdb_methods *methods; - int i; - enum TDB_ERROR ecode; - - if (tdb->flags & TDB_VERSION1) { - if (tdb1_transaction_commit(tdb) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - - if (tdb->tdb2.transaction == NULL) { - return tdb->last_error = tdb_logerr(tdb, TDB_ERR_EINVAL, - TDB_LOG_USE_ERROR, - "tdb_transaction_commit:" - " no transaction"); - } - - tdb_trace(tdb, "tdb_transaction_commit"); - - if (tdb->tdb2.transaction->nesting != 0) { - tdb->tdb2.transaction->nesting--; - return tdb->last_error = TDB_SUCCESS; - } - - /* check for a null transaction */ - if (tdb->tdb2.transaction->blocks == NULL) { - _tdb_transaction_cancel(tdb); - return tdb->last_error = TDB_SUCCESS; - } - - if (!tdb->tdb2.transaction->prepared) { - ecode = _tdb_transaction_prepare_commit(tdb); - if (ecode != TDB_SUCCESS) { - _tdb_transaction_cancel(tdb); - return tdb->last_error = ecode; - } - } - - methods = tdb->tdb2.transaction->io_methods; - - /* perform all the writes */ - for (i=0;itdb2.transaction->num_blocks;i++) { - tdb_off_t offset; - tdb_len_t length; - - if (tdb->tdb2.transaction->blocks[i] == NULL) { - continue; - } - - offset = i * PAGESIZE; - length = PAGESIZE; - if (i == tdb->tdb2.transaction->num_blocks-1) { - length = tdb->tdb2.transaction->last_block_size; - } - - ecode = methods->twrite(tdb, offset, - tdb->tdb2.transaction->blocks[i], length); - if (ecode != TDB_SUCCESS) { - /* we've overwritten part of the data and - possibly expanded the file, so we need to - run the crash recovery code */ - tdb->tdb2.io = methods; - tdb_transaction_recover(tdb); - - _tdb_transaction_cancel(tdb); - - return tdb->last_error = ecode; - } - SAFE_FREE(tdb->tdb2.transaction->blocks[i]); - } - - SAFE_FREE(tdb->tdb2.transaction->blocks); - tdb->tdb2.transaction->num_blocks = 0; - - /* ensure the new data is on disk */ - ecode = transaction_sync(tdb, 0, tdb->file->map_size); - if (ecode != TDB_SUCCESS) { - return tdb->last_error = ecode; - } - - /* - TODO: maybe write to some dummy hdr field, or write to magic - offset without mmap, before the last sync, instead of the - utime() call - */ - - /* on some systems (like Linux 2.6.x) changes via mmap/msync - don't change the mtime of the file, this means the file may - not be backed up (as tdb rounding to block sizes means that - file size changes are quite rare too). The following forces - mtime changes when a transaction completes */ -#if HAVE_UTIME - utime(tdb->name, NULL); -#endif - - /* use a transaction cancel to free memory and remove the - transaction locks: it "restores" map_size, too. */ - tdb->tdb2.transaction->old_map_size = tdb->file->map_size; - _tdb_transaction_cancel(tdb); - - return tdb->last_error = TDB_SUCCESS; -} - - -/* - recover from an aborted transaction. Must be called with exclusive - database write access already established (including the open - lock to prevent new processes attaching) -*/ -enum TDB_ERROR tdb_transaction_recover(struct tdb_context *tdb) -{ - tdb_off_t recovery_head, recovery_eof; - unsigned char *data, *p; - struct tdb_recovery_record rec; - enum TDB_ERROR ecode; - - /* find the recovery area */ - recovery_head = tdb_read_off(tdb, offsetof(struct tdb_header,recovery)); - if (TDB_OFF_IS_ERR(recovery_head)) { - ecode = TDB_OFF_TO_ERR(recovery_head); - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to read recovery head"); - } - - if (recovery_head == 0) { - /* we have never allocated a recovery record */ - return TDB_SUCCESS; - } - - /* read the recovery record */ - ecode = tdb_read_convert(tdb, recovery_head, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to read recovery record"); - } - - if (rec.magic != TDB_RECOVERY_MAGIC) { - /* there is no valid recovery data */ - return TDB_SUCCESS; - } - - if (tdb->flags & TDB_RDONLY) { - return tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " attempt to recover read only database"); - } - - recovery_eof = rec.eof; - - data = (unsigned char *)malloc(rec.len); - if (data == NULL) { - return tdb_logerr(tdb, TDB_ERR_OOM, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to allocate recovery data"); - } - - /* read the full recovery data */ - ecode = tdb->tdb2.io->tread(tdb, recovery_head + sizeof(rec), data, - rec.len); - if (ecode != TDB_SUCCESS) { - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to read recovery data"); - } - - /* recover the file data */ - p = data; - while (p+sizeof(tdb_off_t)+sizeof(tdb_len_t) < data + rec.len) { - tdb_off_t ofs; - tdb_len_t len; - tdb_convert(tdb, p, sizeof(ofs) + sizeof(len)); - memcpy(&ofs, p, sizeof(ofs)); - memcpy(&len, p + sizeof(ofs), sizeof(len)); - p += sizeof(ofs) + sizeof(len); - - ecode = tdb->tdb2.io->twrite(tdb, ofs, p, len); - if (ecode != TDB_SUCCESS) { - free(data); - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to recover %zu bytes" - " at offset %zu", - (size_t)len, (size_t)ofs); - } - p += len; - } - - free(data); - - ecode = transaction_sync(tdb, 0, tdb->file->map_size); - if (ecode != TDB_SUCCESS) { - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to sync recovery"); - } - - /* if the recovery area is after the recovered eof then remove it */ - if (recovery_eof <= recovery_head) { - ecode = tdb_write_off(tdb, offsetof(struct tdb_header, - recovery), - 0); - if (ecode != TDB_SUCCESS) { - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to remove recovery head"); - } - } - - /* remove the recovery magic */ - ecode = tdb_write_off(tdb, - recovery_head - + offsetof(struct tdb_recovery_record, magic), - TDB_RECOVERY_INVALID_MAGIC); - if (ecode != TDB_SUCCESS) { - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to remove recovery magic"); - } - - ecode = transaction_sync(tdb, 0, recovery_eof); - if (ecode != TDB_SUCCESS) { - return tdb_logerr(tdb, ecode, TDB_LOG_ERROR, - "tdb_transaction_recover:" - " failed to sync2 recovery"); - } - - tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING, - "tdb_transaction_recover: recovered %zu byte database", - (size_t)recovery_eof); - - /* all done */ - return TDB_SUCCESS; -} - -tdb_bool_err tdb_needs_recovery(struct tdb_context *tdb) -{ - tdb_off_t recovery_head; - struct tdb_recovery_record rec; - enum TDB_ERROR ecode; - - /* find the recovery area */ - recovery_head = tdb_read_off(tdb, offsetof(struct tdb_header,recovery)); - if (TDB_OFF_IS_ERR(recovery_head)) { - return recovery_head; - } - - if (recovery_head == 0) { - /* we have never allocated a recovery record */ - return false; - } - - /* read the recovery record */ - ecode = tdb_read_convert(tdb, recovery_head, &rec, sizeof(rec)); - if (ecode != TDB_SUCCESS) { - return TDB_ERR_TO_OFF(ecode); - } - - return (rec.magic == TDB_RECOVERY_MAGIC); -} diff --git a/ccan/tdb2/traverse.c b/ccan/tdb2/traverse.c deleted file mode 100644 index 0bf41899..00000000 --- a/ccan/tdb2/traverse.c +++ /dev/null @@ -1,134 +0,0 @@ - /* - Trivial Database 2: traverse function. - Copyright (C) Rusty Russell 2010 - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . -*/ -#include "private.h" -#include - -int64_t tdb_traverse_(struct tdb_context *tdb, - int (*fn)(struct tdb_context *, - TDB_DATA, TDB_DATA, void *), - void *p) -{ - enum TDB_ERROR ecode; - struct traverse_info tinfo; - struct tdb_data k, d; - int64_t count = 0; - - if (tdb->flags & TDB_VERSION1) { - count = tdb1_traverse(tdb, fn, p); - if (count == -1) - return TDB_ERR_TO_OFF(tdb->last_error); - return count; - } - - k.dptr = NULL; - for (ecode = first_in_hash(tdb, &tinfo, &k, &d.dsize); - ecode == TDB_SUCCESS; - ecode = next_in_hash(tdb, &tinfo, &k, &d.dsize)) { - d.dptr = k.dptr + k.dsize; - - count++; - if (fn && fn(tdb, k, d, p)) { - free(k.dptr); - tdb->last_error = TDB_SUCCESS; - return count; - } - free(k.dptr); - } - - if (ecode != TDB_ERR_NOEXIST) { - return TDB_ERR_TO_OFF(tdb->last_error = ecode); - } - tdb->last_error = TDB_SUCCESS; - return count; -} - -enum TDB_ERROR tdb_firstkey(struct tdb_context *tdb, struct tdb_data *key) -{ - struct traverse_info tinfo; - - if (tdb->flags & TDB_VERSION1) { - tdb->last_error = TDB_SUCCESS; - *key = tdb1_firstkey(tdb); - /* TDB1 didn't set error for last key. */ - if (!key->dptr && tdb->last_error == TDB_SUCCESS) { - tdb->last_error = TDB_ERR_NOEXIST; - } - return tdb->last_error; - } - - return tdb->last_error = first_in_hash(tdb, &tinfo, key, NULL); -} - -/* We lock twice, not very efficient. We could keep last key & tinfo cached. */ -enum TDB_ERROR tdb_nextkey(struct tdb_context *tdb, struct tdb_data *key) -{ - struct traverse_info tinfo; - struct hash_info h; - struct tdb_used_record rec; - - if (tdb->flags & TDB_VERSION1) { - struct tdb_data last_key = *key; - tdb->last_error = TDB_SUCCESS; - *key = tdb1_nextkey(tdb, last_key); - free(last_key.dptr); - /* TDB1 didn't set error for last key. */ - if (!key->dptr && tdb->last_error == TDB_SUCCESS) { - tdb->last_error = TDB_ERR_NOEXIST; - } - return tdb->last_error; - } - - tinfo.prev = find_and_lock(tdb, *key, F_RDLCK, &h, &rec, &tinfo); - free(key->dptr); - if (TDB_OFF_IS_ERR(tinfo.prev)) { - return tdb->last_error = TDB_OFF_TO_ERR(tinfo.prev); - } - tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range, F_RDLCK); - - return tdb->last_error = 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; - - if (tdb->flags & TDB_VERSION1) { - if (tdb1_wipe_all(tdb) == -1) - return tdb->last_error; - return TDB_SUCCESS; - } - - ecode = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false); - if (ecode != TDB_SUCCESS) - return tdb->last_error = ecode; - - /* FIXME: Be smarter. */ - count = tdb_traverse(tdb, wipe_one, &ecode); - if (count < 0) - ecode = TDB_OFF_TO_ERR(count); - tdb_allrecord_unlock(tdb, F_WRLCK); - return tdb->last_error = ecode; -} -- 2.39.2