s4-schema consolidate schema handling
[amitay/samba.git] / source4 / dsdb / samdb / ldb_modules / schema_load.c
index 3442e821ce238cc79522e92f8aaf64aa6e19b880..ec574b33e3bc6489e45909c95e71892877267b44 100644 (file)
@@ -5,8 +5,8 @@
    checkings, it also loads the dsdb_schema.
    
    Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
-   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
-    
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009-2010
+
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
 #include "param/param.h"
 #include "dsdb/samdb/ldb_modules/util.h"
 
+struct schema_load_private_data {
+       bool in_transaction;
+};
+
+static int dsdb_schema_from_db(struct ldb_module *module, struct ldb_dn *schema_dn, uint64_t current_usn,
+                              struct dsdb_schema **schema);
+
+static struct dsdb_schema *dsdb_schema_refresh(struct ldb_module *module, struct dsdb_schema *schema, bool is_global_schema)
+{
+       uint64_t current_usn;
+       int ret;
+       struct ldb_result *res;
+       struct ldb_request *treq;
+       struct ldb_seqnum_request *tseq;
+       struct ldb_seqnum_result *tseqr;
+       struct dsdb_control_current_partition *ctrl;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
+       struct dsdb_schema *new_schema;
+       
+       struct schema_load_private_data *private_data = talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+       if (!private_data) {
+               /* We can't refresh until the init function has run */
+               return schema;
+       }
+
+       /* We don't allow a schema reload during a transaction - nobody else can modify our schema behind our backs */
+       if (private_data->in_transaction) {
+               return schema;
+       }
+
+       res = talloc_zero(schema, struct ldb_result);
+       if (res == NULL) {
+               return NULL;
+       }
+       tseq = talloc_zero(res, struct ldb_seqnum_request);
+       if (tseq == NULL) {
+               talloc_free(res);
+               return NULL;
+       }
+       tseq->type = LDB_SEQ_HIGHEST_SEQ;
+       
+       ret = ldb_build_extended_req(&treq, ldb_module_get_ctx(module), res,
+                                    LDB_EXTENDED_SEQUENCE_NUMBER,
+                                    tseq,
+                                    NULL,
+                                    res,
+                                    ldb_extended_default_callback,
+                                    NULL);
+       LDB_REQ_SET_LOCATION(treq);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(res);
+               return NULL;
+       }
+       
+       ctrl = talloc(treq, struct dsdb_control_current_partition);
+       if (!ctrl) {
+               talloc_free(res);
+               return NULL;
+       }
+       ctrl->version = DSDB_CONTROL_CURRENT_PARTITION_VERSION;
+       ctrl->dn = schema->base_dn;
+       
+       ret = ldb_request_add_control(treq,
+                                     DSDB_CONTROL_CURRENT_PARTITION_OID,
+                                     false, ctrl);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(res);
+               return NULL;
+       }
+       
+       ret = ldb_next_request(module, treq);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(res);
+               return NULL;
+       }
+       ret = ldb_wait(treq->handle, LDB_WAIT_ALL);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(res);
+               return NULL;
+       }
+       tseqr = talloc_get_type(res->extended->data,
+                               struct ldb_seqnum_result);
+       if (tseqr->seq_num == schema->reload_seq_number) {
+               talloc_free(res);
+               return schema;
+       }
+
+       schema->reload_seq_number = tseqr->seq_num;
+       talloc_free(res);
+               
+       ret = dsdb_module_load_partition_usn(module, schema->base_dn, &current_usn, NULL, NULL);
+       if (ret != LDB_SUCCESS || current_usn == schema->loaded_usn) {
+               return schema;
+       }
+
+       ret = dsdb_schema_from_db(module, schema->base_dn, current_usn, &new_schema);
+       if (ret != LDB_SUCCESS) {
+               return schema;
+       }
+       
+       if (is_global_schema) {
+               dsdb_make_schema_global(ldb, new_schema);
+       }
+       return new_schema;
+}
+
+
 /*
   Given an LDB module (pointing at the schema DB), and the DN, set the populated schema
 */
 
