]> git.ozlabs.org Git - ccan/blobdiff - ccan/tdb2/check.c
tdb2: shrink free header from 32 to 24 bytes.
[ccan] / ccan / tdb2 / check.c
index 54f96bb0ea8283583faf91693decd8d467970423..97347920291a6376f3010ec6b7289f7190607827 100644 (file)
@@ -77,7 +77,9 @@ static bool check_hash_tree(struct tdb_context *tdb,
                            unsigned hprefix_bits,
                            tdb_off_t used[],
                            size_t num_used,
-                           size_t *num_found);
+                           size_t *num_found,
+                           int (*check)(TDB_DATA, TDB_DATA, void *),
+                           void *private_data);
 
 static bool check_hash_record(struct tdb_context *tdb,
                              tdb_off_t off,
@@ -85,7 +87,9 @@ static bool check_hash_record(struct tdb_context *tdb,
                              unsigned hprefix_bits,
                              tdb_off_t used[],
                              size_t num_used,
-                             size_t *num_found)
+                             size_t *num_found,
+                             int (*check)(TDB_DATA, TDB_DATA, void *),
+                             void *private_data)
 {
        struct tdb_used_record rec;
 
@@ -117,7 +121,7 @@ static bool check_hash_record(struct tdb_context *tdb,
        return check_hash_tree(tdb, off,
                               TDB_SUBLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS,
                               hprefix, hprefix_bits,
-                              used, num_used, num_found);
+                              used, num_used, num_found, check, private_data);
 }
 
 static int off_cmp(const tdb_off_t *a, const tdb_off_t *b)
@@ -141,7 +145,9 @@ static bool check_hash_tree(struct tdb_context *tdb,
                            unsigned hprefix_bits,
                            tdb_off_t used[],
                            size_t num_used,
-                           size_t *num_found)
+                           size_t *num_found,
+                           int (*check)(TDB_DATA, TDB_DATA, void *),
+                           void *private_data)
 {
        unsigned int g, b;
        const tdb_off_t *hash;
@@ -188,7 +194,8 @@ static bool check_hash_tree(struct tdb_context *tdb,
                                               hprefix_bits
                                                       + group_bits
                                                       + TDB_HASH_GROUP_BITS,
-                                              used, num_used, num_found))
+                                              used, num_used, num_found,
+                                              check, private_data))
                                        goto fail;
                                continue;
                        }
@@ -256,6 +263,22 @@ static bool check_hash_tree(struct tdb_context *tdb,
                                         (long long)rec_hash(&rec));
                                goto fail;
                        }
+
+                       if (check) {
+                               TDB_DATA key, data;
+                               key.dsize = rec_key_length(&rec);
+                               data.dsize = rec_data_length(&rec);
+                               key.dptr = (void *)tdb_access_read(tdb,
+                                                  off + sizeof(rec),
+                                                  key.dsize + data.dsize,
+                                                  false);
+                               if (!key.dptr)
+                                       goto fail;
+                               data.dptr = key.dptr + key.dsize;
+                               if (check(key, data, private_data) != 0)
+                                       goto fail;
+                               tdb_access_release(tdb, key.dptr);
+                       }
                }
        }
        tdb_access_release(tdb, hash);
@@ -268,14 +291,17 @@ fail:
 
 static bool check_hash(struct tdb_context *tdb,
                       tdb_off_t used[],
-                      size_t num_used, size_t num_flists)
+                      size_t num_used, size_t num_flists,
+                      int (*check)(TDB_DATA, TDB_DATA, void *),
+                      void *private_data)
 {
        /* Free lists also show up as used. */
        size_t num_found = num_flists;
 
        if (!check_hash_tree(tdb, offsetof(struct tdb_header, hashtable),
                             TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS,
-                            0, 0, used, num_used, &num_found))
+                            0, 0, used, num_used, &num_found,
+                            check, private_data))
                return false;
 
        if (num_found != num_used) {
@@ -289,37 +315,37 @@ static bool check_hash(struct tdb_context *tdb,
 static bool check_free(struct tdb_context *tdb,
                       tdb_off_t off,
                       const struct tdb_free_record *frec,
-                      tdb_off_t prev, tdb_off_t flist_off, unsigned int bucket)
+                      tdb_off_t prev, unsigned int flist, unsigned int bucket)
 {
        if (frec_magic(frec) != TDB_FREE_MAGIC) {
                tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
                         "tdb_check: offset %llu bad magic 0x%llx\n",
-                        (long long)off, (long long)frec->magic_and_meta);
+                        (long long)off, (long long)frec->magic_and_prev);
                return false;
        }
-       if (frec_flist(frec) != flist_off) {
+       if (frec_flist(frec) != flist) {
                tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
-                        "tdb_check: offset %llu bad freelist 0x%llx\n",
-                        (long long)off, (long long)frec_flist(frec));
+                        "tdb_check: offset %llu bad freelist %u\n",
+                        (long long)off, frec_flist(frec));
                return false;
        }
 
        if (tdb->methods->oob(tdb, off
-                             + frec->data_len+sizeof(struct tdb_used_record),
+                             + frec_len(frec) + sizeof(struct tdb_used_record),
                              false))
                return false;
