LCOV - code coverage report
Current view: top level - lib/tdb/common - rescue.c (source / functions) Hit Total Coverage
Test: coverage report for master 2f515e9b Lines: 131 169 77.5 %
Date: 2024-04-21 15:09:00 Functions: 10 11 90.9 %

          Line data    Source code
       1             :  /*
       2             :    Unix SMB/CIFS implementation.
       3             : 
       4             :    trivial database library, rescue attempt code.
       5             : 
       6             :    Copyright (C) Rusty Russell             2012
       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 "tdb_private.h"
      26             : #include <assert.h>
      27             : 
      28             : 
      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;
      35             : };
      36             : 
      37             : struct found_table {
      38             :         /* As an ordered array (by head offset). */
      39             :         struct found *arr;
      40             :         unsigned int num, max;
      41             : };
      42             : 
      43     3878724 : 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)
      47             : {
      48           0 :         unsigned int hval;
      49             : 
      50     3878724 :         if (rec->magic != TDB_MAGIC)
      51     3873172 :                 return false;
      52             : 
      53        5552 :         if (rec->key_len + rec->data_len > rec->rec_len)
      54           8 :                 return false;
      55             : 
      56        5544 :         if (rec->rec_len % TDB_ALIGNMENT)
      57           0 :                 return false;
      58             : 
      59             :         /* Next pointer must make some sense. */
      60        5544 :         if (rec->next > 0 && rec->next < TDB_DATA_START(tdb->hash_size))
      61           1 :                 return false;
      62             : 
      63        5543 :         if (tdb_oob(tdb, rec->next, sizeof(*rec), 1))
      64           3 :                 return false;
      65             : 
      66        5540 :         key->dsize = rec->key_len;
      67        5540 :         key->dptr = tdb_alloc_read(tdb, off + sizeof(*rec), key->dsize);
      68        5540 :         if (!key->dptr)
      69           0 :                 return false;
      70             : 
      71        5540 :         hval = tdb->hash_fn(key);
      72        5540 :         if (hval != rec->full_hash) {
      73           6 :                 free(key->dptr);
      74           6 :                 return false;
      75             :         }
      76             : 
      77             :         /* Caller frees up key->dptr */
      78        5534 :         return true;
      79             : }
      80             : 
      81        5534 : static bool add_to_table(struct found_table *found,
      82             :                          tdb_off_t off,
      83             :                          struct tdb_record *rec,
      84             :                          TDB_DATA key)
      85             : {
      86        5534 :         if (found->num + 1 > found->max) {
      87           0 :                 struct found *new;
      88        3912 :                 found->max = (found->max ? found->max * 2 : 128);
      89        3912 :                 new = realloc(found->arr, found->max * sizeof(found->arr[0]));
      90        3912 :                 if (!new)
      91           0 :                         return false;
      92        3912 :                 found->arr = new;
      93             :         }
      94             : 
      95        5534 :         found->arr[found->num].head = off;
      96        5534 :         found->arr[found->num].rec = *rec;
      97        5534 :         found->arr[found->num].key = key;
      98        5534 :         found->arr[found->num].in_hash = false;
      99        5534 :         found->arr[found->num].in_free = false;
     100             : 
     101        5534 :         found->num++;
     102        5534 :         return true;
     103             : }
     104             : 
     105        5534 : 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)
     109             : {
     110           0 :         TDB_DATA data;
     111             : 
     112        5534 :         data.dsize = f->rec.data_len;
     113       11068 :         data.dptr = tdb_alloc_read(tdb,
     114        5534 :                                    f->head + sizeof(f->rec) + f->rec.key_len,
     115        5534 :                                    data.dsize);
     116        5534 :         if (!data.dptr) {
     117           0 :                 if (tdb->ecode == TDB_ERR_OOM)
     118           0 :                         return false;
     119             :                 /* I/O errors are expected. */
     120           0 :                 return true;
     121             :         }
     122             : 
     123        5534 :         walk(f->key, data, private_data);
     124        5534 :         free(data.dptr);
     125        5534 :         return true;
     126             : }
     127             : 
     128             : /* First entry which has offset >= this one. */
     129       10167 : static unsigned int find_entry(struct found_table *found, tdb_off_t off)
     130             : {
     131       10167 :         unsigned int start = 0, end = found->num;
     132             : 
     133       30467 :         while (start < end) {
     134             :                 /* We can't overflow here. */
     135       26014 :                 unsigned int mid = (start + end) / 2;
     136             : 
     137       26014 :                 if (off < found->arr[mid].head) {
     138       12301 :                         end = mid;
     139       13713 :                 } else if (off > found->arr[mid].head) {
     140        7999 :                         start = mid + 1;
     141             :                 } else {
     142        5714 :                         return mid;
     143             :                 }
     144             :         }
     145             : 
     146        4453 :         assert(start == end);
     147        4453 :         return end;
     148             : }
     149             : 
     150        5548 : static void found_in_hashchain(struct found_table *found, tdb_off_t head)
     151             : {
     152           0 :         unsigned int match;
     153             : 
     154        5548 :         match = find_entry(found, head);
     155        5548 :         if (match < found->num && found->arr[match].head == head) {
     156        5524 :                 found->arr[match].in_hash = true;
     157             :         }
     158        5548 : }
     159             : 
     160        3929 : static void mark_free_area(struct found_table *found, tdb_off_t head,
     161             :                            tdb_len_t len)
     162             : {
     163           0 :         unsigned int match;
     164             : 
     165        3929 :         match = find_entry(found, head);
     166             :         /* Mark everything within this free entry. */
     167        3933 :         while (match < found->num) {
     168        3907 :                 if (found->arr[match].head >= head + len) {
     169        3903 :                         break;
     170             :                 }
     171           4 :                 found->arr[match].in_free = true;
     172           4 :                 match++;
     173             :         }
     174        3929 : }
     175             : 
     176       15294 : static int cmp_key(const void *a, const void *b)
     177             : {
     178       15294 :         const struct found *fa = a, *fb = b;
     179             : 
     180       15294 :         if (fa->key.dsize < fb->key.dsize) {
     181        2323 :                 return -1;
     182       12971 :         } else if (fa->key.dsize > fb->key.dsize) {
     183        1553 :                 return 1;
     184             :         }
     185       11418 :         return memcmp(fa->key.dptr, fb->key.dptr, fa->key.dsize);
     186             : }
     187             : 
     188        7160 : static bool key_eq(TDB_DATA a, TDB_DATA b)
     189             : {
     190        7160 :         return a.dsize == b.dsize
     191        7160 :                 && memcmp(a.dptr, b.dptr, a.dsize) == 0;
     192             : }
     193             : 
     194           0 : static void free_table(struct found_table *found)
     195             : {
     196           0 :         unsigned int i;
     197             : 
     198           0 :         for (i = 0; i < found->num; i++) {
     199           0 :                 free(found->arr[i].key.dptr);
     200             :         }
     201           0 :         free(found->arr);
     202           0 : }
     203             : 
     204       19662 : static void logging_suppressed(struct tdb_context *tdb,
     205             :                                enum tdb_debug_level level, const char *fmt, ...)
     206             : {
     207       19662 : }
     208             : 
     209        3930 : _PUBLIC_ int tdb_rescue(struct tdb_context *tdb,
     210             :                         void (*walk)(TDB_DATA, TDB_DATA, void *private_data),
     211             :                         void *private_data)
     212             : {
     213        3930 :         struct found_table found = { NULL, 0, 0 };
     214           0 :         tdb_off_t h, off, i;
     215        3930 :         tdb_log_func oldlog = tdb->log.log_fn;
     216           0 :         struct tdb_record rec;
     217           0 :         TDB_DATA key;
     218           0 :         bool locked;
     219             : 
     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        3930 :         if (tdb->read_only || tdb->allrecord_lock.count != 0) {
     223           0 :                 locked = false;
     224             :         } else {
     225        3930 :                 if (tdb_lockall_read(tdb) == -1)
     226           0 :                         return -1;
     227        3930 :                 locked = true;
     228             :         }
     229             : 
     230             :         /* Make sure we know true size of the underlying file. */
     231        3930 :         tdb_oob(tdb, tdb->map_size, 1, 1);
     232             : 
     233             :         /* Suppress logging, since we anticipate errors. */
     234        3930 :         tdb->log.log_fn = logging_suppressed;
     235             : 
     236             :         /* Now walk entire db looking for records. */
     237        3930 :         for (off = TDB_DATA_START(tdb->hash_size);
     238     3902304 :              off < tdb->map_size;
     239     3898374 :              off += TDB_ALIGNMENT) {
     240     3898374 :                 if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
     241     3898374 :                                            DOCONV()) == -1)
     242       19650 :                         continue;
     243             : 
     244     3878724 :                 if (looks_like_valid_record(tdb, off, &rec, &key)) {
     245        5534 :                         if (!add_to_table(&found, off, &rec, key)) {
     246           0 :                                 goto oom;
     247             :                         }
     248             :                 }
     249             :         }
     250             : 
     251             :         /* Walk hash chains to positive vet. */
     252       11920 :         for (h = 0; h < 1+tdb->hash_size; h++) {
     253        7990 :                 bool slow_chase = false;
     254        7990 :                 tdb_off_t slow_off = FREELIST_TOP + h*sizeof(tdb_off_t);
     255             : 
     256        7990 :                 if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_off_t),
     257             :                                  &off) == -1)
     258           0 :                         continue;
     259             : 
     260       17467 :                 while (off && off != slow_off) {
     261        9495 :                         if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
     262        9495 :                                                    DOCONV()) != 0) {
     263          12 :                                 break;
     264             :                         }
     265             : 
     266             :                         /* 0 is the free list, rest are hash chains. */
     267        9483 :                         if (h == 0) {
     268             :                                 /* Don't mark garbage as free. */
     269        3935 :                                 if (rec.magic != TDB_FREE_MAGIC) {
     270           6 :                                         break;
     271             :                                 }
     272        3929 :                                 mark_free_area(&found, off,
     273        3929 :                                                sizeof(rec) + rec.rec_len);
     274             :                         } else {
     275        5548 :                                 found_in_hashchain(&found, off);
     276             :                         }
     277             : 
     278        9477 :                         off = rec.next;
     279             : 
     280             :                         /* Loop detection using second pointer at half-speed */
     281        9477 :                         if (slow_chase) {
     282             :                                 /* First entry happens to be next ptr */
     283         780 :                                 tdb_ofs_read(tdb, slow_off, &slow_off);
     284             :                         }
     285        9477 :                         slow_chase = !slow_chase;
     286             :                 }
     287             :         }
     288             : 
     289             :         /* Recovery area: must be marked as free, since it often has old
     290             :          * records in there! */
     291        3930 :         if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &off) == 0 && off != 0) {
     292           0 :                 if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
     293           0 :                                            DOCONV()) == 0) {
     294           0 :                         mark_free_area(&found, off, sizeof(rec) + rec.rec_len);
     295             :                 }
     296             :         }
     297             : 
     298             :         /* Now sort by key! */
     299        3930 :         if (found.arr != NULL) {
     300        3908 :                 qsort(found.arr, found.num, sizeof(found.arr[0]), cmp_key);
     301             :         }
     302             : 
     303        9464 :         for (i = 0; (found.arr != NULL) && i < found.num; ) {
     304        5534 :                 unsigned int num, num_in_hash = 0;
     305             : 
     306             :                 /* How many are identical? */
     307       11068 :                 for (num = 0; num < found.num - i; num++) {
     308        7160 :                         if (!key_eq(found.arr[i].key, found.arr[i+num].key)) {
     309        1626 :                                 break;
     310             :                         }
     311        5534 :                         if (found.arr[i+num].in_hash) {
     312        5524 :                                 if (!walk_record(tdb, &found.arr[i+num],
     313             :                                                  walk, private_data))
     314           0 :                                         goto oom;
     315        5524 :                                 num_in_hash++;
     316             :                         }
     317             :                 }
     318        5534 :                 assert(num);
     319             : 
     320             :                 /* If none were in the hash, we print any not in free list. */
     321        5534 :                 if (num_in_hash == 0) {
     322             :                         unsigned int j;
     323             : 
     324          20 :                         for (j = i; j < i + num; j++) {
     325          10 :                                 if (!found.arr[j].in_free) {
     326          10 :                                         if (!walk_record(tdb, &found.arr[j],
     327             :                                                          walk, private_data))
     328           0 :                                                 goto oom;
     329             :                                 }
     330             :                         }
     331             :                 }
     332             : 
     333        5534 :                 i += num;
     334             :         }
     335             : 
     336        3930 :         tdb->log.log_fn = oldlog;
     337        3930 :         if (locked) {
     338        3930 :                 tdb_unlockall_read(tdb);
     339             :         }
     340        3930 :         return 0;
     341             : 
     342           0 : oom:
     343           0 :         tdb->log.log_fn = oldlog;
     344           0 :         tdb->ecode = TDB_ERR_OOM;
     345           0 :         TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_rescue: failed allocating\n"));
     346           0 :         free_table(&found);
     347           0 :         if (locked) {
     348           0 :                 tdb_unlockall_read(tdb);
     349             :         }
     350           0 :         return -1;
     351             : }

Generated by: LCOV version 1.14