-static int dsdb_schema_from_schema_dn(TALLOC_CTX *mem_ctx, struct ldb_module *module,
-                                     struct smb_iconv_convenience *iconv_convenience, 
-                                     struct ldb_dn *schema_dn,
-                                     struct dsdb_schema **schema) 
+static int dsdb_schema_from_db(struct ldb_module *module, struct ldb_dn *schema_dn, uint64_t current_usn,
+                              struct dsdb_schema **schema)
 {
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
        TALLOC_CTX *tmp_ctx;
        char *error_string;
        int ret;
-       struct ldb_context *ldb = ldb_module_get_ctx(module);
        struct ldb_result *schema_res;
-       struct ldb_result *a_res;
-       struct ldb_result *c_res;
+       struct ldb_result *res;
        static const char *schema_attrs[] = {
                "prefixMap",
                "schemaInfo",
@@ -55,10 +159,9 @@ static int dsdb_schema_from_schema_dn(TALLOC_CTX *mem_ctx, struct ldb_module *mo
        };
        unsigned flags;
 
-       tmp_ctx = talloc_new(mem_ctx);
+       tmp_ctx = talloc_new(module);
        if (!tmp_ctx) {
-               ldb_oom(ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
+               return ldb_oom(ldb);
        }
 
        /* we don't want to trace the schema load */
@@ -69,8 +172,12 @@ static int dsdb_schema_from_schema_dn(TALLOC_CTX *mem_ctx, struct ldb_module *mo
         * setup the prefix mappings and schema info
         */
        ret = dsdb_module_search_dn(module, tmp_ctx, &schema_res,
-                                   schema_dn, schema_attrs, 0);
+                                   schema_dn, schema_attrs,
+                                   DSDB_FLAG_NEXT_MODULE, NULL);
        if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+               ldb_reset_err_string(ldb);
+               ldb_debug(ldb, LDB_DEBUG_WARNING,
+                         "schema_load_init: no schema head present: (skip schema loading)\n");
                goto failed;
        } else if (ret != LDB_SUCCESS) {
                ldb_asprintf_errstring(ldb, 
@@ -82,41 +189,56 @@ static int dsdb_schema_from_schema_dn(TALLOC_CTX *mem_ctx, struct ldb_module *mo
        /*
         * load the attribute definitions
         */
-       ret = dsdb_module_search(module, tmp_ctx, &a_res,
+       ret = dsdb_module_search(module, tmp_ctx, &res,
                                 schema_dn, LDB_SCOPE_ONELEVEL, NULL,
-                                0, 
-                                "(objectClass=attributeSchema)");
+                                DSDB_FLAG_NEXT_MODULE |
+                                DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+                                NULL,
+                                "(|(objectClass=attributeSchema)(objectClass=classSchema))");
        if (ret != LDB_SUCCESS) {
                ldb_asprintf_errstring(ldb, 
-                                      "dsdb_schema: failed to search attributeSchema objects: %s",
+                                      "dsdb_schema: failed to search attributeSchema and classSchema objects: %s",
                                       ldb_errstring(ldb));
                goto failed;
        }
 
-       /*
-        * load the objectClass definitions
-        */
-       ret = dsdb_module_search(module, tmp_ctx, &c_res,
-                                schema_dn, LDB_SCOPE_ONELEVEL, NULL,
-                                DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
-                                "(objectClass=classSchema)");
+       ret = dsdb_schema_from_ldb_results(tmp_ctx, ldb,
+                                          schema_res, res, schema, &error_string);
        if (ret != LDB_SUCCESS) {
                ldb_asprintf_errstring(ldb, 
-                                      "dsdb_schema: failed to search classSchema objects: %s",
-                                      ldb_errstring(ldb));
+                                      "dsdb_schema load failed: %s",
+                                      error_string);
                goto failed;
        }
 
-       ret = dsdb_schema_from_ldb_results(tmp_ctx, ldb,
-                                          lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
-                                          schema_res, a_res, c_res, schema, &error_string);
+       (*schema)->refresh_in_progress = true;
+
+       /* If we have the readOnlySchema opaque, then don't check for
+        * runtime schema updates, as they are not permitted (we would
+        * have to update the backend server schema too */
+       if (!ldb_get_opaque(ldb, "readOnlySchema")) {
+               (*schema)->refresh_fn = dsdb_schema_refresh;
+               (*schema)->loaded_from_module = module;
+               (*schema)->loaded_usn = current_usn;
+       }
+
+       /* "dsdb_set_schema()" steals schema into the ldb_context */
+       ret = dsdb_set_schema(ldb, (*schema));
+
+       (*schema)->refresh_in_progress = false;
+
        if (ret != LDB_SUCCESS) {
-               ldb_asprintf_errstring(ldb, 
-                                                   "dsdb_schema load failed: %s",
-                                                   error_string);
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             "schema_load_init: dsdb_set_schema() failed: %d:%s: %s",
+                             ret, ldb_strerror(ret), ldb_errstring(ldb));
                goto failed;
        }
-       talloc_steal(mem_ctx, *schema);
+
+       /* Ensure this module won't go away before the callback */
+       if (talloc_reference(*schema, ldb) == NULL) {
+               ldb_oom(ldb);
+               ret = LDB_ERR_OPERATIONS_ERROR;
+       }
 
 failed:
        if (flags & LDB_FLG_ENABLE_TRACING) {
@@ -130,19 +252,30 @@ failed:
 
 static int schema_load_init(struct ldb_module *module)
 {
-       struct ldb_context *ldb;
-       TALLOC_CTX *mem_ctx;
-       struct ldb_dn *schema_dn;
+       struct schema_load_private_data *private_data;
        struct dsdb_schema *schema;
+       struct ldb_context *ldb = ldb_module_get_ctx(module);
        int ret;
+       uint64_t current_usn;
+       struct ldb_dn *schema_dn;
+
+       private_data = talloc_zero(module, struct schema_load_private_data);
+       if (private_data == NULL) {
+               return ldb_oom(ldb);
+       }
+
+       ldb_module_set_private(module, private_data);
 
        ret = ldb_next_init(module);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
 
-       ldb = ldb_module_get_ctx(module);
-       schema_dn = samdb_schema_dn(ldb);
+       if (dsdb_get_schema(ldb, NULL)) {
+               return LDB_SUCCESS;
+       }
+
+       schema_dn = ldb_get_schema_basedn(ldb);
        if (!schema_dn) {
                ldb_reset_err_string(ldb);
                ldb_debug(ldb, LDB_DEBUG_WARNING,
@@ -150,54 +283,48 @@ static int schema_load_init(struct ldb_module *module)
                return LDB_SUCCESS;
        }
 
-       if (dsdb_get_schema(ldb)) {
-               return LDB_SUCCESS;
+       ret = dsdb_module_load_partition_usn(module, schema_dn, &current_usn, NULL, NULL);
+       if (ret != LDB_SUCCESS) {
+               /* Ignore the error and just reload the DB more often */
+               current_usn = 0;
        }
 
-       mem_ctx = talloc_new(module);
-       if (!mem_ctx) {
-               ldb_oom(ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
+       return dsdb_schema_from_db(module, schema_dn, current_usn, &schema);
+}
 
-       ret = dsdb_schema_from_schema_dn(mem_ctx, module,
-                                        lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
-                                        schema_dn, &schema);
+static int schema_load_start_transaction(struct ldb_module *module)
+{
+       struct schema_load_private_data *private_data =
+               talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
 
-       if (ret == LDB_ERR_NO_SUCH_OBJECT) {
-               ldb_reset_err_string(ldb);
-               ldb_debug(ldb, LDB_DEBUG_WARNING,
-                         "schema_load_init: no schema head present: (skip schema loading)\n");
-               talloc_free(mem_ctx);
-               return LDB_SUCCESS;
-       }
+       private_data->in_transaction = true;
 
-       if (ret != LDB_SUCCESS) {
-               talloc_free(mem_ctx);
-               return ret;
-       }
+       return ldb_next_start_trans(module);
+}
 
-       /* dsdb_set_schema() steal schema into the ldb_context */
-       ret = dsdb_set_schema(ldb, schema);
-       if (ret != LDB_SUCCESS) {
-               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
-                             "schema_load_init: dsdb_set_schema() failed: %d:%s: %s",
-                             ret, ldb_strerror(ret), ldb_errstring(ldb));
-               talloc_free(mem_ctx);
-               return ret;
-       }
+static int schema_load_end_transaction(struct ldb_module *module)
+{
+       struct schema_load_private_data *private_data =
+               talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+
+       private_data->in_transaction = false;
+
+       return ldb_next_end_trans(module);
+}
 
-       talloc_free(mem_ctx);
-       return LDB_SUCCESS;
+static int schema_load_del_transaction(struct ldb_module *module)
+{
+       struct schema_load_private_data *private_data =
+               talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+
+       private_data->in_transaction = false;
+
+       return ldb_next_del_trans(module);
 }
 
 static int schema_load_extended(struct ldb_module *module, struct ldb_request *req)
 {
        struct ldb_context *ldb;
-       struct ldb_dn *schema_dn;
-       struct dsdb_schema *schema;
-       int ret;
-       TALLOC_CTX *mem_ctx;
 
        ldb = ldb_module_get_ctx(module);
 
@@ -205,66 +332,22 @@ static int schema_load_extended(struct ldb_module *module, struct ldb_request *r
                return ldb_next_request(module, req);
        }
 
-       /*
-        * TODO:
-        *
-        * We should check "schemaInfo" if we really need to reload the schema!
-        *
-        * We should also for a new schema version at the start of each
-        * "write" (add/modify/rename/delete) operation. And have tests
-        * to prove that windows does the same.
-        */
-
-       schema_dn = samdb_schema_dn(ldb);
-       if (!schema_dn) {
-               ldb_reset_err_string(ldb);
-               ldb_debug(ldb, LDB_DEBUG_WARNING,
-                         "schema_load_extended: no schema dn present: (skip schema loading)\n");
-               return ldb_next_request(module, req);
-       }
-       
-       mem_ctx = talloc_new(module);
-       if (!mem_ctx) {
-               ldb_oom(ldb);
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-       
-       ret = dsdb_schema_from_schema_dn(mem_ctx, module,
-                                        lp_iconv_convenience(ldb_get_opaque(ldb, "loadparm")),
-                                        schema_dn, &schema);
-
-       if (ret == LDB_ERR_NO_SUCH_OBJECT) {
-               ldb_reset_err_string(ldb);
-               ldb_debug(ldb, LDB_DEBUG_WARNING,
-                         "schema_load_extended: no schema head present: (skip schema loading)\n");
-               talloc_free(mem_ctx);
-               return ldb_next_request(module, req);
-       }
-
-       if (ret != LDB_SUCCESS) {
-               talloc_free(mem_ctx);
-               return ldb_next_request(module, req);
-       }
-
-       /* Replace the old schema*/
-       ret = dsdb_set_schema(ldb, schema);
-       if (ret != LDB_SUCCESS) {
-               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
-                             "schema_load_extended: dsdb_set_schema() failed: %d:%s: %s",
-                             ret, ldb_strerror(ret), ldb_errstring(ldb));
-               talloc_free(mem_ctx);
-               return ret;
-       }
-
-       dsdb_make_schema_global(ldb);
-
-       talloc_free(mem_ctx);
+       /* This is a no-op.  We reload as soon as we can */
        return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
 }
 
 
-_PUBLIC_ const struct ldb_module_ops ldb_schema_load_module_ops = {
+static const struct ldb_module_ops ldb_schema_load_module_ops = {
        .name           = "schema_load",
        .init_context   = schema_load_init,
        .extended       = schema_load_extended,
+       .start_transaction = schema_load_start_transaction,
+       .end_transaction   = schema_load_end_transaction,
+       .del_transaction   = schema_load_del_transaction,
 };
+
+int ldb_schema_load_module_init(const char *version)
+{
+       LDB_MODULE_CHECK_VERSION(version);
+       return ldb_register_module(&ldb_schema_load_module_ops);
+}