summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>2017-01-06 16:38:03 +1300
committerDouglas Bagnall <dbagnall@samba.org>2017-02-09 03:17:16 +0100
commit762bde22ed27866c2e1e7de5181645532814a170 (patch)
tree4efa7805bba20e4c1f8e995f3150059470b97a9b
parentedcfededd2f92e7d0196a077890c69a07b2d887b (diff)
downloadsamba-762bde22ed27866c2e1e7de5181645532814a170.tar.gz
replmd linked_attributes: maintain sorted links in replace
We use a merge-like algorithm, which gives us a slight algorithmic improvement (O(m + n) vs O(m log(n) + n log(m))) and keeps the results sorted. Here's an example. There are existing links to {A C D* F*} where D* and F* represent deleted links, and we want to replace them with {B C E F}. existing: A C D* E F* | | | replacements: B C E F result: A* B C D* E F This is what happens to each link: A gets deleted to A*. B gets added. C is retained, with possible extended DN changes. D* stays in the list as a deleted link E is retained like C F is undeleted. Backlinks are created in the case of B and F The backlink for A is deleted The backlinks are not changed for C and E or D* (D* has none) Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> Pair-programmed-with: Andrew Bartlett <abartlet@samba.org> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
-rw-r--r--source4/dsdb/samdb/ldb_modules/repl_meta_data.c247
1 files changed, 145 insertions, 102 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
index 5951e83e2b8..2de00e1f197 100644
--- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
+++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
@@ -2827,32 +2827,61 @@ static int replmd_modify_la_replace(struct ldb_module *module,
struct GUID *msg_guid,
struct ldb_request *parent)
{
- unsigned int i;
+ unsigned int i, old_i, new_i;
struct parsed_dn *dns, *old_dns;
TALLOC_CTX *tmp_ctx = talloc_new(msg);
int ret;
const struct GUID *invocation_id;
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_val *new_values = NULL;
- unsigned int num_new_values = 0;
- unsigned int old_num_values = old_el?old_el->num_values:0;
+ const char *ldap_oid = schema_attr->syntax->ldap_oid;
+ unsigned int old_num_values;
+ unsigned int repl_num_values;
+ unsigned int max_num_values;
NTTIME now;
unix_to_nt_time(&now, t);
- /* check if there is nothing to replace */
- if ((!old_el || old_el->num_values == 0) &&
- el->num_values == 0) {
+ /*
+ * The replace operation is unlike the replace and delete cases in that
+ * we need to look at every existing link to see whether it is being
+ * retained or deleted. In other words, we can't avoid parsing the GUIDs.
+ *
+ * As we are trying to combine two sorted lists, the algorithm we use
+ * is akin to the merge phase of a merge sort. We interleave the two
+ * lists, doing different things depending on which side the current
+ * item came from.
+ *
+ * There are three main cases, with some sub-cases.
+ *
+ * - a DN is in the old list but not the new one. It needs to be
+ * marked as deleted (but left in the list).
+ * - maybe it is already deleted, and we have less to do.
+ *
+ * - a DN is in both lists. The old data gets replaced by the new,
+ * and the list doesn't grow. The old link may have been marked as
+ * deleted, in which case we undelete it.
+ *
+ * - a DN is in the new list only. We add it in the right place.
+ */
+
+ old_num_values = old_el ? old_el->num_values : 0;
+ repl_num_values = el->num_values;
+ max_num_values = old_num_values + repl_num_values;
+
+ if (max_num_values == 0) {
+ /* There is nothing to do! */
return LDB_SUCCESS;
}
- ret = get_parsed_dns(module, tmp_ctx, el, &dns, schema_attr->syntax->ldap_oid, parent);
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns, ldap_oid, parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- ret = get_parsed_dns(module, tmp_ctx, old_el, &old_dns, schema_attr->syntax->ldap_oid, parent);
+ ret = get_parsed_dns(module, tmp_ctx, old_el, &old_dns,
+ ldap_oid, parent);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
@@ -2864,128 +2893,142 @@ static int replmd_modify_la_replace(struct ldb_module *module,
}
ret = replmd_check_upgrade_links(ldb, old_dns, old_num_values,
- old_el, invocation_id,
- schema_attr->syntax->ldap_oid);
+ old_el, invocation_id, ldap_oid);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- /* mark all the old ones as deleted */
- for (i=0; i<old_num_values; i++) {
- struct parsed_dn *old_p = &old_dns[i];
- struct parsed_dn *exact = NULL, *next = NULL;
- uint32_t rmd_flags = dsdb_dn_rmd_flags(old_p->dsdb_dn->dn);
-
- if (rmd_flags & DSDB_RMD_FLAG_DELETED) continue;
+ new_values = talloc_array(tmp_ctx, struct ldb_val, max_num_values);
+ if (new_values == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
- ret = replmd_add_backlink(module, replmd_private,
- schema, msg_guid, &old_dns[i].guid,
- false, schema_attr, false);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
+ old_i = 0;
+ new_i = 0;
+ for (i = 0; i < max_num_values; i++) {
+ int cmp;
+ struct parsed_dn *old_p, *new_p;
+ if (old_i < old_num_values && new_i < repl_num_values) {
+ old_p = &old_dns[old_i];
+ new_p = &dns[new_i];
+ cmp = parsed_dn_compare(old_p, new_p);
+ } else if (old_i < old_num_values) {
+ /* the new list is empty, read the old list */
+ old_p = &old_dns[old_i];
+ new_p = NULL;
+ cmp = -1;
+ } else if (new_i < repl_num_values) {
+ /* the old list is empty, read new list */
+ old_p = NULL;
+ new_p = &dns[new_i];
+ cmp = 1;
+ } else {
+ break;
}
- ret = parsed_dn_find(ldb, dns, el->num_values,
- &old_p->guid,
- NULL,
- &exact, &next,
- schema_attr->syntax->ldap_oid);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
- if (exact) {
- /* we don't delete it if we are re-adding it */
- continue;
- }
+ if (cmp < 0) {
+ /*
+ * An old ones that come before the next replacement
+ * (if any). We mark it as deleted and add it to the
+ * final list.
+ */
+ uint32_t rmd_flags = dsdb_dn_rmd_flags(old_p->dsdb_dn->dn);
+ if ((rmd_flags & DSDB_RMD_FLAG_DELETED) == 0) {
+ ret = replmd_update_la_val(new_values, old_p->v,
+ old_p->dsdb_dn,
+ old_p->dsdb_dn,
+ invocation_id,
+ seq_num, seq_num,
+ now, 0, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
- ret = replmd_update_la_val(old_el->values, old_p->v, old_p->dsdb_dn, old_p->dsdb_dn,
- invocation_id, seq_num, seq_num, now, 0, true);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
- }
- }
+ ret = replmd_add_backlink(module, replmd_private,
+ schema, msg_guid,
+ &old_p->guid, false,
+ schema_attr, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ new_values[i] = *old_p->v;
+ old_i++;
+ } else if (cmp == 0) {
+ /*
+ * We are overwriting one. If it was previously
+ * deleted, we need to add a backlink.
+ *
+ * Note that if any RMD_FLAGs in an extended new DN
+ * will be ignored.
+ */
+ uint32_t rmd_flags;
- /* for each new value, either update its meta-data, or add it
- * to old_el
- */
- for (i=0; i<el->num_values; i++) {
- struct parsed_dn *p = &dns[i];
- struct parsed_dn *old_p = NULL, *next = NULL;
-
- if (old_dns) {
- ret = parsed_dn_find(ldb, old_dns, old_num_values,
- &p->guid,
- NULL,
- &old_p, &next,
- schema_attr->syntax->ldap_oid);
+ ret = replmd_update_la_val(new_values, old_p->v,
+ new_p->dsdb_dn,
+ old_p->dsdb_dn,
+ invocation_id,
+ seq_num, seq_num,
+ now, 0, false);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- }
- if (old_p != NULL) {
- /* update in place */
- ret = replmd_update_la_val(old_el->values, old_p->v, p->dsdb_dn,
- old_p->dsdb_dn, invocation_id,
- seq_num, seq_num, now, 0, false);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
+ rmd_flags = dsdb_dn_rmd_flags(old_p->dsdb_dn->dn);
+ if ((rmd_flags & DSDB_RMD_FLAG_DELETED) != 0) {
+ ret = replmd_add_backlink(module, replmd_private,
+ schema, msg_guid,
+ &new_p->guid, true,
+ schema_attr, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
}
+
+ new_values[i] = *old_p->v;
+ old_i++;
+ new_i++;
} else {
- /* add a new one */
- new_values = talloc_realloc(tmp_ctx, new_values, struct ldb_val,
- num_new_values+1);
- if (new_values == NULL) {
- ldb_module_oom(module);
+ /*
+ * Replacements that don't match an existing one. We
+ * just add them to the final list.
+ */
+ ret = replmd_build_la_val(new_values,
+ new_p->v,
+ new_p->dsdb_dn,
+ invocation_id,
+ seq_num, seq_num,
+ now, 0, false);
+ if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
+ return ret;
}
- ret = replmd_build_la_val(new_values, &new_values[num_new_values], dns[i].dsdb_dn,
- invocation_id, seq_num, seq_num, now, 0, false);
+ ret = replmd_add_backlink(module, replmd_private,
+ schema, msg_guid,
+ &new_p->guid, true,
+ schema_attr, true);
if (ret != LDB_SUCCESS) {
talloc_free(tmp_ctx);
return ret;
}
- num_new_values++;
- }
-
- ret = replmd_add_backlink(module, replmd_private,
- schema, msg_guid, &dns[i].guid,
- true, schema_attr, false);
- if (ret != LDB_SUCCESS) {
- talloc_free(tmp_ctx);
- return ret;
+ new_values[i] = *new_p->v;
+ new_i++;
}
}
-
- /* add the new values to the end of old_el */
- if (num_new_values != 0) {
- el->values = talloc_realloc(msg->elements, old_el?old_el->values:NULL,
- struct ldb_val, old_num_values+num_new_values);
- if (el->values == NULL) {
- ldb_module_oom(module);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- memcpy(&el->values[old_num_values], &new_values[0],
- sizeof(struct ldb_val)*num_new_values);
- el->num_values = old_num_values + num_new_values;
- talloc_steal(msg->elements, new_values);
- } else {
- el->values = old_el->values;
- el->num_values = old_el->num_values;
- talloc_steal(msg->elements, el->values);
+ if (old_el != NULL) {
+ talloc_steal(msg->elements, old_el->values);
}
-
+ el->values = talloc_steal(msg->elements, new_values);
+ el->num_values = i;
talloc_free(tmp_ctx);
- /* we now tell the backend to replace all existing values
- with the one we have constructed */
el->flags = LDB_FLAG_MOD_REPLACE;
return LDB_SUCCESS;