-       if (size_to_bucket(frec->data_len) != bucket) {
+       if (size_to_bucket(frec_len(frec)) != bucket) {
                tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
                         "tdb_check: offset %llu in wrong bucket %u vs %u\n",
                         (long long)off,
-                        bucket, size_to_bucket(frec->data_len));
+                        bucket, size_to_bucket(frec_len(frec)));
                return false;
        }
-       if (prev != frec->prev) {
+       if (prev != frec_prev(frec)) {
                tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
                         "tdb_check: offset %llu bad prev %llu vs %llu\n",
                         (long long)off,
-                        (long long)prev, (long long)frec->prev);
+                        (long long)prev, (long long)frec_len(frec));
                return false;
        }
        return true;
@@ -327,6 +353,7 @@ static bool check_free(struct tdb_context *tdb,
                       
 static bool check_free_list(struct tdb_context *tdb,
                            tdb_off_t flist_off,
+                           unsigned flist_num,
                            tdb_off_t free[],
                            size_t num_free,
                            size_t *num_found)
@@ -358,7 +385,7 @@ static bool check_free_list(struct tdb_context *tdb,
                                return false;
                        if (tdb_read_convert(tdb, off, &f, sizeof(f)))
                                return false;
-                       if (!check_free(tdb, off, &f, prev, flist_off, i))
+                       if (!check_free(tdb, off, &f, prev, flist_num, i))
                                return false;
 
                        /* FIXME: Check hash bits */
@@ -410,13 +437,17 @@ static bool check_linear(struct tdb_context *tdb,
                        struct tdb_free_record f;
                        struct tdb_recovery_record r;
                } pad, *p;
-               p = tdb_get(tdb, off, &pad, sizeof(pad));
+               /* r is larger: only get that if we need to. */
+               p = tdb_get(tdb, off, &pad, sizeof(pad.f));
                if (!p)
                        return false;
 
                /* If we crash after ftruncate, we can get zeroes or fill. */
                if (p->r.magic == TDB_RECOVERY_INVALID_MAGIC
                    || p->r.magic ==  0x4343434343434343ULL) {
+                       p = tdb_get(tdb, off, &pad, sizeof(pad.r));
+                       if (!p)
+                               return false;
                        if (recovery == off) {
                                found_recovery = true;
                                len = sizeof(p->r) + p->r.max_len;
@@ -436,6 +467,9 @@ static bool check_linear(struct tdb_context *tdb,
                                         (size_t)tdb->map_size);
                        }
                } else if (p->r.magic == TDB_RECOVERY_MAGIC) {
+                       p = tdb_get(tdb, off, &pad, sizeof(pad.r));
+                       if (!p)
+                               return false;
                        if (recovery != off) {
                                tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
                                         "tdb_check: unexpected recovery"
@@ -443,11 +477,23 @@ static bool check_linear(struct tdb_context *tdb,
                                         (size_t)off);
                                return false;
                        }
+                       if (p->r.len > p->r.max_len) {
+                               tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
+                                        "tdb_check: invalid recovery length"
+                                        " %zu\n", (size_t)p->r.len);
+                               return false;
+                       }
+                       if (p->r.eof > tdb->map_size) {
+                               tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
+                                        "tdb_check: invalid old EOF"
+                                        " %zu\n", (size_t)p->r.eof);
+                               return false;
+                       }
                        found_recovery = true;
                        len = sizeof(p->r) + p->r.max_len;
                } else if (frec_magic(&p->f) == TDB_FREE_MAGIC
                           || frec_magic(&p->f) == TDB_COALESCING_MAGIC) {
-                       len = sizeof(p->u) + p->f.data_len;
+                       len = sizeof(p->u) + frec_len(&p->f);
                        if (off + len > tdb->map_size) {
                                tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
                                         "tdb_check: free overlength %llu"
@@ -509,7 +555,6 @@ static bool check_linear(struct tdb_context *tdb,
        return true;
 }
 
-/* FIXME: call check() function. */
 int tdb_check(struct tdb_context *tdb,
              int (*check)(TDB_DATA key, TDB_DATA data, void *private_data),
              void *private_data)
@@ -535,19 +580,20 @@ int tdb_check(struct tdb_context *tdb,
        for (flist = first_flist(tdb); flist; flist = next_flist(tdb, flist)) {
                if (flist == TDB_OFF_ERR)
                        goto fail;
-               if (!check_free_list(tdb, flist, free, num_free, &num_found))
+               if (!check_free_list(tdb, flist, num_flists, free, num_free,
+                                    &num_found))
                        goto fail;
                num_flists++;
        }
 
        /* FIXME: Check key uniqueness? */
-       if (!check_hash(tdb, used, num_used, num_flists))
+       if (!check_hash(tdb, used, num_used, num_flists, check, private_data))
                goto fail;
 
        if (num_found != num_free) {
                tdb->log(tdb, TDB_DEBUG_ERROR, tdb->log_priv,
                         "tdb_check: Not all entries are in free table\n");
-               return false;
+               return -1;
        }
 
        tdb_allrecord_unlock(tdb, F_RDLCK);