_PUBLIC_ int tdb_check(struct tdb_context *tdb, int (*check)(TDB_DATA key, TDB_DATA data, void *private_data), void *private_data) { unsigned int h; unsigned char **hashes; tdb_off_t off, recovery_start; struct tdb_record rec; bool found_recovery = false; tdb_len_t dead; bool locked; /* Read-only databases use no locking at all: it's best-effort. * We may have a write lock already, so skip that case too. */ if (tdb->read_only || tdb->allrecord_lock.count != 0) { locked = false; } else { if (tdb_lockall_read(tdb) == -1) return -1; locked = true; } /* Make sure we know true size of the underlying file. */ tdb->methods->tdb_oob(tdb, tdb->map_size, 1, 1); /* Header must be OK: also gets us the recovery ptr, if any. */ if (!tdb_check_header(tdb, &recovery_start)) goto unlock; /* We should have the whole header, too. */ if (tdb->map_size < TDB_DATA_START(tdb->hash_size)) { tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_ERROR, "File too short for hashes\n")); goto unlock; } /* One big malloc: pointers then bit arrays. */ hashes = (unsigned char **)calloc( 1, sizeof(hashes[0]) * (1+tdb->hash_size) + BITMAP_BITS / CHAR_BIT * (1+tdb->hash_size)); if (!hashes) { tdb->ecode = TDB_ERR_OOM; goto unlock; } /* Initialize pointers */ hashes[0] = (unsigned char *)(&hashes[1+tdb->hash_size]); for (h = 1; h < 1+tdb->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->hash_size; h++) { if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_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 = TDB_DATA_START(tdb->hash_size); off < tdb->map_size; off += sizeof(rec) + rec.rec_len) { if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec), DOCONV()) == -1) goto free; switch (rec.magic) { case TDB_MAGIC: case TDB_DEAD_MAGIC: if (!tdb_check_used_record(tdb, off, &rec, hashes, check, private_data)) goto free; break; case TDB_FREE_MAGIC: if (!tdb_check_free_record(tdb, off, &rec, hashes)) goto free; break; /* If we crash after ftruncate, we can get zeroes or fill. */ case TDB_RECOVERY_INVALID_MAGIC: case 0x42424242: if (recovery_start == off) { found_recovery = true; break; } dead = tdb_dead_space(tdb, off); if (dead < sizeof(rec)) goto corrupt; TDB_LOG((tdb, TDB_DEBUG_ERROR, "Dead space at %d-%d (of %u)\n", off, off + dead, tdb->map_size)); rec.rec_len = dead - sizeof(rec); break; case TDB_RECOVERY_MAGIC: if (recovery_start != off) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "Unexpected recovery record at offset %d\n", off)); goto free; } found_recovery = true; break; default: ; corrupt: tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_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->hash_size; h++) { unsigned int i; for (i = 0; i < BITMAP_BITS / CHAR_BIT; i++) { if (hashes[h][i] != 0) { tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_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_LOG((tdb, TDB_DEBUG_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; }
int tdb_check(struct tdb_context *tdb, int (*check)(TDB_DATA key, TDB_DATA data, void *private_data), void *private_data) { unsigned int h; unsigned char **hashes; tdb_off_t off, recovery_start; struct tdb_record rec; bool found_recovery = false; if (tdb_lockall(tdb) == -1) return -1; /* Make sure we know true size of the underlying file. */ tdb->methods->tdb_oob(tdb, tdb->map_size + 1, 1); /* Header must be OK: also gets us the recovery ptr, if any. */ if (!tdb_check_header(tdb, &recovery_start)) goto unlock; /* We should have the whole header, too. */ if (tdb->map_size < TDB_DATA_START(tdb->header.hash_size)) { tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_ERROR, "File too short for hashes\n")); goto unlock; } /* One big malloc: pointers then bit arrays. */ hashes = (unsigned char **)calloc( 1, sizeof(hashes[0]) * (1+tdb->header.hash_size) + BITMAP_BITS / CHAR_BIT * (1+tdb->header.hash_size)); if (!hashes) { tdb->ecode = TDB_ERR_OOM; goto unlock; } /* Initialize pointers */ hashes[0] = (unsigned char *)(&hashes[1+tdb->header.hash_size]); for (h = 1; h < 1+tdb->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->header.hash_size; h++) { if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_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 = TDB_DATA_START(tdb->header.hash_size); off < tdb->map_size; off += sizeof(rec) + rec.rec_len) { if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec), DOCONV()) == -1) goto free; switch (rec.magic) { case TDB_MAGIC: case TDB_DEAD_MAGIC: if (!tdb_check_used_record(tdb, off, &rec, hashes, check, private_data)) goto free; break; case TDB_FREE_MAGIC: if (!tdb_check_free_record(tdb, off, &rec, hashes)) goto free; break; case TDB_RECOVERY_MAGIC: case 0: /* Used for invalid (or in-progress) recovery area. */ if (recovery_start != off) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "Unexpected recovery record at offset %d\n", off)); goto free; } found_recovery = true; break; default: tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_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->header.hash_size; h++) { unsigned int i; for (i = 0; i < BITMAP_BITS / CHAR_BIT; i++) { if (hashes[h][i] != 0) { tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_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_LOG((tdb, TDB_DEBUG_ERROR, "Expected %s recovery area, got %s\n", recovery_start ? "a" : "no", found_recovery ? "one" : "none")); goto free; } free(hashes); tdb_unlockall(tdb); return 0; free: free(hashes); unlock: tdb_unlockall(tdb); return -1; }