From b31c685ec293ef65bc33a474fc5a1d83545d4749 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Sat, 3 Jun 2006 11:57:20 +0000 Subject: r16028: Re-add the objectclass module, in the new async scheme. Add a test to show that we need this, and to prove it works (for add at least). Andrew Bartlett (This used to be commit f72079029abb594677bf8c2b63e40c07e910004f) --- source4/lib/ldb/config.mk | 18 +- source4/lib/ldb/modules/objectclass.c | 440 ++++++++++++++++++++++++++-------- source4/setup/provision_init.ldif | 2 +- 3 files changed, 352 insertions(+), 108 deletions(-) (limited to 'source4') diff --git a/source4/lib/ldb/config.mk b/source4/lib/ldb/config.mk index 68dbdd10f0..85a6cb6c67 100644 --- a/source4/lib/ldb/config.mk +++ b/source4/lib/ldb/config.mk @@ -38,15 +38,15 @@ OBJ_FILES = \ # End MODULE ldb_operational ################################################ -# ################################################ -# # Start MODULE ldb_objectclass -# [MODULE::ldb_objectclass] -# INIT_FUNCTION = ldb_objectclass_init -# SUBSYSTEM = ldb -# OBJ_FILES = \ -# modules/objectclass.o -# # End MODULE ldb_objectclass -# ################################################ +################################################ +# Start MODULE ldb_objectclass +[MODULE::ldb_objectclass] +INIT_FUNCTION = ldb_objectclass_init +SUBSYSTEM = ldb +OBJ_FILES = \ + modules/objectclass.o +# End MODULE ldb_objectclass +################################################ ################################################ # Start MODULE ldb_rdn_name diff --git a/source4/lib/ldb/modules/objectclass.c b/source4/lib/ldb/modules/objectclass.c index a9c51341a8..4423f82aed 100644 --- a/source4/lib/ldb/modules/objectclass.c +++ b/source4/lib/ldb/modules/objectclass.c @@ -1,7 +1,8 @@ /* ldb database library - Copyright (C) Andrew Bartlett 2005 + Copyright (C) Simo Sorce 2006 + Copyright (C) Andrew Bartlett 2005-2006 ** NOTE! The following LGPL license applies to the ldb ** library. This does NOT imply that all of Samba is released @@ -35,98 +36,256 @@ #include "includes.h" #include "ldb/include/includes.h" -/* It turns out the MMC assumes that the last objectClass in the list - * is the most specific subclass. As such, we must sort the list, - * according to the schema. - * - * For performance, we do this on the add/modify, not on the search - * - * We perform the original add/modify, then search for that is now in - * the objectClass list. We can then then replace that with the new - * sorted list. The backend is expected to preserve ordering for - * subsequent searches. - * - * We are in a transaction, so this is all perfectly safe... - */ +struct oc_async_context { + + enum oc_step {OC_DO_REQ, OC_SEARCH_SELF, OC_DO_MOD} step; + + struct ldb_module *module; + struct ldb_request *orig_req; + + struct ldb_request *down_req; + + struct ldb_request *search_req; + struct ldb_async_result *search_res; + + struct ldb_request *mod_req; +}; -static int objectclass_handle(struct ldb_module *module, struct ldb_request *req, const struct ldb_message *msg) +static struct ldb_async_handle *oc_init_handle(struct ldb_request *req, struct ldb_module *module) { - TALLOC_CTX *mem_ctx; - int ret; - struct ldb_request *search_request; - struct ldb_request *modify_request; - struct ldb_message *modify_msg; - struct ldb_result *res; - const char *attrs[] = { "objectClass", NULL }; - struct class_list { - struct class_list *prev, *next; - const char *objectclass; - }; - struct class_list *sorted = NULL, *parent_class = NULL, - *subclass = NULL, *unsorted = NULL, *current, *poss_subclass; - int i; - int layer; + struct oc_async_context *ac; + struct ldb_async_handle *h; - struct ldb_message_element *objectclass_element; + h = talloc_zero(req, struct ldb_async_handle); + if (h == NULL) { + ldb_set_errstring(module->ldb, talloc_asprintf(module, "Out of Memory")); + return NULL; + } + + h->module = module; + + ac = talloc_zero(h, struct oc_async_context); + if (ac == NULL) { + ldb_set_errstring(module->ldb, talloc_asprintf(module, "Out of Memory")); + talloc_free(h); + return NULL; + } + + h->private_data = (void *)ac; + + h->state = LDB_ASYNC_INIT; + h->status = LDB_SUCCESS; + + ac->module = module; + ac->orig_req = req; + + return h; +} + +static int objectclass_add(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_async_handle *h; + struct oc_async_context *ac; + struct ldb_message_element *objectClassAttr; - ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_handle\n"); + ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_add\n"); - if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */ + if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */ return ldb_next_request(module, req); } + + objectClassAttr = ldb_msg_find_element(req->op.add.message, "objectClass"); + + /* If no part of this touches the objectClass, then we don't + * need to make any changes. */ + /* If the only operation is the deletion of the objectClass then go on */ + if (!objectClassAttr) { + ldb_set_errstring(module->ldb, talloc_asprintf(ac, "Object class violation: no objectClass present")); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + + h = oc_init_handle(req, module); + if (!h) { + return LDB_ERR_OPERATIONS_ERROR; + } + ac = talloc_get_type(h->private_data, struct oc_async_context); - ret = ldb_next_request(module, req); + /* return or own handle to deal with this call */ + req->async.handle = h; - if (ret) { - return ret; + /* prepare the first operation */ + ac->down_req = talloc_zero(ac, struct ldb_request); + if (ac->down_req == NULL) { + ldb_set_errstring(module->ldb, talloc_asprintf(module->ldb, "Out of memory!")); + return LDB_ERR_OPERATIONS_ERROR; } - if (ldb_msg_find_element(msg, "objectClass") == NULL ) { - /* No sign of the objectClass: no change, nothing to see here */ - return ret; + *(ac->down_req) = *req; /* copy the request */ + + ac->down_req->async.context = NULL; + ac->down_req->async.callback = NULL; + + ac->step = OC_DO_REQ; + + return ldb_next_request(module, ac->down_req); +} + +static int objectclass_modify(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_async_handle *h; + struct oc_async_context *ac; + struct ldb_message_element *objectClassAttr; + + ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_modify\n"); + + if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */ + return ldb_next_request(module, req); } + + objectClassAttr = ldb_msg_find_element(req->op.mod.message, "objectClass"); - /* Thanks to transactions: Now do a search, find the full list - * of objectClasses and do the sort */ + /* If no part of this touches the objectClass, then we don't + * need to make any changes. */ + /* If the only operation is the deletion of the objectClass then go on */ + if (!objectClassAttr) { + return ldb_next_request(module, req); + } - mem_ctx = talloc_new(module); - if (!mem_ctx) { + h = oc_init_handle(req, module); + if (!h) { return LDB_ERR_OPERATIONS_ERROR; } + ac = talloc_get_type(h->private_data, struct oc_async_context); - search_request = talloc(mem_ctx, struct ldb_request); - if (!search_request) { - talloc_free(mem_ctx); + /* return or own handle to deal with this call */ + req->async.handle = h; + + /* prepare the first operation */ + ac->down_req = talloc_zero(ac, struct ldb_request); + if (ac->down_req == NULL) { + ldb_set_errstring(module->ldb, talloc_asprintf(module->ldb, "Out of memory!")); return LDB_ERR_OPERATIONS_ERROR; } - search_request->operation = LDB_REQ_SEARCH; - search_request->op.search.base = msg->dn; - search_request->op.search.scope = LDB_SCOPE_BASE; - search_request->op.search.tree = ldb_parse_tree(module->ldb, NULL); - search_request->op.search.attrs = attrs; - search_request->controls = NULL; + *(ac->down_req) = *req; /* copy the request */ - ret = ldb_next_request(module, search_request); - if (ret) { - return ret; + ac->down_req->async.context = NULL; + ac->down_req->async.callback = NULL; + + ac->step = OC_DO_REQ; + + return ldb_next_request(module, ac->down_req); +} + +static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_async_result *ares) +{ + struct oc_async_context *ac; + + if (!context || !ares) { + ldb_set_errstring(ldb, talloc_asprintf(ldb, "NULL Context or Result in callback")); + return LDB_ERR_OPERATIONS_ERROR; + } + + ac = talloc_get_type(context, struct oc_async_context); + + /* we are interested only in the single reply (base search) we receive here */ + if (ares->type == LDB_REPLY_ENTRY) { + if (ac->search_res != NULL) { + ldb_set_errstring(ldb, talloc_asprintf(ldb, "Too many results")); + talloc_free(ares); + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->search_res = talloc_steal(ac, ares); + } else { + talloc_free(ares); + } + + return LDB_SUCCESS; +} + +static int objectclass_search_self(struct ldb_async_handle *h) { + + struct oc_async_context *ac; + static const char * const attrs[] = { "objectClass", NULL }; + + ac = talloc_get_type(h->private_data, struct oc_async_context); + + /* prepare the search operation */ + ac->search_req = talloc_zero(ac, struct ldb_request); + if (ac->search_req == NULL) { + ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->search_req->operation = LDB_SEARCH; + ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn; + ac->search_req->op.search.scope = LDB_SCOPE_BASE; + ac->search_req->op.search.tree = ldb_parse_tree(ac->module->ldb, NULL); + if (ac->search_req->op.search.tree == NULL) { + ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: Internal error producing null search")); + return LDB_ERR_OPERATIONS_ERROR; + } + ac->search_req->op.search.attrs = attrs; + ac->search_req->controls = NULL; + ac->search_req->async.context = ac; + ac->search_req->async.callback = get_self_callback; + ac->search_req->async.timeout = ac->orig_req->async.timeout; + + ac->step = OC_SEARCH_SELF; + + return ldb_next_request(ac->module, ac->search_req); +} + +static int objectclass_do_mod(struct ldb_async_handle *h) { + + struct oc_async_context *ac; + struct ldb_message_element *objectclass_element; + struct ldb_message *msg; + TALLOC_CTX *mem_ctx; + struct class_list { + struct class_list *prev, *next; + const char *objectclass; + }; + struct class_list *sorted = NULL, *parent_class = NULL, + *subclass = NULL, *unsorted = NULL, *current, *poss_subclass; + int i; + int layer; + int ret; + + ac = talloc_get_type(h->private_data, struct oc_async_context); + + mem_ctx = talloc_new(ac); + if (mem_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ac->mod_req = talloc(ac, struct ldb_request); + if (ac->mod_req == NULL) { + talloc_free(mem_ctx); + return LDB_ERR_OPERATIONS_ERROR; } - res = search_request->op.search.res; - talloc_steal(mem_ctx, res); - if (res->count != 1) { - ldb_set_errstring(module->ldb, - talloc_asprintf(mem_ctx, "objectClass_handle: " - "search for %s found %d != 1 objects, for entry we just added/modified", - ldb_dn_linearize(mem_ctx, msg->dn), - res->count)); - /* What happened? The above add/modify worked... */ + ac->mod_req->operation = LDB_MODIFY; + ac->mod_req->controls = NULL; + ac->mod_req->async.context = ac; + ac->mod_req->async.callback = NULL; + ac->mod_req->async.timeout = ac->orig_req->async.timeout; + + /* use a new message structure */ + ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req); + if (msg == NULL) { + ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: could not create new modify msg")); talloc_free(mem_ctx); - return LDB_ERR_NO_SUCH_OBJECT; + return LDB_ERR_OPERATIONS_ERROR; } + /* modify dn */ + msg->dn = ac->orig_req->op.mod.message->dn; + /* This is now the objectClass list from the database */ - objectclass_element = ldb_msg_find_element(res->msgs[0], "objectClass"); + objectclass_element = ldb_msg_find_element(ac->search_res->message, + "objectClass"); if (!objectclass_element) { /* Perhaps the above was a remove? Move along now, nothing to see here */ talloc_free(mem_ctx); @@ -166,6 +325,7 @@ static int objectclass_handle(struct ldb_module *module, struct ldb_request *req for (i=0; i < objectclass_element->num_values; i++) { current = talloc(mem_ctx, struct class_list); if (!current) { + ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: out of memory allocating objectclass list")); talloc_free(mem_ctx); return LDB_ERR_OPERATIONS_ERROR; } @@ -189,7 +349,7 @@ static int objectclass_handle(struct ldb_module *module, struct ldb_request *req /* Ensure we don't bother if there are no unsorted entries left */ for (current = parent_class; unsorted && current; current = current->next) { - const char **subclasses = ldb_subclass_list(module->ldb, current->objectclass); + const char **subclasses = ldb_subclass_list(ac->module->ldb, current->objectclass); /* Walk the list of possible subclasses in unsorted */ for (poss_subclass = unsorted; poss_subclass; ) { @@ -231,78 +391,162 @@ static int objectclass_handle(struct ldb_module *module, struct ldb_request *req */ DLIST_CONCATENATE(sorted, unsorted, struct class_list *); - modify_msg = ldb_msg_new(mem_ctx); - if (!modify_msg) { - talloc_free(mem_ctx); - return LDB_ERR_OPERATIONS_ERROR; - } - modify_msg->dn = talloc_reference(modify_msg, msg->dn); - /* We must completely replace the existing objectClass entry. * We could do a constrained add/del, but we are meant to be * in a transaction... */ - ret = ldb_msg_add_empty(modify_msg, "objectClass", LDB_FLAG_MOD_REPLACE); + ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE); if (ret != LDB_SUCCESS) { + ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: could not clear objectclass in modify msg")); talloc_free(mem_ctx); return ret; } /* Move from the linked list back into an ldb msg */ for (current = sorted; current; current = current->next) { - ret = ldb_msg_add_string(modify_msg, "objectClass", current->objectclass); + ret = ldb_msg_add_string(msg, "objectClass", current->objectclass); if (ret != LDB_SUCCESS) { + ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "objectclass: could not re-add sorted objectclass to modify msg")); talloc_free(mem_ctx); return ret; } } - ret = ldb_msg_sanity_check(modify_msg); + ret = ldb_msg_sanity_check(msg); if (ret != LDB_SUCCESS) { talloc_free(mem_ctx); return ret; } - modify_request = talloc(mem_ctx, struct ldb_request); - if (!modify_request) { - talloc_free(mem_ctx); + + h->state = LDB_ASYNC_INIT; + h->status = LDB_SUCCESS; + + ac->step = OC_DO_MOD; + + talloc_free(mem_ctx); + /* perform the search */ + return ldb_next_request(ac->module, ac->mod_req); +} + +static int oc_async_wait(struct ldb_async_handle *handle) { + struct oc_async_context *ac; + int ret; + + if (!handle || !handle->private_data) { return LDB_ERR_OPERATIONS_ERROR; } - modify_request->operation = LDB_REQ_MODIFY; - modify_request->op.mod.message = modify_msg; - modify_request->controls = NULL; + if (handle->state == LDB_ASYNC_DONE) { + return handle->status; + } - /* And now push the write into the database */ - ret = ldb_next_request(module, modify_request); - - talloc_free(mem_ctx); + handle->state = LDB_ASYNC_PENDING; + handle->status = LDB_SUCCESS; + + ac = talloc_get_type(handle->private_data, struct oc_async_context); + + switch (ac->step) { + case OC_DO_REQ: + ret = ldb_async_wait(ac->down_req->async.handle, LDB_WAIT_NONE); + + if (ret != LDB_SUCCESS) { + handle->status = ret; + goto done; + } + if (ac->down_req->async.handle->status != LDB_SUCCESS) { + handle->status = ac->down_req->async.handle->status; + goto done; + } + + if (ac->down_req->async.handle->state != LDB_ASYNC_DONE) { + return LDB_SUCCESS; + } + + /* mods done, go on */ + return objectclass_search_self(handle); + + case OC_SEARCH_SELF: + ret = ldb_async_wait(ac->search_req->async.handle, LDB_WAIT_NONE); + + if (ret != LDB_SUCCESS) { + handle->status = ret; + goto done; + } + if (ac->search_req->async.handle->status != LDB_SUCCESS) { + handle->status = ac->search_req->async.handle->status; + goto done; + } + + if (ac->search_req->async.handle->state != LDB_ASYNC_DONE) { + return LDB_SUCCESS; + } + + /* self search done, go on */ + return objectclass_do_mod(handle); + + case OC_DO_MOD: + ret = ldb_async_wait(ac->mod_req->async.handle, LDB_WAIT_NONE); + + if (ret != LDB_SUCCESS) { + handle->status = ret; + goto done; + } + if (ac->mod_req->async.handle->status != LDB_SUCCESS) { + handle->status = ac->mod_req->async.handle->status; + goto done; + } + + if (ac->mod_req->async.handle->state != LDB_ASYNC_DONE) { + return LDB_SUCCESS; + } + + break; + + default: + ret = LDB_ERR_OPERATIONS_ERROR; + goto done; + } + + ret = LDB_SUCCESS; + +done: + handle->state = LDB_ASYNC_DONE; return ret; } -static int objectclass_request(struct ldb_module *module, struct ldb_request *req) -{ - switch (req->operation) { +static int oc_async_wait_all(struct ldb_async_handle *handle) { - /* only care about add and modify requests */ - case LDB_REQ_ADD: - return objectclass_handle(module, req, req->op.add.message); + int ret; - case LDB_REQ_MODIFY: - return objectclass_handle(module, req, req->op.mod.message); + while (handle->state != LDB_ASYNC_DONE) { + ret = oc_async_wait(handle); + if (ret != LDB_SUCCESS) { + return ret; + } + } - default: - return ldb_next_request(module, req); + return handle->status; +} +static int objectclass_async_wait(struct ldb_async_handle *handle, enum ldb_async_wait_type type) +{ + if (type == LDB_WAIT_ALL) { + return oc_async_wait_all(handle); + } else { + return oc_async_wait(handle); } } static const struct ldb_module_ops objectclass_ops = { .name = "objectclass", - .request = objectclass_request, + .add = objectclass_add, + .modify = objectclass_modify, + .async_wait = objectclass_async_wait }; int ldb_objectclass_init(void) { return ldb_register_module(&objectclass_ops); } + diff --git a/source4/setup/provision_init.ldif b/source4/setup/provision_init.ldif index e91d4db630..ca044842e9 100644 --- a/source4/setup/provision_init.ldif +++ b/source4/setup/provision_init.ldif @@ -85,5 +85,5 @@ vendorVersion: ${VERSION} # - samldb must be before password_hash, because password_hash checks that the objectclass is of type person (filled in by samldb) dn: @MODULES -@LIST: rootdse,kludge_acl,paged_results,server_sort,extended_dn,asq,samldb,password_hash,operational,objectguid,rdn_name +@LIST: rootdse,kludge_acl,paged_results,server_sort,extended_dn,asq,samldb,objectclass,password_hash,operational,objectguid,rdn_name -- cgit