]> git.ozlabs.org Git - ccan/blob - ccan/tdb2/tdb1_check.c
tdb2: unify tdb1_check and tdb1_summary into tdb_check and tdb_summary.
[ccan] / ccan / tdb2 / tdb1_check.c
1  /*
2    Unix SMB/CIFS implementation.
3
4    trivial database library
5
6    Copyright (C) Rusty Russell             2009
7
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
11
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.
16
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.
21
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/>.
24 */
25 #include "tdb1_private.h"
26
27 /* Since we opened it, these shouldn't fail unless it's recent corruption. */
28 static bool tdb1_check_header(struct tdb_context *tdb, tdb1_off_t *recovery)
29 {
30         struct tdb1_header hdr;
31         uint32_t h1, h2;
32
33         if (tdb->tdb1.io->tdb1_read(tdb, 0, &hdr, sizeof(hdr), 0) == -1)
34                 return false;
35         if (strcmp(hdr.magic_food, TDB_MAGIC_FOOD) != 0)
36                 goto corrupt;
37
38         TDB1_CONV(hdr);
39         if (hdr.version != TDB1_VERSION)
40                 goto corrupt;
41
42         if (hdr.rwlocks != 0 && hdr.rwlocks != TDB1_HASH_RWLOCK_MAGIC)
43                 goto corrupt;
44
45         tdb1_header_hash(tdb, &h1, &h2);
46         if (hdr.magic1_hash && hdr.magic2_hash &&
47             (hdr.magic1_hash != h1 || hdr.magic2_hash != h2))
48                 goto corrupt;
49
50         if (hdr.hash_size == 0)
51                 goto corrupt;
52
53         if (hdr.hash_size != tdb->tdb1.header.hash_size)
54                 goto corrupt;
55
56         if (hdr.recovery_start != 0 &&
57             hdr.recovery_start < TDB1_DATA_START(tdb->tdb1.header.hash_size))
58                 goto corrupt;
59
60         *recovery = hdr.recovery_start;
61         return true;
62
63 corrupt:
64         tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
65                                 "Header is corrupt\n");
66         return false;
67 }
68
69 /* Generic record header check. */
70 static bool tdb1_check_record(struct tdb_context *tdb,
71                              tdb1_off_t off,
72                              const struct tdb1_record *rec)
73 {
74         tdb1_off_t tailer;
75
76         /* Check rec->next: 0 or points to record offset, aligned. */
77         if (rec->next > 0 && rec->next < TDB1_DATA_START(tdb->tdb1.header.hash_size)){
78                 tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
79                            "Record offset %d too small next %d\n",
80                            off, rec->next);
81                 goto corrupt;
82         }
83         if (rec->next + sizeof(*rec) < rec->next) {
84                 tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
85                            "Record offset %d too large next %d\n",
86                            off, rec->next);
87                 goto corrupt;
88         }
89         if ((rec->next % TDB1_ALIGNMENT) != 0) {
90                 tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
91                            "Record offset %d misaligned next %d\n",
92                            off, rec->next);
93                 goto corrupt;
94         }
95         if (tdb->tdb1.io->tdb1_oob(tdb, rec->next+sizeof(*rec), 0))
96                 goto corrupt;
97
98         /* Check rec_len: similar to rec->next, implies next record. */
99         if ((rec->rec_len % TDB1_ALIGNMENT) != 0) {
100                 tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
101                            "Record offset %d misaligned length %d\n",
102                            off, rec->rec_len);
103                 goto corrupt;
104         }
105         /* Must fit tailer. */
106         if (rec->rec_len < sizeof(tailer)) {
107                 tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
108                            "Record offset %d too short length %d\n",
109                            off, rec->rec_len);
110                 goto corrupt;
111         }
112         /* OOB allows "right at the end" access, so this works for last rec. */
113         if (tdb->tdb1.io->tdb1_oob(tdb, off+sizeof(*rec)+rec->rec_len, 0))
114                 goto corrupt;
115
116         /* Check tailer. */
117         if (tdb1_ofs_read(tdb, off+sizeof(*rec)+rec->rec_len-sizeof(tailer),
118                          &tailer) == -1)
119                 goto corrupt;
120         if (tailer != sizeof(*rec) + rec->rec_len) {
121                 tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
122                            "Record offset %d invalid tailer\n", off);
123                 goto corrupt;
124         }
125
126         return true;
127
128 corrupt:
129         tdb->last_error = TDB_ERR_CORRUPT;
130         return false;
131 }
132
133 /* Grab some bytes: may copy if can't use mmap.
134    Caller has already done bounds check. */
135 static TDB_DATA get_bytes(struct tdb_context *tdb,
136                           tdb1_off_t off, tdb1_len_t len)
137 {
138         TDB_DATA d;
139
140         d.dsize = len;
141
142         if (tdb->tdb1.transaction == NULL && tdb->file->map_ptr != NULL)
143                 d.dptr = (unsigned char *)tdb->file->map_ptr + off;
144         else
145                 d.dptr = tdb1_alloc_read(tdb, off, d.dsize);
146         return d;
147 }
148
149 /* Frees data if we're not able to simply use mmap. */
150 static void put_bytes(struct tdb_context *tdb, TDB_DATA d)
151 {
152         if (tdb->tdb1.transaction == NULL && tdb->file->map_ptr != NULL)
153                 return;
154         free(d.dptr);
155 }
156
157 /* We use the excellent Jenkins lookup3 hash; this is based on hash_word2.
158  * See: http://burtleburtle.net/bob/c/lookup3.c
159  */
160 #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
161 static void jhash(uint32_t key, uint32_t *pc, uint32_t *pb)
162 {
163         uint32_t a,b,c;
164
165         /* Set up the internal state */
166         a = b = c = 0xdeadbeef + *pc;
167         c += *pb;
168         a += key;
169         c ^= b; c -= rot(b,14);
170         a ^= c; a -= rot(c,11);
171         b ^= a; b -= rot(a,25);
172         c ^= b; c -= rot(b,16);
173         a ^= c; a -= rot(c,4);
174         b ^= a; b -= rot(a,14);
175         c ^= b; c -= rot(b,24);
176         *pc=c; *pb=b;
177 }
178
179 /*
180   We want to check that all free records are in the free list
181   (only once), and all free list entries are free records.  Similarly
182   for each hash chain of used records.
183
184   Doing that naively (without walking hash chains, since we want to be
185   linear) means keeping a list of records which have been seen in each
186   hash chain, and another of records pointed to (ie. next pointers
187   from records and the initial hash chain heads).  These two lists
188   should be equal.  This will take 8 bytes per record, and require
189   sorting at the end.
190
191   So instead, we record each offset in a bitmap such a way that
192   recording it twice will cancel out.  Since each offset should appear
193   exactly twice, the bitmap should be zero at the end.
194
195   The approach was inspired by Bloom Filters (see Wikipedia).  For
196   each value, we flip K bits in a bitmap of size N.  The number of
197   distinct arrangements is:
198
199         N! / (K! * (N-K)!)
200
201   Of course, not all arrangements are actually distinct, but testing
202   shows this formula to be close enough.
203
204   So, if K == 8 and N == 256, the probability of two things flipping the same
205   bits is 1 in 409,663,695,276,000.
206
207   Given that ldb uses a hash size of 10000, using 32 bytes per hash chain
208   (320k) seems reasonable.
209 */
210 #define NUM_HASHES 8
211 #define BITMAP_BITS 256
212
213 static void bit_flip(unsigned char bits[], unsigned int idx)
214 {
215         bits[idx / CHAR_BIT] ^= (1 << (idx % CHAR_BIT));
216 }
217
218 /* We record offsets in a bitmap for the particular chain it should be in.  */
219 static void record_offset(unsigned char bits[], tdb1_off_t off)
220 {
221         uint32_t h1 = off, h2 = 0;
222         unsigned int i;
223
224         /* We get two good hash values out of jhash2, so we use both.  Then
225          * we keep going to produce further hash values. */
226         for (i = 0; i < NUM_HASHES / 2; i++) {
227                 jhash(off, &h1, &h2);
228                 bit_flip(bits, h1 % BITMAP_BITS);
229                 bit_flip(bits, h2 % BITMAP_BITS);
230                 h2++;
231         }
232 }
233
234 /* Check that an in-use record is valid. */
235 static bool tdb1_check_used_record(struct tdb_context *tdb,
236                                   tdb1_off_t off,
237                                   const struct tdb1_record *rec,
238                                   unsigned char **hashes,
239                                   enum TDB_ERROR (*check)(TDB_DATA, TDB_DATA,
240                                                           void *),
241                                   void *private_data)
242 {
243         TDB_DATA key, data;
244
245         if (!tdb1_check_record(tdb, off, rec))
246                 return false;
247
248         /* key + data + tailer must fit in record */
249         if (rec->key_len + rec->data_len + sizeof(tdb1_off_t) > rec->rec_len) {
250                 tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
251                                         "Record offset %d too short for contents\n", off);
252                 return false;
253         }
254
255         key = get_bytes(tdb, off + sizeof(*rec), rec->key_len);
256         if (!key.dptr)
257                 return false;
258
259         if ((uint32_t)tdb_hash(tdb, key.dptr, key.dsize) != rec->full_hash) {
260                 tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
261                                         "Record offset %d has incorrect hash\n", off);
262                 goto fail_put_key;
263         }
264
265         /* Mark this offset as a known value for this hash bucket. */
266         record_offset(hashes[TDB1_BUCKET(rec->full_hash)+1], off);
267         /* And similarly if the next pointer is valid. */
268         if (rec->next)
269                 record_offset(hashes[TDB1_BUCKET(rec->full_hash)+1], rec->next);
270
271         /* If they supply a check function and this record isn't dead,
272            get data and feed it. */
273         if (check && rec->magic != TDB1_DEAD_MAGIC) {
274                 enum TDB_ERROR ecode;
275
276                 data = get_bytes(tdb, off + sizeof(*rec) + rec->key_len,
277                                  rec->data_len);
278                 if (!data.dptr)
279                         goto fail_put_key;
280
281                 ecode = check(key, data, private_data);
282                 if (ecode != TDB_SUCCESS) {
283                         tdb->last_error = ecode;
284                         goto fail_put_data;
285                 }
286                 put_bytes(tdb, data);
287         }
288
289         put_bytes(tdb, key);
290         return true;
291
292 fail_put_data:
293         put_bytes(tdb, data);
294 fail_put_key:
295         put_bytes(tdb, key);
296         return false;
297 }
298
299 /* Check that an unused record is valid. */
300 static bool tdb1_check_free_record(struct tdb_context *tdb,
301                                   tdb1_off_t off,
302                                   const struct tdb1_record *rec,
303                                   unsigned char **hashes)
304 {
305         if (!tdb1_check_record(tdb, off, rec))
306                 return false;
307
308         /* Mark this offset as a known value for the free list. */
309         record_offset(hashes[0], off);
310         /* And similarly if the next pointer is valid. */
311         if (rec->next)
312                 record_offset(hashes[0], rec->next);
313         return true;
314 }
315
316 /* Slow, but should be very rare. */
317 size_t tdb1_dead_space(struct tdb_context *tdb, tdb1_off_t off)
318 {
319         size_t len;
320
321         for (len = 0; off + len < tdb->file->map_size; len++) {
322                 char c;
323                 if (tdb->tdb1.io->tdb1_read(tdb, off, &c, 1, 0))
324                         return 0;
325                 if (c != 0 && c != 0x42)
326                         break;
327         }
328         return len;
329 }
330
331 int tdb1_check(struct tdb_context *tdb,
332                enum TDB_ERROR (*check)(TDB_DATA key, TDB_DATA data, void *),
333                void *private_data)
334 {
335         unsigned int h;
336         unsigned char **hashes;
337         tdb1_off_t off, recovery_start;
338         struct tdb1_record rec;
339         bool found_recovery = false;
340         tdb1_len_t dead;
341         bool locked;
342
343         /* We may have a write lock already, so don't re-lock. */
344         if (tdb->file->allrecord_lock.count != 0) {
345                 locked = false;
346         } else {
347                 if (tdb_lockall_read(tdb) != TDB_SUCCESS)
348                         return -1;
349                 locked = true;
350         }
351
352         /* Make sure we know true size of the underlying file. */
353         tdb->tdb1.io->tdb1_oob(tdb, tdb->file->map_size + 1, 1);
354
355         /* Header must be OK: also gets us the recovery ptr, if any. */
356         if (!tdb1_check_header(tdb, &recovery_start))
357                 goto unlock;
358
359         /* We should have the whole header, too. */
360         if (tdb->file->map_size < TDB1_DATA_START(tdb->tdb1.header.hash_size)) {
361                 tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
362                                         "File too short for hashes\n");
363                 goto unlock;
364         }
365
366         /* One big malloc: pointers then bit arrays. */
367         hashes = (unsigned char **)calloc(
368                         1, sizeof(hashes[0]) * (1+tdb->tdb1.header.hash_size)
369                         + BITMAP_BITS / CHAR_BIT * (1+tdb->tdb1.header.hash_size));
370         if (!hashes) {
371                 tdb->last_error = TDB_ERR_OOM;
372                 goto unlock;
373         }
374
375         /* Initialize pointers */
376         hashes[0] = (unsigned char *)(&hashes[1+tdb->tdb1.header.hash_size]);
377         for (h = 1; h < 1+tdb->tdb1.header.hash_size; h++)
378                 hashes[h] = hashes[h-1] + BITMAP_BITS / CHAR_BIT;
379
380         /* Freelist and hash headers are all in a row: read them. */
381         for (h = 0; h < 1+tdb->tdb1.header.hash_size; h++) {
382                 if (tdb1_ofs_read(tdb, TDB1_FREELIST_TOP + h*sizeof(tdb1_off_t),
383                                  &off) == -1)
384                         goto free;
385                 if (off)
386                         record_offset(hashes[h], off);
387         }
388
389         /* For each record, read it in and check it's ok. */
390         for (off = TDB1_DATA_START(tdb->tdb1.header.hash_size);
391              off < tdb->file->map_size;
392              off += sizeof(rec) + rec.rec_len) {
393                 if (tdb->tdb1.io->tdb1_read(tdb, off, &rec, sizeof(rec),
394                                            TDB1_DOCONV()) == -1)
395                         goto free;
396                 switch (rec.magic) {
397                 case TDB1_MAGIC:
398                 case TDB1_DEAD_MAGIC:
399                         if (!tdb1_check_used_record(tdb, off, &rec, hashes,
400                                                    check, private_data))
401                                 goto free;
402                         break;
403                 case TDB1_FREE_MAGIC:
404                         if (!tdb1_check_free_record(tdb, off, &rec, hashes))
405                                 goto free;
406                         break;
407                 /* If we crash after ftruncate, we can get zeroes or fill. */
408                 case TDB1_RECOVERY_INVALID_MAGIC:
409                 case 0x42424242:
410                         if (recovery_start == off) {
411                                 found_recovery = true;
412                                 break;
413                         }
414                         dead = tdb1_dead_space(tdb, off);
415                         if (dead < sizeof(rec))
416                                 goto corrupt;
417
418                         tdb_logerr(tdb, TDB_SUCCESS, TDB_LOG_WARNING,
419                                    "Dead space at %d-%d (of %u)\n",
420                                    off, off + dead, tdb->file->map_size);
421                         rec.rec_len = dead - sizeof(rec);
422                         break;
423                 case TDB1_RECOVERY_MAGIC:
424                         if (recovery_start != off) {
425                                 tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
426                                                         "Unexpected recovery record at offset %d\n",
427                                                         off);
428                                 goto free;
429                         }
430                         found_recovery = true;
431                         break;
432                 default: ;
433                 corrupt:
434                         tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
435                                                 "Bad magic 0x%x at offset %d\n",
436                                                 rec.magic, off);
437                         goto free;
438                 }
439         }
440
441         /* Now, hashes should all be empty: each record exists and is referred
442          * to by one other. */
443         for (h = 0; h < 1+tdb->tdb1.header.hash_size; h++) {
444                 unsigned int i;
445                 for (i = 0; i < BITMAP_BITS / CHAR_BIT; i++) {
446                         if (hashes[h][i] != 0) {
447                                 tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
448                                                         "Hashes do not match records\n");
449                                 goto free;
450                         }
451                 }
452         }
453
454         /* We must have found recovery area if there was one. */
455         if (recovery_start != 0 && !found_recovery) {
456                 tdb->last_error = tdb_logerr(tdb, TDB_ERR_CORRUPT, TDB_LOG_ERROR,
457                                         "Expected a recovery area at %u\n",
458                                         recovery_start);
459                 goto free;
460         }
461
462         free(hashes);
463         if (locked) {
464                 tdb_unlockall_read(tdb);
465         }
466         return 0;
467
468 free:
469         free(hashes);
470 unlock:
471         if (locked) {
472                 tdb_unlockall_read(tdb);
473         }
474         return -1;
475 }