mirror of
https://github.com/samba-team/samba.git
synced 2025-01-22 22:04:08 +03:00
tdb: add tdb_rescue()
This allows for an emergency best-effort dump. It's a little better than strings(1). Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
fe38a93c71
commit
90f463b25f
67
lib/tdb/ABI/tdb-1.2.11.sigs
Normal file
67
lib/tdb/ABI/tdb-1.2.11.sigs
Normal file
@ -0,0 +1,67 @@
|
||||
tdb_add_flags: void (struct tdb_context *, unsigned int)
|
||||
tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
|
||||
tdb_chainlock: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
|
||||
tdb_close: int (struct tdb_context *)
|
||||
tdb_delete: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_dump_all: void (struct tdb_context *)
|
||||
tdb_enable_seqnum: void (struct tdb_context *)
|
||||
tdb_error: enum TDB_ERROR (struct tdb_context *)
|
||||
tdb_errorstr: const char *(struct tdb_context *)
|
||||
tdb_exists: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_fd: int (struct tdb_context *)
|
||||
tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
|
||||
tdb_firstkey: TDB_DATA (struct tdb_context *)
|
||||
tdb_freelist_size: int (struct tdb_context *)
|
||||
tdb_get_flags: int (struct tdb_context *)
|
||||
tdb_get_logging_private: void *(struct tdb_context *)
|
||||
tdb_get_seqnum: int (struct tdb_context *)
|
||||
tdb_hash_size: int (struct tdb_context *)
|
||||
tdb_increment_seqnum_nonblock: void (struct tdb_context *)
|
||||
tdb_jenkins_hash: unsigned int (TDB_DATA *)
|
||||
tdb_lock_nonblock: int (struct tdb_context *, int, int)
|
||||
tdb_lockall: int (struct tdb_context *)
|
||||
tdb_lockall_mark: int (struct tdb_context *)
|
||||
tdb_lockall_nonblock: int (struct tdb_context *)
|
||||
tdb_lockall_read: int (struct tdb_context *)
|
||||
tdb_lockall_read_nonblock: int (struct tdb_context *)
|
||||
tdb_lockall_unmark: int (struct tdb_context *)
|
||||
tdb_log_fn: tdb_log_func (struct tdb_context *)
|
||||
tdb_map_size: size_t (struct tdb_context *)
|
||||
tdb_name: const char *(struct tdb_context *)
|
||||
tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
|
||||
tdb_null: dptr = 0xXXXX, dsize = 0
|
||||
tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
|
||||
tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
|
||||
tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
|
||||
tdb_printfreelist: int (struct tdb_context *)
|
||||
tdb_remove_flags: void (struct tdb_context *, unsigned int)
|
||||
tdb_reopen: int (struct tdb_context *)
|
||||
tdb_reopen_all: int (int)
|
||||
tdb_repack: int (struct tdb_context *)
|
||||
tdb_rescue: int (struct tdb_context *, void (*)(TDB_DATA, TDB_DATA, void *), void *)
|
||||
tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
|
||||
tdb_set_max_dead: void (struct tdb_context *, int)
|
||||
tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
|
||||
tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
|
||||
tdb_summary: char *(struct tdb_context *)
|
||||
tdb_transaction_cancel: int (struct tdb_context *)
|
||||
tdb_transaction_commit: int (struct tdb_context *)
|
||||
tdb_transaction_prepare_commit: int (struct tdb_context *)
|
||||
tdb_transaction_start: int (struct tdb_context *)
|
||||
tdb_transaction_start_nonblock: int (struct tdb_context *)
|
||||
tdb_transaction_write_lock_mark: int (struct tdb_context *)
|
||||
tdb_transaction_write_lock_unmark: int (struct tdb_context *)
|
||||
tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
|
||||
tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
|
||||
tdb_unlock: int (struct tdb_context *, int, int)
|
||||
tdb_unlockall: int (struct tdb_context *)
|
||||
tdb_unlockall_read: int (struct tdb_context *)
|
||||
tdb_validate_freelist: int (struct tdb_context *, int *)
|
||||
tdb_wipe_all: int (struct tdb_context *)
|
349
lib/tdb/common/rescue.c
Normal file
349
lib/tdb/common/rescue.c
Normal file
@ -0,0 +1,349 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
trivial database library, rescue attempt code.
|
||||
|
||||
Copyright (C) Rusty Russell 2012
|
||||
|
||||
** 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "tdb_private.h"
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
struct found {
|
||||
tdb_off_t head; /* 0 -> invalid. */
|
||||
struct tdb_record rec;
|
||||
TDB_DATA key;
|
||||
bool in_hash;
|
||||
bool in_free;
|
||||
};
|
||||
|
||||
struct found_table {
|
||||
/* As an ordered array (by head offset). */
|
||||
struct found *arr;
|
||||
unsigned int num, max;
|
||||
};
|
||||
|
||||
static bool looks_like_valid_record(struct tdb_context *tdb,
|
||||
tdb_off_t off,
|
||||
const struct tdb_record *rec,
|
||||
TDB_DATA *key)
|
||||
{
|
||||
unsigned int hval;
|
||||
|
||||
if (rec->magic != TDB_MAGIC)
|
||||
return false;
|
||||
|
||||
if (rec->key_len + rec->data_len > rec->rec_len)
|
||||
return false;
|
||||
|
||||
if (rec->rec_len % TDB_ALIGNMENT)
|
||||
return false;
|
||||
|
||||
/* Next pointer must make some sense. */
|
||||
if (rec->next > 0 && rec->next < TDB_DATA_START(tdb->header.hash_size))
|
||||
return false;
|
||||
|
||||
if (tdb->methods->tdb_oob(tdb, rec->next, sizeof(*rec), 1))
|
||||
return false;
|
||||
|
||||
key->dsize = rec->key_len;
|
||||
key->dptr = tdb_alloc_read(tdb, off + sizeof(*rec), key->dsize);
|
||||
if (!key->dptr)
|
||||
return false;
|
||||
|
||||
hval = tdb->hash_fn(key);
|
||||
if (hval != rec->full_hash) {
|
||||
free(key->dptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Caller frees up key->dptr */
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool add_to_table(struct found_table *found,
|
||||
tdb_off_t off,
|
||||
struct tdb_record *rec,
|
||||
TDB_DATA key)
|
||||
{
|
||||
if (found->num + 1 > found->max) {
|
||||
struct found *new;
|
||||
found->max = (found->max ? found->max * 2 : 128);
|
||||
new = realloc(found->arr, found->max * sizeof(found->arr[0]));
|
||||
if (!new)
|
||||
return false;
|
||||
found->arr = new;
|
||||
}
|
||||
|
||||
found->arr[found->num].head = off;
|
||||
found->arr[found->num].rec = *rec;
|
||||
found->arr[found->num].key = key;
|
||||
found->arr[found->num].in_hash = false;
|
||||
found->arr[found->num].in_free = false;
|
||||
|
||||
found->num++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool walk_record(struct tdb_context *tdb,
|
||||
const struct found *f,
|
||||
void (*walk)(TDB_DATA, TDB_DATA, void *private_data),
|
||||
void *private_data)
|
||||
{
|
||||
TDB_DATA data;
|
||||
|
||||
data.dsize = f->rec.data_len;
|
||||
data.dptr = tdb_alloc_read(tdb,
|
||||
f->head + sizeof(f->rec) + f->rec.key_len,
|
||||
data.dsize);
|
||||
if (!data.dptr) {
|
||||
if (tdb->ecode == TDB_ERR_OOM)
|
||||
return false;
|
||||
/* I/O errors are expected. */
|
||||
return true;
|
||||
}
|
||||
|
||||
walk(f->key, data, private_data);
|
||||
free(data.dptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* First entry which has offset >= this one. */
|
||||
static unsigned int find_entry(struct found_table *found, tdb_off_t off)
|
||||
{
|
||||
unsigned int start = 0, end = found->num;
|
||||
|
||||
while (start < end) {
|
||||
/* We can't overflow here. */
|
||||
unsigned int mid = (start + end) / 2;
|
||||
|
||||
if (off < found->arr[mid].head) {
|
||||
end = mid;
|
||||
} else if (off > found->arr[mid].head) {
|
||||
start = mid + 1;
|
||||
} else {
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
|
||||
assert(start == end);
|
||||
return end;
|
||||
}
|
||||
|
||||
static void found_in_hashchain(struct found_table *found, tdb_off_t head)
|
||||
{
|
||||
unsigned int match;
|
||||
|
||||
match = find_entry(found, head);
|
||||
if (match < found->num && found->arr[match].head == head) {
|
||||
found->arr[match].in_hash = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_free_area(struct found_table *found, tdb_off_t head,
|
||||
tdb_len_t len)
|
||||
{
|
||||
unsigned int match;
|
||||
|
||||
match = find_entry(found, head);
|
||||
/* Mark everything within this free entry. */
|
||||
while (match < found->num) {
|
||||
if (found->arr[match].head >= head + len) {
|
||||
break;
|
||||
}
|
||||
found->arr[match].in_free = true;
|
||||
match++;
|
||||
}
|
||||
}
|
||||
|
||||
static int cmp_key(const void *a, const void *b)
|
||||
{
|
||||
const struct found *fa = a, *fb = b;
|
||||
|
||||
if (fa->key.dsize < fb->key.dsize) {
|
||||
return -1;
|
||||
} else if (fa->key.dsize > fb->key.dsize) {
|
||||
return 1;
|
||||
}
|
||||
return memcmp(fa->key.dptr, fb->key.dptr, fa->key.dsize);
|
||||
}
|
||||
|
||||
static bool key_eq(TDB_DATA a, TDB_DATA b)
|
||||
{
|
||||
return a.dsize == b.dsize
|
||||
&& memcmp(a.dptr, b.dptr, a.dsize) == 0;
|
||||
}
|
||||
|
||||
static void free_table(struct found_table *found)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < found->num; i++) {
|
||||
free(found->arr[i].key.dptr);
|
||||
}
|
||||
free(found->arr);
|
||||
}
|
||||
|
||||
static void logging_suppressed(struct tdb_context *tdb,
|
||||
enum tdb_debug_level level, const char *fmt, ...)
|
||||
{
|
||||
}
|
||||
|
||||
_PUBLIC_ int tdb_rescue(struct tdb_context *tdb,
|
||||
void (*walk)(TDB_DATA, TDB_DATA, void *private_data),
|
||||
void *private_data)
|
||||
{
|
||||
struct found_table found = { NULL, 0, 0 };
|
||||
tdb_off_t h, off, i;
|
||||
tdb_log_func oldlog = tdb->log.log_fn;
|
||||
struct tdb_record rec;
|
||||
TDB_DATA key;
|
||||
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);
|
||||
|
||||
/* Suppress logging, since we anticipate errors. */
|
||||
tdb->log.log_fn = logging_suppressed;
|
||||
|
||||
/* Now walk entire db looking for records. */
|
||||
for (off = TDB_DATA_START(tdb->header.hash_size);
|
||||
off < tdb->map_size;
|
||||
off += TDB_ALIGNMENT) {
|
||||
if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
|
||||
DOCONV()) == -1)
|
||||
continue;
|
||||
|
||||
if (looks_like_valid_record(tdb, off, &rec, &key)) {
|
||||
if (!add_to_table(&found, off, &rec, key)) {
|
||||
goto oom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Walk hash chains to positive vet. */
|
||||
for (h = 0; h < 1+tdb->header.hash_size; h++) {
|
||||
bool slow_chase = false;
|
||||
tdb_off_t slow_off = FREELIST_TOP + h*sizeof(tdb_off_t);
|
||||
|
||||
if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_off_t),
|
||||
&off) == -1)
|
||||
continue;
|
||||
|
||||
while (off && off != slow_off) {
|
||||
if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
|
||||
DOCONV()) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* 0 is the free list, rest are hash chains. */
|
||||
if (h == 0) {
|
||||
/* Don't mark garbage as free. */
|
||||
if (rec.magic != TDB_FREE_MAGIC) {
|
||||
break;
|
||||
}
|
||||
mark_free_area(&found, off,
|
||||
sizeof(rec) + rec.rec_len);
|
||||
} else {
|
||||
found_in_hashchain(&found, off);
|
||||
}
|
||||
|
||||
off = rec.next;
|
||||
|
||||
/* Loop detection using second pointer at half-speed */
|
||||
if (slow_chase) {
|
||||
/* First entry happens to be next ptr */
|
||||
tdb_ofs_read(tdb, slow_off, &slow_off);
|
||||
}
|
||||
slow_chase = !slow_chase;
|
||||
}
|
||||
}
|
||||
|
||||
/* Recovery area: must be marked as free, since it often has old
|
||||
* records in there! */
|
||||
if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &off) == 0 && off != 0) {
|
||||
if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
|
||||
DOCONV()) == 0) {
|
||||
mark_free_area(&found, off, sizeof(rec) + rec.rec_len);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now sort by key! */
|
||||
qsort(found.arr, found.num, sizeof(found.arr[0]), cmp_key);
|
||||
|
||||
for (i = 0; i < found.num; ) {
|
||||
unsigned int num, num_in_hash = 0;
|
||||
|
||||
/* How many are identical? */
|
||||
for (num = 0; num < found.num - i; num++) {
|
||||
if (!key_eq(found.arr[i].key, found.arr[i+num].key)) {
|
||||
break;
|
||||
}
|
||||
if (found.arr[i+num].in_hash) {
|
||||
if (!walk_record(tdb, &found.arr[i+num],
|
||||
walk, private_data))
|
||||
goto oom;
|
||||
num_in_hash++;
|
||||
}
|
||||
}
|
||||
assert(num);
|
||||
|
||||
/* If none were in the hash, we print any not in free list. */
|
||||
if (num_in_hash == 0) {
|
||||
unsigned int j;
|
||||
|
||||
for (j = i; j < i + num; j++) {
|
||||
if (!found.arr[j].in_free) {
|
||||
if (!walk_record(tdb, &found.arr[j],
|
||||
walk, private_data))
|
||||
goto oom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i += num;
|
||||
}
|
||||
|
||||
tdb->log.log_fn = oldlog;
|
||||
if (locked) {
|
||||
tdb_unlockall_read(tdb);
|
||||
}
|
||||
return 0;
|
||||
|
||||
oom:
|
||||
tdb->log.log_fn = oldlog;
|
||||
tdb->ecode = TDB_ERR_OOM;
|
||||
TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_rescue: failed allocating\n"));
|
||||
free_table(&found);
|
||||
if (locked) {
|
||||
tdb_unlockall_read(tdb);
|
||||
}
|
||||
return -1;
|
||||
}
|
@ -814,6 +814,28 @@ int tdb_check(struct tdb_context *tdb,
|
||||
int (*check) (TDB_DATA key, TDB_DATA data, void *private_data),
|
||||
void *private_data);
|
||||
|
||||
/**
|
||||
* @brief Dump all possible records in a corrupt database.
|
||||
*
|
||||
* This is the only way to get data out of a database where tdb_check() fails.
|
||||
* It will call walk() with anything which looks like a database record; this
|
||||
* may well include invalid, incomplete or duplicate records.
|
||||
*
|
||||
* @param[in] tdb The database to check.
|
||||
*
|
||||
* @param[in] walk The walk function to use.
|
||||
*
|
||||
* @param[in] private_data the private data to pass to the walk function.
|
||||
*
|
||||
* @return 0 on success, -1 on error with error code set.
|
||||
*
|
||||
* @see tdb_error()
|
||||
* @see tdb_errorstr()
|
||||
*/
|
||||
int tdb_rescue(struct tdb_context *tdb,
|
||||
void (*walk) (TDB_DATA key, TDB_DATA data, void *private_data),
|
||||
void *private_data);
|
||||
|
||||
/* @} ******************************************************************/
|
||||
|
||||
/* Low level locking functions: use with care */
|
||||
|
@ -13,7 +13,7 @@ if test x"$tdbdir" = "x"; then
|
||||
AC_MSG_ERROR([cannot find tdb source in $tdbpaths])
|
||||
fi
|
||||
TDB_OBJ="common/tdb.o common/dump.o common/transaction.o common/error.o common/traverse.o"
|
||||
TDB_OBJ="$TDB_OBJ common/freelist.o common/freelistcheck.o common/io.o common/lock.o common/open.o common/check.o common/hash.o common/summary.o"
|
||||
TDB_OBJ="$TDB_OBJ common/freelist.o common/freelistcheck.o common/io.o common/lock.o common/open.o common/check.o common/hash.o common/summary.o common/rescue.o"
|
||||
AC_SUBST(TDB_OBJ)
|
||||
AC_SUBST(LIBREPLACEOBJ)
|
||||
|
||||
|
50
lib/tdb/test/run-rescue-find_entry.c
Normal file
50
lib/tdb/test/run-rescue-find_entry.c
Normal file
@ -0,0 +1,50 @@
|
||||
#include "../common/tdb_private.h"
|
||||
#include "../common/io.c"
|
||||
#include "../common/tdb.c"
|
||||
#include "../common/lock.c"
|
||||
#include "../common/freelist.c"
|
||||
#include "../common/traverse.c"
|
||||
#include "../common/transaction.c"
|
||||
#include "../common/error.c"
|
||||
#include "../common/open.c"
|
||||
#include "../common/check.c"
|
||||
#include "../common/hash.c"
|
||||
#include "../common/rescue.c"
|
||||
#include "tap-interface.h"
|
||||
#include <stdlib.h>
|
||||
#include "logging.h"
|
||||
|
||||
#define NUM 20
|
||||
|
||||
/* Binary searches are deceptively simple: easy to screw up! */
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
unsigned int i, j, n;
|
||||
struct found f[NUM+1];
|
||||
struct found_table table;
|
||||
|
||||
/* Set up array for searching. */
|
||||
for (i = 0; i < NUM+1; i++) {
|
||||
f[i].head = i * 3;
|
||||
}
|
||||
table.arr = f;
|
||||
|
||||
for (i = 0; i < NUM; i++) {
|
||||
table.num = i;
|
||||
for (j = 0; j < (i + 2) * 3; j++) {
|
||||
n = find_entry(&table, j);
|
||||
ok1(n <= i);
|
||||
|
||||
/* If we were searching for something too large... */
|
||||
if (j > i*3)
|
||||
ok1(n == i);
|
||||
else {
|
||||
/* It must give us something after j */
|
||||
ok1(f[n].head >= j);
|
||||
ok1(n == 0 || f[n-1].head < j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exit_status();
|
||||
}
|
126
lib/tdb/test/run-rescue.c
Normal file
126
lib/tdb/test/run-rescue.c
Normal file
@ -0,0 +1,126 @@
|
||||
#include "../common/tdb_private.h"
|
||||
#include "../common/io.c"
|
||||
#include "../common/tdb.c"
|
||||
#include "../common/lock.c"
|
||||
#include "../common/freelist.c"
|
||||
#include "../common/traverse.c"
|
||||
#include "../common/transaction.c"
|
||||
#include "../common/error.c"
|
||||
#include "../common/open.c"
|
||||
#include "../common/check.c"
|
||||
#include "../common/hash.c"
|
||||
#include "../common/rescue.c"
|
||||
#include "tap-interface.h"
|
||||
#include <stdlib.h>
|
||||
#include "logging.h"
|
||||
|
||||
struct walk_data {
|
||||
TDB_DATA key;
|
||||
TDB_DATA data;
|
||||
bool fail;
|
||||
unsigned count;
|
||||
};
|
||||
|
||||
static inline bool tdb_deq(TDB_DATA a, TDB_DATA b)
|
||||
{
|
||||
return a.dsize == b.dsize && memcmp(a.dptr, b.dptr, a.dsize) == 0;
|
||||
}
|
||||
|
||||
static inline TDB_DATA tdb_mkdata(const void *p, size_t len)
|
||||
{
|
||||
TDB_DATA d;
|
||||
d.dptr = (void *)p;
|
||||
d.dsize = len;
|
||||
return d;
|
||||
}
|
||||
|
||||
static void walk(TDB_DATA key, TDB_DATA data, void *_wd)
|
||||
{
|
||||
struct walk_data *wd = _wd;
|
||||
|
||||
if (!tdb_deq(key, wd->key)) {
|
||||
wd->fail = true;
|
||||
}
|
||||
|
||||
if (!tdb_deq(data, wd->data)) {
|
||||
wd->fail = true;
|
||||
}
|
||||
wd->count++;
|
||||
}
|
||||
|
||||
static void count_records(TDB_DATA key, TDB_DATA data, void *_wd)
|
||||
{
|
||||
struct walk_data *wd = _wd;
|
||||
|
||||
if (!tdb_deq(key, wd->key) || !tdb_deq(data, wd->data))
|
||||
diag("%.*s::%.*s\n",
|
||||
(int)key.dsize, key.dptr, (int)data.dsize, data.dptr);
|
||||
wd->count++;
|
||||
}
|
||||
|
||||
static void log_fn(struct tdb_context *tdb, enum tdb_debug_level level, const char *fmt, ...)
|
||||
{
|
||||
unsigned int *count = tdb_get_logging_private(tdb);
|
||||
(*count)++;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct tdb_context *tdb;
|
||||
struct walk_data wd;
|
||||
unsigned int i, size, log_count = 0;
|
||||
struct tdb_logging_context log_ctx = { log_fn, &log_count };
|
||||
|
||||
plan_tests(8);
|
||||
tdb = tdb_open_ex("run-rescue.tdb", 1, TDB_CLEAR_IF_FIRST,
|
||||
O_CREAT|O_TRUNC|O_RDWR, 0600, &log_ctx, NULL);
|
||||
|
||||
wd.key.dsize = strlen("hi");
|
||||
wd.key.dptr = (void *)"hi";
|
||||
wd.data.dsize = strlen("world");
|
||||
wd.data.dptr = (void *)"world";
|
||||
wd.count = 0;
|
||||
wd.fail = false;
|
||||
|
||||
ok1(tdb_store(tdb, wd.key, wd.data, TDB_INSERT) == 0);
|
||||
|
||||
ok1(tdb_rescue(tdb, walk, &wd) == 0);
|
||||
ok1(!wd.fail);
|
||||
ok1(wd.count == 1);
|
||||
|
||||
/* Corrupt the database, walk should either get it or not. */
|
||||
size = tdb->map_size;
|
||||
for (i = sizeof(struct tdb_header); i < size; i++) {
|
||||
char c;
|
||||
if (tdb->methods->tdb_read(tdb, i, &c, 1, false) != 0)
|
||||
fail("Reading offset %i", i);
|
||||
if (tdb->methods->tdb_write(tdb, i, "X", 1) != 0)
|
||||
fail("Writing X at offset %i", i);
|
||||
|
||||
wd.count = 0;
|
||||
if (tdb_rescue(tdb, count_records, &wd) != 0) {
|
||||
wd.fail = true;
|
||||
break;
|
||||
}
|
||||
/* Could be 0 or 1. */
|
||||
if (wd.count > 1) {
|
||||
wd.fail = true;
|
||||
break;
|
||||
}
|
||||
if (tdb->methods->tdb_write(tdb, i, &c, 1) != 0)
|
||||
fail("Restoring offset %i", i);
|
||||
}
|
||||
ok1(log_count == 0);
|
||||
ok1(!wd.fail);
|
||||
tdb_close(tdb);
|
||||
|
||||
/* Now try our known-corrupt db. */
|
||||
tdb = tdb_open_ex("test/tdb.corrupt", 1024, 0, O_RDWR, 0,
|
||||
&taplogctx, NULL);
|
||||
wd.count = 0;
|
||||
ok1(tdb_rescue(tdb, count_records, &wd) == 0);
|
||||
ok1(wd.count == 1627);
|
||||
tdb_close(tdb);
|
||||
|
||||
return exit_status();
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
APPNAME = 'tdb'
|
||||
VERSION = '1.2.10'
|
||||
VERSION = '1.2.11'
|
||||
|
||||
blddir = 'bin'
|
||||
|
||||
@ -65,7 +65,7 @@ def build(bld):
|
||||
COMMON_SRC = bld.SUBDIR('common',
|
||||
'''check.c error.c tdb.c traverse.c
|
||||
freelistcheck.c lock.c dump.c freelist.c
|
||||
io.c open.c transaction.c hash.c summary.c''')
|
||||
io.c open.c transaction.c hash.c summary.c rescue.c''')
|
||||
|
||||
if bld.env.standalone_tdb:
|
||||
bld.env.PKGCONFIGDIR = '${LIBDIR}/pkgconfig'
|
||||
@ -143,6 +143,10 @@ def build(bld):
|
||||
'replace tdb-test-helpers', includes='include', install=False)
|
||||
bld.SAMBA_BINARY('tdb1-run-readonly-check', 'test/run-readonly-check.c',
|
||||
'replace tdb-test-helpers', includes='include', install=False)
|
||||
bld.SAMBA_BINARY('tdb1-run-rescue', 'test/run-rescue.c',
|
||||
'replace tdb-test-helpers', includes='include', install=False)
|
||||
bld.SAMBA_BINARY('tdb1-run-rescue-find_entry', 'test/run-rescue-find_entry.c',
|
||||
'replace tdb-test-helpers', includes='include', install=False)
|
||||
bld.SAMBA_BINARY('tdb1-run-rwlock-check', 'test/run-rwlock-check.c',
|
||||
'replace tdb-test-helpers', includes='include', install=False)
|
||||
bld.SAMBA_BINARY('tdb1-run-summary', 'test/run-summary.c',
|
||||
@ -185,7 +189,7 @@ def testonly(ctx):
|
||||
if not os.path.exists(link):
|
||||
os.symlink(os.path.abspath(os.path.join(env.cwd, 'test')), link)
|
||||
|
||||
for f in 'tdb1-run-3G-file', 'tdb1-run-bad-tdb-header', 'tdb1-run', 'tdb1-run-check', 'tdb1-run-corrupt', 'tdb1-run-die-during-transaction', 'tdb1-run-endian', 'tdb1-run-incompatible', 'tdb1-run-nested-transactions', 'tdb1-run-nested-traverse', 'tdb1-run-no-lock-during-traverse', 'tdb1-run-oldhash', 'tdb1-run-open-during-transaction', 'tdb1-run-readonly-check', 'tdb1-run-rwlock-check', 'tdb1-run-summary', 'tdb1-run-transaction-expand', 'tdb1-run-traverse-in-transaction', 'tdb1-run-wronghash-fail', 'tdb1-run-zero-append':
|
||||
for f in 'tdb1-run-3G-file', 'tdb1-run-bad-tdb-header', 'tdb1-run', 'tdb1-run-check', 'tdb1-run-corrupt', 'tdb1-run-die-during-transaction', 'tdb1-run-endian', 'tdb1-run-incompatible', 'tdb1-run-nested-transactions', 'tdb1-run-nested-traverse', 'tdb1-run-no-lock-during-traverse', 'tdb1-run-oldhash', 'tdb1-run-open-during-transaction', 'tdb1-run-readonly-check', 'tdb1-run-rescue', 'tdb1-run-rescue-find_entry', 'tdb1-run-rwlock-check', 'tdb1-run-summary', 'tdb1-run-transaction-expand', 'tdb1-run-traverse-in-transaction', 'tdb1-run-wronghash-fail', 'tdb1-run-zero-append':
|
||||
cmd = "cd " + testdir + " && " + os.path.abspath(os.path.join(Utils.g_module.blddir, f)) + " > test-output 2>&1"
|
||||
print("..." + f)
|
||||
ret = samba_utils.RUN_COMMAND(cmd)
|
||||
|
Loading…
x
Reference in New Issue
Block a user