2 Unix SMB/CIFS implementation.
4 trivial database library, rescue attempt code.
6 Copyright (C) Rusty Russell 2012
8 ** NOTE! The following LGPL license applies to the tdb
9 ** library. This does NOT imply that all of Samba is released
12 This library is free software; you can redistribute it and/or
13 modify it under the terms of the GNU Lesser General Public
14 License as published by the Free Software Foundation; either
15 version 3 of the License, or (at your option) any later version.
17 This library is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 Lesser General Public License for more details.
22 You should have received a copy of the GNU Lesser General Public
23 License along with this library; if not, see <http://www.gnu.org/licenses/>.
25 #include "tdb_private.h"
30 tdb_off_t head
; /* 0 -> invalid. */
31 struct tdb_record rec
;
38 /* As an ordered array (by head offset). */
40 unsigned int num
, max
;
43 static bool looks_like_valid_record(struct tdb_context
*tdb
,
45 const struct tdb_record
*rec
,
50 if (rec
->magic
!= TDB_MAGIC
)
53 if (rec
->key_len
+ rec
->data_len
> rec
->rec_len
)
56 if (rec
->rec_len
% TDB_ALIGNMENT
)
59 /* Next pointer must make some sense. */
60 if (rec
->next
> 0 && rec
->next
< TDB_DATA_START(tdb
->hash_size
))
63 if (tdb_oob(tdb
, rec
->next
, sizeof(*rec
), 1))
66 key
->dsize
= rec
->key_len
;
67 key
->dptr
= tdb_alloc_read(tdb
, off
+ sizeof(*rec
), key
->dsize
);
71 hval
= tdb
->hash_fn(key
);
72 if (hval
!= rec
->full_hash
) {
77 /* Caller frees up key->dptr */
81 static bool add_to_table(struct found_table
*found
,
83 struct tdb_record
*rec
,
86 if (found
->num
+ 1 > found
->max
) {
88 found
->max
= (found
->max
? found
->max
* 2 : 128);
89 new = realloc(found
->arr
, found
->max
* sizeof(found
->arr
[0]));
95 found
->arr
[found
->num
].head
= off
;
96 found
->arr
[found
->num
].rec
= *rec
;
97 found
->arr
[found
->num
].key
= key
;
98 found
->arr
[found
->num
].in_hash
= false;
99 found
->arr
[found
->num
].in_free
= false;
105 static bool walk_record(struct tdb_context
*tdb
,
106 const struct found
*f
,
107 void (*walk
)(TDB_DATA
, TDB_DATA
, void *private_data
),
112 data
.dsize
= f
->rec
.data_len
;
113 data
.dptr
= tdb_alloc_read(tdb
,
114 f
->head
+ sizeof(f
->rec
) + f
->rec
.key_len
,
117 if (tdb
->ecode
== TDB_ERR_OOM
)
119 /* I/O errors are expected. */
123 walk(f
->key
, data
, private_data
);
128 /* First entry which has offset >= this one. */
129 static unsigned int find_entry(struct found_table
*found
, tdb_off_t off
)
131 unsigned int start
= 0, end
= found
->num
;
133 while (start
< end
) {
134 /* We can't overflow here. */
135 unsigned int mid
= (start
+ end
) / 2;
137 if (off
< found
->arr
[mid
].head
) {
139 } else if (off
> found
->arr
[mid
].head
) {
146 assert(start
== end
);
150 static void found_in_hashchain(struct found_table
*found
, tdb_off_t head
)
154 match
= find_entry(found
, head
);
155 if (match
< found
->num
&& found
->arr
[match
].head
== head
) {
156 found
->arr
[match
].in_hash
= true;
160 static void mark_free_area(struct found_table
*found
, tdb_off_t head
,
165 match
= find_entry(found
, head
);
166 /* Mark everything within this free entry. */
167 while (match
< found
->num
) {
168 if (found
->arr
[match
].head
>= head
+ len
) {
171 found
->arr
[match
].in_free
= true;
176 static int cmp_key(const void *a
, const void *b
)
178 const struct found
*fa
= a
, *fb
= b
;
180 if (fa
->key
.dsize
< fb
->key
.dsize
) {
182 } else if (fa
->key
.dsize
> fb
->key
.dsize
) {
185 return memcmp(fa
->key
.dptr
, fb
->key
.dptr
, fa
->key
.dsize
);
188 static bool key_eq(TDB_DATA a
, TDB_DATA b
)
190 return a
.dsize
== b
.dsize
191 && memcmp(a
.dptr
, b
.dptr
, a
.dsize
) == 0;
194 static void free_table(struct found_table
*found
)
198 for (i
= 0; i
< found
->num
; i
++) {
199 free(found
->arr
[i
].key
.dptr
);
204 static void logging_suppressed(struct tdb_context
*tdb
,
205 enum tdb_debug_level level
, const char *fmt
, ...)
209 _PUBLIC_
int tdb_rescue(struct tdb_context
*tdb
,
210 void (*walk
)(TDB_DATA
, TDB_DATA
, void *private_data
),
213 struct found_table found
= { NULL
, 0, 0 };
215 tdb_log_func oldlog
= tdb
->log
.log_fn
;
216 struct tdb_record rec
;
220 /* Read-only databases use no locking at all: it's best-effort.
221 * We may have a write lock already, so skip that case too. */
222 if (tdb
->read_only
|| tdb
->allrecord_lock
.count
!= 0) {
225 if (tdb_lockall_read(tdb
) == -1)
230 /* Make sure we know true size of the underlying file. */
231 tdb_oob(tdb
, tdb
->map_size
, 1, 1);
233 /* Suppress logging, since we anticipate errors. */
234 tdb
->log
.log_fn
= logging_suppressed
;
236 /* Now walk entire db looking for records. */
237 for (off
= TDB_DATA_START(tdb
->hash_size
);
239 off
+= TDB_ALIGNMENT
) {
240 if (tdb
->methods
->tdb_read(tdb
, off
, &rec
, sizeof(rec
),
244 if (looks_like_valid_record(tdb
, off
, &rec
, &key
)) {
245 if (!add_to_table(&found
, off
, &rec
, key
)) {
251 /* Walk hash chains to positive vet. */
252 for (h
= 0; h
< 1+tdb
->hash_size
; h
++) {
253 bool slow_chase
= false;
254 tdb_off_t slow_off
= FREELIST_TOP
+ h
*sizeof(tdb_off_t
);
256 if (tdb_ofs_read(tdb
, FREELIST_TOP
+ h
*sizeof(tdb_off_t
),
260 while (off
&& off
!= slow_off
) {
261 if (tdb
->methods
->tdb_read(tdb
, off
, &rec
, sizeof(rec
),
266 /* 0 is the free list, rest are hash chains. */
268 /* Don't mark garbage as free. */
269 if (rec
.magic
!= TDB_FREE_MAGIC
) {
272 mark_free_area(&found
, off
,
273 sizeof(rec
) + rec
.rec_len
);
275 found_in_hashchain(&found
, off
);
280 /* Loop detection using second pointer at half-speed */
282 /* First entry happens to be next ptr */
283 tdb_ofs_read(tdb
, slow_off
, &slow_off
);
285 slow_chase
= !slow_chase
;
289 /* Recovery area: must be marked as free, since it often has old
290 * records in there! */
291 if (tdb_ofs_read(tdb
, TDB_RECOVERY_HEAD
, &off
) == 0 && off
!= 0) {
292 if (tdb
->methods
->tdb_read(tdb
, off
, &rec
, sizeof(rec
),
294 mark_free_area(&found
, off
, sizeof(rec
) + rec
.rec_len
);
298 /* Now sort by key! */
299 if (found
.arr
!= NULL
) {
300 qsort(found
.arr
, found
.num
, sizeof(found
.arr
[0]), cmp_key
);
303 for (i
= 0; (found
.arr
!= NULL
) && i
< found
.num
; ) {
304 unsigned int num
, num_in_hash
= 0;
306 /* How many are identical? */
307 for (num
= 0; num
< found
.num
- i
; num
++) {
308 if (!key_eq(found
.arr
[i
].key
, found
.arr
[i
+num
].key
)) {
311 if (found
.arr
[i
+num
].in_hash
) {
312 if (!walk_record(tdb
, &found
.arr
[i
+num
],
320 /* If none were in the hash, we print any not in free list. */
321 if (num_in_hash
== 0) {
324 for (j
= i
; j
< i
+ num
; j
++) {
325 if (!found
.arr
[j
].in_free
) {
326 if (!walk_record(tdb
, &found
.arr
[j
],
336 tdb
->log
.log_fn
= oldlog
;
338 tdb_unlockall_read(tdb
);
343 tdb
->log
.log_fn
= oldlog
;
344 tdb
->ecode
= TDB_ERR_OOM
;
345 TDB_LOG((tdb
, TDB_DEBUG_ERROR
, "tdb_rescue: failed allocating\n"));
348 tdb_unlockall_read(tdb
);