The branch, master has been updated via 100d38d tdb: add -e option to tdbdump (and docment it). via ffde867 tdb: tdbdump should log errors, and fail in that case. via 90f463b tdb: add tdb_rescue() from fe38a93 Correct fix for bug #9222 - smbd ignores the "server signing = no" setting for SMB2.
http://gitweb.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 100d38d6e0fae1dc666ae43941570c9f106b73c2 Author: Rusty Russell <ru...@rustcorp.com.au> Date: Thu Oct 4 09:04:23 2012 +0930 tdb: add -e option to tdbdump (and docment it). This allows for an emergency best-effort dump. It's a little better than strings(1). Signed-off-by: Rusty Russell <ru...@rustcorp.com.au> Autobuild-User(master): Rusty Russell <ru...@rustcorp.com.au> Autobuild-Date(master): Thu Oct 4 03:16:06 CEST 2012 on sn-devel-104 commit ffde8678910ae838588ab380b48a544333f81241 Author: Rusty Russell <ru...@rustcorp.com.au> Date: Thu Oct 4 09:04:23 2012 +0930 tdb: tdbdump should log errors, and fail in that case. Dumping a corrupt database should not exit silently with 0 status! Signed-off-by: Rusty Russell <ru...@rustcorp.com.au> commit 90f463b25f7bb0bc944732773c56e356834ea203 Author: Rusty Russell <ru...@rustcorp.com.au> Date: Thu Oct 4 09:04:19 2012 +0930 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 <ru...@rustcorp.com.au> ----------------------------------------------------------------------- Summary of changes: lib/tdb/ABI/{tdb-1.2.10.sigs => tdb-1.2.11.sigs} | 1 + lib/tdb/common/rescue.c | 349 ++++++++++++++++++++++ lib/tdb/include/tdb.h | 22 ++ lib/tdb/libtdb.m4 | 2 +- lib/tdb/manpages/tdbdump.8.xml | 31 ++ lib/tdb/test/run-rescue-find_entry.c | 50 +++ lib/tdb/test/run-rescue.c | 126 ++++++++ lib/tdb/tools/tdbdump.c | 65 ++++- lib/tdb/wscript | 10 +- 9 files changed, 647 insertions(+), 9 deletions(-) copy lib/tdb/ABI/{tdb-1.2.10.sigs => tdb-1.2.11.sigs} (97%) create mode 100644 lib/tdb/common/rescue.c create mode 100644 lib/tdb/test/run-rescue-find_entry.c create mode 100644 lib/tdb/test/run-rescue.c Changeset truncated at 500 lines: diff --git a/lib/tdb/ABI/tdb-1.2.10.sigs b/lib/tdb/ABI/tdb-1.2.11.sigs similarity index 97% copy from lib/tdb/ABI/tdb-1.2.10.sigs copy to lib/tdb/ABI/tdb-1.2.11.sigs index 61f6c19..d727f21 100644 --- a/lib/tdb/ABI/tdb-1.2.10.sigs +++ b/lib/tdb/ABI/tdb-1.2.11.sigs @@ -45,6 +45,7 @@ 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 *) diff --git a/lib/tdb/common/rescue.c b/lib/tdb/common/rescue.c new file mode 100644 index 0000000..03ae8d6 --- /dev/null +++ b/lib/tdb/common/rescue.c @@ -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; +} diff --git a/lib/tdb/include/tdb.h b/lib/tdb/include/tdb.h index 5eb9562..d19439e 100644 --- a/lib/tdb/include/tdb.h +++ b/lib/tdb/include/tdb.h @@ -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 */ diff --git a/lib/tdb/libtdb.m4 b/lib/tdb/libtdb.m4 index b5164fc..47f098e 100644 --- a/lib/tdb/libtdb.m4 +++ b/lib/tdb/libtdb.m4 @@ -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) diff --git a/lib/tdb/manpages/tdbdump.8.xml b/lib/tdb/manpages/tdbdump.8.xml index 90465e5..3420193 100644 --- a/lib/tdb/manpages/tdbdump.8.xml +++ b/lib/tdb/manpages/tdbdump.8.xml @@ -19,6 +19,9 @@ <refsynopsisdiv> <cmdsynopsis> <command>tdbdump</command> + <arg choice="opt">-k <replaceable>keyname</replaceable></arg> + <arg choice="opt">-e</arg> + <arg choice="opt">-h</arg> <arg choice="req">filename</arg> </cmdsynopsis> </refsynopsisdiv> @@ -39,6 +42,34 @@ </para> </refsect1> +<refsect1> + <title>OPTIONS</title> + + <variablelist> + + <varlistentry> + <term>-h</term> + <listitem><para> + Get help information. + </para></listitem> + </varlistentry> + + <varlistentry> + <term>-k <replaceable>keyname</replaceable></term> + <listitem><para> + The <command>-k</command> option restricts dumping to a single key, if found. + </para> </listitem> + </varlistentry> + + <varlistentry> + <term>-e</term> + <listitem><para> + The <command>-e</command> tries to dump out from a corrupt database. Naturally, such a dump is unreliable, at best. + </para></listitem> + </varlistentry> + + </variablelist> +</refsect1> <refsect1> <title>VERSION</title> diff --git a/lib/tdb/test/run-rescue-find_entry.c b/lib/tdb/test/run-rescue-find_entry.c new file mode 100644 index 0000000..25f4f1c --- /dev/null +++ b/lib/tdb/test/run-rescue-find_entry.c @@ -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; + } -- Samba Shared Repository