s3:winbindd: s/struct timed_event/struct tevent_timer
[Samba/gebeck_regimport.git] / lib / tdb / common / rescue.c
blob17e7ed85453ddb700890ca03b0f0bf34bac020ba
1 /*
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
10 ** under the LGPL
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"
26 #include <assert.h>
29 struct found {
30 tdb_off_t head; /* 0 -> invalid. */
31 struct tdb_record rec;
32 TDB_DATA key;
33 bool in_hash;
34 bool in_free;
37 struct found_table {
38 /* As an ordered array (by head offset). */
39 struct found *arr;
40 unsigned int num, max;
43 static bool looks_like_valid_record(struct tdb_context *tdb,
44 tdb_off_t off,
45 const struct tdb_record *rec,
46 TDB_DATA *key)
48 unsigned int hval;
50 if (rec->magic != TDB_MAGIC)
51 return false;
53 if (rec->key_len + rec->data_len > rec->rec_len)
54 return false;
56 if (rec->rec_len % TDB_ALIGNMENT)
57 return false;
59 /* Next pointer must make some sense. */
60 if (rec->next > 0 && rec->next < TDB_DATA_START(tdb->hash_size))
61 return false;
63 if (tdb->methods->tdb_oob(tdb, rec->next, sizeof(*rec), 1))
64 return false;
66 key->dsize = rec->key_len;
67 key->dptr = tdb_alloc_read(tdb, off + sizeof(*rec), key->dsize);
68 if (!key->dptr)
69 return false;
71 hval = tdb->hash_fn(key);
72 if (hval != rec->full_hash) {
73 free(key->dptr);
74 return false;
77 /* Caller frees up key->dptr */
78 return true;
81 static bool add_to_table(struct found_table *found,
82 tdb_off_t off,
83 struct tdb_record *rec,
84 TDB_DATA key)
86 if (found->num + 1 > found->max) {
87 struct found *new;
88 found->max = (found->max ? found->max * 2 : 128);
89 new = realloc(found->arr, found->max * sizeof(found->arr[0]));
90 if (!new)
91 return false;
92 found->arr = new;
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;
101 found->num++;
102 return true;
105 static bool walk_record(struct tdb_context *tdb,
106 const struct found *f,
107 void (*walk)(TDB_DATA, TDB_DATA, void *private_data),
108 void *private_data)
110 TDB_DATA 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,
115 data.dsize);
116 if (!data.dptr) {
117 if (tdb->ecode == TDB_ERR_OOM)
118 return false;
119 /* I/O errors are expected. */
120 return true;
123 walk(f->key, data, private_data);
124 free(data.dptr);
125 return true;
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) {
138 end = mid;
139 } else if (off > found->arr[mid].head) {
140 start = mid + 1;
141 } else {
142 return mid;
146 assert(start == end);
147 return end;
150 static void found_in_hashchain(struct found_table *found, tdb_off_t head)
152 unsigned int match;
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,
161 tdb_len_t len)
163 unsigned int match;
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) {
169 break;
171 found->arr[match].in_free = true;
172 match++;
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) {
181 return -1;
182 } else if (fa->key.dsize > fb->key.dsize) {
183 return 1;
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)
196 unsigned int i;
198 for (i = 0; i < found->num; i++) {
199 free(found->arr[i].key.dptr);
201 free(found->arr);
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),
211 void *private_data)
213 struct found_table found = { NULL, 0, 0 };
214 tdb_off_t h, off, i;
215 tdb_log_func oldlog = tdb->log.log_fn;
216 struct tdb_record rec;
217 TDB_DATA key;
218 bool locked;
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) {
223 locked = false;
224 } else {
225 if (tdb_lockall_read(tdb) == -1)
226 return -1;
227 locked = true;
230 /* Make sure we know true size of the underlying file. */
231 tdb->methods->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);
238 off < tdb->map_size;
239 off += TDB_ALIGNMENT) {
240 if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
241 DOCONV()) == -1)
242 continue;
244 if (looks_like_valid_record(tdb, off, &rec, &key)) {
245 if (!add_to_table(&found, off, &rec, key)) {
246 goto oom;
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),
257 &off) == -1)
258 continue;
260 while (off && off != slow_off) {
261 if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
262 DOCONV()) != 0) {
263 break;
266 /* 0 is the free list, rest are hash chains. */
267 if (h == 0) {
268 /* Don't mark garbage as free. */
269 if (rec.magic != TDB_FREE_MAGIC) {
270 break;
272 mark_free_area(&found, off,
273 sizeof(rec) + rec.rec_len);
274 } else {
275 found_in_hashchain(&found, off);
278 off = rec.next;
280 /* Loop detection using second pointer at half-speed */
281 if (slow_chase) {
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),
293 DOCONV()) == 0) {
294 mark_free_area(&found, off, sizeof(rec) + rec.rec_len);
298 /* Now sort by key! */
299 qsort(found.arr, found.num, sizeof(found.arr[0]), cmp_key);
301 for (i = 0; i < found.num; ) {
302 unsigned int num, num_in_hash = 0;
304 /* How many are identical? */
305 for (num = 0; num < found.num - i; num++) {
306 if (!key_eq(found.arr[i].key, found.arr[i+num].key)) {
307 break;
309 if (found.arr[i+num].in_hash) {
310 if (!walk_record(tdb, &found.arr[i+num],
311 walk, private_data))
312 goto oom;
313 num_in_hash++;
316 assert(num);
318 /* If none were in the hash, we print any not in free list. */
319 if (num_in_hash == 0) {
320 unsigned int j;
322 for (j = i; j < i + num; j++) {
323 if (!found.arr[j].in_free) {
324 if (!walk_record(tdb, &found.arr[j],
325 walk, private_data))
326 goto oom;
331 i += num;
334 tdb->log.log_fn = oldlog;
335 if (locked) {
336 tdb_unlockall_read(tdb);
338 return 0;
340 oom:
341 tdb->log.log_fn = oldlog;
342 tdb->ecode = TDB_ERR_OOM;
343 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_rescue: failed allocating\n"));
344 free_table(&found);
345 if (locked) {
346 tdb_unlockall_read(tdb);
348 return -1;