ldb: repack old format database if GUID indexing enabled
authorAaron Haslett <aaronhaslett@catalyst.net.nz>
Mon, 13 May 2019 04:37:25 +0000 (16:37 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 29 May 2019 04:41:24 +0000 (04:41 +0000)
VERY IMPORTANT PATCH
Now that we have a new packing format, we need to enable it by repacking
the database. We've decided to link all new database features together,
so once GUID indexing is enabled, the database will be repacked with
version 2 format. Repacking is done following the same iterate pattern as
reindexing.

Signed-off-by: Aaron Haslett <aaronhaslett@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
lib/ldb/ldb_key_value/ldb_kv.c
lib/ldb/ldb_key_value/ldb_kv.h
lib/ldb/ldb_key_value/ldb_kv_cache.c
lib/ldb/ldb_key_value/ldb_kv_index.c

index c8f7fd1396d7d46a0cd99cec2be1777d530c4d2d..174651ce1058746017dc402c53772eff788a4bda 100644 (file)
@@ -301,6 +301,28 @@ static int ldb_kv_check_special_dn(struct ldb_module *module,
        return LDB_SUCCESS;
 }
 
+/*
+ * Called after modifies and when starting a transaction. Checks target pack
+ * format version and current pack format version, which are set by cache_load,
+ * and repacks if necessary.
+ */
+static int ldb_kv_maybe_repack(struct ldb_kv_private *ldb_kv) {
+       if (ldb_kv->pack_format_version !=
+           ldb_kv->target_pack_format_version) {
+               int r;
+               struct ldb_context *ldb = ldb_module_get_ctx(ldb_kv->module);
+               ldb_kv->pack_format_version =
+                       ldb_kv->target_pack_format_version;
+               r = ldb_kv_repack(ldb_kv->module);
+               if (r != LDB_SUCCESS) {
+                       ldb_debug(ldb, LDB_DEBUG_ERROR,
+                                 "Database repack failed.");
+               }
+               return r;
+       }
+
+       return LDB_SUCCESS;
+}
 
 /*
   we've made a modification to a dn - possibly reindex and
@@ -1447,6 +1469,20 @@ static int ldb_kv_prepare_commit(struct ldb_module *module)
                return ret;
        }
 
+       /*
+        * If GUID indexing was toggled in this transaction, we repack at
+        * format version 2 if GUID indexing was enabled, or version 1 if
+        * it was disabled.
+        */
+       ret = ldb_kv_maybe_repack(ldb_kv);
+       if (ret != LDB_SUCCESS) {
+               ldb_kv_del_trans(module);
+               ldb_set_errstring(ldb_module_get_ctx(module),
+                                 "Failure during re-pack, so "
+                                 "transaction must be aborted.");
+               return ret;
+       }
+
        if (ldb_kv->kv_ops->prepare_write(ldb_kv) != 0) {
                ret = ldb_kv->kv_ops->error(ldb_kv);
                ldb_debug_set(ldb_module_get_ctx(module),
@@ -1895,8 +1931,6 @@ int ldb_kv_init_store(struct ldb_kv_private *ldb_kv,
 
        ldb_kv->sequence_number = 0;
 
-       ldb_kv->pack_format_version = LDB_PACKING_FORMAT;
-
        ldb_kv->pid = getpid();
 
        ldb_kv->module = ldb_module_new(ldb, ldb, name, &ldb_kv_ops);
index 1186e86ec9f3e140857ebedaa6b96ca85ae1ac9d..ce9a447186c21d747ad7c98a2c9a362e3c735811 100644 (file)
@@ -64,6 +64,7 @@ struct ldb_kv_private {
 
        unsigned long long sequence_number;
        uint32_t pack_format_version;
+       uint32_t target_pack_format_version;
 
        /* the low level tdb seqnum - used to avoid loading BASEINFO when
           possible */
@@ -141,6 +142,12 @@ struct ldb_kv_reindex_context {
        uint32_t count;
 };
 
+struct ldb_kv_repack_context {
+       int error;
+       uint32_t count;
+       bool normal_record_seen;
+};
+
 
 /* special record types */
 #define LDB_KV_INDEX      "@INDEX"
@@ -226,6 +233,7 @@ int ldb_kv_index_del_value(struct ldb_module *module,
                           struct ldb_message_element *el,
                           unsigned int v_idx);
 int ldb_kv_reindex(struct ldb_module *module);
+int ldb_kv_repack(struct ldb_module *module);
 int ldb_kv_index_transaction_start(
        struct ldb_module *module,
        size_t cache_size);
index c5f661113fd543fac0e22eb5b2f466226a82895b..3d8a09123e094fb1d72d0cd8d4425053c224edd4 100644 (file)
@@ -418,7 +418,6 @@ int ldb_kv_cache_load(struct ldb_module *module)
        const struct ldb_schema_attribute *a;
        bool have_write_txn = false;
        int r;
-       uint32_t pack_format_version;
        struct ldb_val key;
 
        ldb = ldb_module_get_ctx(module);
@@ -453,29 +452,7 @@ int ldb_kv_cache_load(struct ldb_module *module)
        /* Read packing format from first 4 bytes of @BASEINFO record */
        r = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key,
                                            get_pack_format_version,
-                                           &pack_format_version);
-
-       if (r != LDB_ERR_NO_SUCH_OBJECT) {
-               if (r != LDB_SUCCESS) {
-                       goto failed_and_unlock;
-               }
-
-               /* Make sure the database has the right format */
-               if (pack_format_version != ldb_kv->pack_format_version) {
-                       ldb_debug(ldb, LDB_DEBUG_ERROR,
-                                 "Unexpected packing format. "
-                                 "Expected: %#010x, Got: %#010x",
-                                 pack_format_version,
-                                 ldb_kv->pack_format_version);
-                       goto failed_and_unlock;
-               }
-       }
-
-       /* Now fetch the whole @BASEINFO record */
-       r = ldb_kv_search_dn1(module, baseinfo_dn, baseinfo, 0);
-       if (r != LDB_SUCCESS && r != LDB_ERR_NO_SUCH_OBJECT) {
-               goto failed_and_unlock;
-       }
+                                           &ldb_kv->pack_format_version);
 
        /* possibly initialise the baseinfo */
        if (r == LDB_ERR_NO_SUCH_OBJECT) {
@@ -492,15 +469,25 @@ int ldb_kv_cache_load(struct ldb_module *module)
 
                have_write_txn = true;
 
+               /*
+                * We need to write but haven't figured out packing format yet.
+                * Just go with version 1 and we'll repack if we got it wrong.
+                */
+               ldb_kv->pack_format_version = LDB_PACKING_FORMAT;
+               ldb_kv->target_pack_format_version = LDB_PACKING_FORMAT;
+
                /* error handling for ltdb_baseinfo_init() is by
                   looking for the record again. */
                ldb_kv_baseinfo_init(module);
 
-               if (ldb_kv_search_dn1(module, baseinfo_dn, baseinfo, 0) !=
-                   LDB_SUCCESS) {
-                       goto failed_and_unlock;
-               }
+       } else if (r != LDB_SUCCESS) {
+               goto failed_and_unlock;
+       }
 
+       /* OK now we definitely have a @BASEINFO record so fetch it */
+       r = ldb_kv_search_dn1(module, baseinfo_dn, baseinfo, 0);
+       if (r != LDB_SUCCESS) {
+               goto failed_and_unlock;
        }
 
        /* Ignore the result, and update the sequence number */
@@ -562,8 +549,15 @@ int ldb_kv_cache_load(struct ldb_module *module)
                goto failed_and_unlock;
        }
 
+       /*
+        * Initialise packing version and GUID index syntax, and force the
+        * two to travel together, ie a GUID indexed database must use V2
+        * packing format and a DN indexed database must use V1.
+        */
        ldb_kv->GUID_index_syntax = NULL;
        if (ldb_kv->cache->GUID_index_attribute != NULL) {
+               ldb_kv->target_pack_format_version = LDB_PACKING_FORMAT_V2;
+
                /*
                 * Now the attributes are loaded, set the guid_index_syntax.
                 * This can't fail, it will return a default at worst
@@ -571,6 +565,8 @@ int ldb_kv_cache_load(struct ldb_module *module)
                a = ldb_schema_attribute_by_name(
                    ldb, ldb_kv->cache->GUID_index_attribute);
                ldb_kv->GUID_index_syntax = a->syntax;
+       } else {
+               ldb_kv->target_pack_format_version = LDB_PACKING_FORMAT;
        }
 
 done:
index fff00918a126db01c8643428a24480142c08ed00..5af248a1931e74813d8cfb17f5e53a71ff70a039 100644 (file)
@@ -3412,6 +3412,96 @@ static int re_index(struct ldb_kv_private *ldb_kv,
        return 0;
 }
 
+static int re_pack(struct ldb_kv_private *ldb_kv,
+                  struct ldb_val key,
+                  struct ldb_val val,
+                  void *state)
+{
+       struct ldb_context *ldb;
+       struct ldb_message *msg;
+       struct ldb_module *module = ldb_kv->module;
+       struct ldb_kv_repack_context *ctx =
+           (struct ldb_kv_repack_context *)state;
+       int ret;
+
+       ldb = ldb_module_get_ctx(module);
+
+       msg = ldb_msg_new(module);
+       if (msg == NULL) {
+               return -1;
+       }
+
+       ret = ldb_unpack_data(ldb, &val, msg);
+       if (ret != 0) {
+               ldb_debug(ldb, LDB_DEBUG_ERROR, "Repack: unpack failed: %s\n",
+                         ldb_dn_get_linearized(msg->dn));
+               ctx->error = ret;
+               talloc_free(msg);
+               return -1;
+       }
+
+       ret = ldb_kv_store(module, msg, TDB_MODIFY);
+       if (ret != LDB_SUCCESS) {
+               ldb_debug(ldb, LDB_DEBUG_ERROR, "Repack: store failed: %s\n",
+                         ldb_dn_get_linearized(msg->dn));
+               ctx->error = ret;
+               talloc_free(msg);
+               return -1;
+       }
+
+       /*
+        * Warn the user that we're repacking the first time we see a normal
+        * record. This means we never warn if we're repacking a database with
+        * only @ records. This is because during database initialisation,
+        * we might repack as initial settings are written out, and we don't
+        * want to spam the log.
+        */
+       if ((!ctx->normal_record_seen) && (!ldb_dn_is_special(msg->dn))) {
+               ldb_debug(ldb, LDB_DEBUG_WARNING,
+                         "Repacking database with format %#010x",
+                         ldb_kv->pack_format_version);
+               ctx->normal_record_seen = true;
+       }
+
+       ctx->count++;
+       if (ctx->count % 10000 == 0) {
+               ldb_debug(ldb, LDB_DEBUG_WARNING,
+                         "Repack: re-packed %u records so far",
+                         ctx->count);
+       }
+
+       return 0;
+}
+
+int ldb_kv_repack(struct ldb_module *module)
+{
+       struct ldb_kv_private *ldb_kv = talloc_get_type(
+           ldb_module_get_private(module), struct ldb_kv_private);
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       struct ldb_kv_repack_context ctx;
+       int ret;
+
+       ctx.count = 0;
+       ctx.error = LDB_SUCCESS;
+       ctx.normal_record_seen = false;
+
+       /* Iterate all database records and repack them in the new format */
+       ret = ldb_kv->kv_ops->iterate(ldb_kv, re_pack, &ctx);
+       if (ret < 0) {
+               ldb_debug(ldb, LDB_DEBUG_ERROR, "Repack traverse failed: %s",
+                         ldb_errstring(ldb));
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       if (ctx.error != LDB_SUCCESS) {
+               ldb_debug(ldb, LDB_DEBUG_ERROR, "Repack failed: %s",
+                         ldb_errstring(ldb));
+               return ctx.error;
+       }
+
+       return LDB_SUCCESS;
+}
+
 /*
   force a complete reindex of the database
 */