static int descriptor_sd_propagation_recursive(struct ldb_module *module,
					       struct descriptor_changes *change)
{
	struct descriptor_data *descriptor_private =
		talloc_get_type_abort(ldb_module_get_private(module),
		struct descriptor_data);
	struct descriptor_transaction *t = &descriptor_private->transaction;
	struct ldb_result *guid_res = NULL;
	struct ldb_result *res = NULL;
	unsigned int i;
	const char * const no_attrs[] = { "@__NONE__", NULL };
	struct ldb_dn *stopped_dn = NULL;
	struct GUID_txt_buf guid_buf;
	int ret;
	bool stop = false;

	t->changes.num_processed += 1;

	/*
	 * First confirm this object has children, or exists
	 * (depending on change->force_self)
	 * 
	 * LDB_SCOPE_SUBTREE searches are expensive.
	 *
	 * We know this is safe against a rename race as we are in the
	 * prepare_commit(), so must be in a transaction.
	 */

	/* Find the DN by GUID, as this is stable under rename */
	ret = dsdb_module_search(module,
				 change,
				 &guid_res,
				 change->nc_root,
				 LDB_SCOPE_SUBTREE,
				 no_attrs,
				 DSDB_FLAG_NEXT_MODULE |
				 DSDB_FLAG_AS_SYSTEM |
				 DSDB_SEARCH_SHOW_DELETED |
				 DSDB_SEARCH_SHOW_RECYCLED |
				 DSDB_SEARCH_SHOW_EXTENDED_DN,
				 NULL, /* parent_req */
				 "(objectGUID=%s)",
				 GUID_buf_string(&change->guid,
						 &guid_buf));

	if (ret != LDB_SUCCESS) {
		return ret;
	}

	if (guid_res->count != 1) {
		/*
		 * We were just given this GUID during the same
		 * transaction, if it is missing this is a big
		 * problem.
		 *
		 * Cleanup of tombstones does not trigger this module
		 * as it just does a delete.
		 */
		ldb_asprintf_errstring(ldb_module_get_ctx(module),
				       "failed to find GUID %s under %s "
				       "for transaction-end SD inheritance: %d results",
				       GUID_buf_string(&change->guid,
						       &guid_buf),
				       ldb_dn_get_linearized(change->nc_root),
				       guid_res->count);
		return LDB_ERR_OPERATIONS_ERROR;
	}

	/*
	 * OK, so there was a parent, are there children?  Note: that
	 * this time we do not search for deleted/recycled objects
	 */
	ret = dsdb_module_search(module,
				 change,
				 &res,
				 guid_res->msgs[0]->dn,
				 LDB_SCOPE_ONELEVEL,
				 no_attrs,
				 DSDB_FLAG_NEXT_MODULE |
				 DSDB_FLAG_AS_SYSTEM,
				 NULL, /* parent_req */
				 "(objectClass=*)");
	if (ret != LDB_SUCCESS) {
		/*
		 * LDB_ERR_NO_SUCH_OBJECT, say if the DN was a deleted
		 * object, is ignored by the caller
		 */
		return ret;
	}

	if (res->count == 0 && !change->force_self) {
		/* All done, no children */
		TALLOC_FREE(res);
		return LDB_SUCCESS;
	}

	/*
	 * First, if we are in force_self mode (eg renamed under new
	 * parent) then apply the SD to the top object
	 */
	if (change->force_self) {
		ret = descriptor_sd_propagation_object(module,
						       guid_res->msgs[0],
						       &stop);
		if (ret != LDB_SUCCESS) {
			TALLOC_FREE(guid_res);
			return ret;
		}

		if (stop == true && !change->force_children) {
			/* There was no change, nothing more to do */
			TALLOC_FREE(guid_res);
			return LDB_SUCCESS;
		}

		if (res->count == 0) {
			/* All done! */
			TALLOC_FREE(guid_res);
			return LDB_SUCCESS;
		}
	}

	/*
	 * Look for children
	 *
	 * Note: that we do not search for deleted/recycled objects
	 */
	ret = dsdb_module_search(module,
				 change,
				 &res,
				 guid_res->msgs[0]->dn,
				 LDB_SCOPE_SUBTREE,
				 no_attrs,
				 DSDB_FLAG_NEXT_MODULE |
				 DSDB_FLAG_AS_SYSTEM |
				 DSDB_SEARCH_SHOW_EXTENDED_DN,
				 NULL, /* parent_req */
				 "(objectClass=*)");
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	TYPESAFE_QSORT(res->msgs, res->count,
		       descriptor_sd_propagation_msg_sort);

	/* We start from 1, the top object has been done */
	for (i = 1; i < res->count; i++) {
		/*
		 * ldb_dn_compare_base() does not match for NULL but
		 * this is clearer
		 */
		if (stopped_dn != NULL) {
			ret = ldb_dn_compare_base(stopped_dn,
						  res->msgs[i]->dn);
			/*
			 * Skip further processing of this
			 * sub-subtree
			 */
			if (ret == 0) {
				continue;
			}
		}
		ret = descriptor_sd_propagation_object(module,
						       res->msgs[i],
						       &stop);
		if (ret != LDB_SUCCESS) {
			return ret;
		}

		if (stop) {
			/*
			 * If this child didn't change, then nothing
			 * under it needs to change
			 *
			 * res has been sorted into tree order so the
			 * next few entries can be skipped
			 */
			stopped_dn = res->msgs[i]->dn;
		}
	}

	TALLOC_FREE(res);
	return LDB_SUCCESS;
}