- num_found = 0;
- num_nonzero = 0;
- for (i = 0, h = tdb->header.v.hash_off;
- i < (1ULL << tdb->header.v.hash_bits);
- i++, h += sizeof(tdb_off_t)) {
- tdb_off_t off, *p, pos;
- struct tdb_used_record rec;
- uint64_t hash;
-
- off = tdb_read_off(tdb, h);
- if (off == TDB_OFF_ERR)
- return false;
- if (!off) {
- num_nonzero = 0;
- continue;
- }
- /* FIXME: Check hash bits */
- p = asearch(&off, used, num_used, off_cmp);
- if (!p) {
- tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
- "tdb_check: Invalid offset %llu in hash\n",
- (long long)off);
- return false;
- }
- /* Mark it invalid. */
- *p ^= 1;
- num_found++;
-
- if (tdb_read_convert(tdb, off, &rec, sizeof(rec)) == -1)
- return false;
-
- /* Check it is hashed correctly. */
- hash = hash_record(tdb, off);
-
- /* Top bits must match header. */
- if (hash >> (64 - 11) != rec_hash(&rec)) {
- tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
- "tdb_check: Bad hash magic at offset %llu"
- " (0x%llx vs 0x%llx)\n",
- (long long)off,
- (long long)hash, (long long)rec_hash(&rec));
- return false;
- }
+ 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);
+}
+
+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 bool 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)
+{
+ unsigned int g, b;
+ const tdb_off_t *hash;
+ struct tdb_used_record rec;
+
+ hash = tdb_access_read(tdb, off,
+ sizeof(tdb_off_t)
+ << (group_bits + TDB_HASH_GROUP_BITS),
+ true);
+ if (!hash)
+ return false;
+
+ 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) {
+ tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
+ "tdb_check: Invalid offset %llu "
+ "in hash\n",
+ (long long)off);
+ goto fail;
+ }
+ /* Mark it invalid. */
+ *p ^= 1;
+ (*num_found)++;
+
+ if (is_subhash(group[b])) {
+ uint64_t subprefix;
+ subprefix = (hprefix
+ << (group_bits + TDB_HASH_GROUP_BITS))
+ + g * (1 << TDB_HASH_GROUP_BITS) + b;
+
+ if (!check_hash_record(tdb,
+ group[b] & TDB_OFF_MASK,
+ subprefix,
+ hprefix_bits
+ + group_bits
+ + TDB_HASH_GROUP_BITS,
+ used, num_used, num_found))
+ 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) {
+ tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
+ "check: bad hash placement"
+ " 0x%llx vs 0x%llx\n",
+ (long long)h, (long long)hprefix);
+ goto fail;
+ }
+
+ /* Does it belong in this group? */
+ if (get_bits(h, group_bits, &used_bits) != g) {
+ tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
+ "check: bad group %llu vs %u\n",
+ (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;
+ tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
+ "check: bad bucket %u vs %u\n",
+ (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) {
+ tdb->log(tdb, TDB_DEBUG_ERROR,
+ tdb->log_priv,
+ "check: bad group placement"
+ " %u vs %u\n",
+ b, bucket);
+ goto fail;
+ }
+ }
+
+ if (tdb_read_convert(tdb, off, &rec, sizeof(rec)) == -1)
+ goto fail;