static int replmd_modify_handle_linked_attribs(struct ldb_module *module,
					       struct replmd_private *replmd_private,
					       struct replmd_replicated_request *ac,
					       struct ldb_message *msg,
					       time_t t,
					       struct ldb_request *parent)
{
	struct ldb_result *res;
	unsigned int i;
	int ret;
	struct ldb_context *ldb = ldb_module_get_ctx(module);
	struct ldb_message *old_msg;

	if (dsdb_functional_level(ldb) == DS_DOMAIN_FUNCTION_2000) {
		/*
		 * Nothing special is required for modifying or vanishing links
		 * in fl2000 since they are just strings in a multi-valued
		 * attribute.
		 */
		struct ldb_control *ctrl = ldb_request_get_control(parent,
								   DSDB_CONTROL_REPLMD_VANISH_LINKS);
		if (ctrl) {
			ctrl->critical = false;
		}
		return LDB_SUCCESS;
	}

	/*
	 * TODO:
	 *
	 * We should restrict this to the intersection of the list of
	 * linked attributes in the schema and the list of attributes
	 * being modified.
	 *
	 * This will help performance a little, as otherwise we have
	 * to allocate the entire object value-by-value.
	 */
	ret = dsdb_module_search_dn(module, msg, &res, msg->dn, NULL,
	                            DSDB_FLAG_NEXT_MODULE |
	                            DSDB_SEARCH_SHOW_RECYCLED |
				    DSDB_SEARCH_REVEAL_INTERNALS |
				    DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
				    parent);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	old_msg = res->msgs[0];

	for (i=0; i<msg->num_elements; i++) {
		struct ldb_message_element *el = &msg->elements[i];
		struct ldb_message_element *old_el, *new_el;
		unsigned int mod_type = LDB_FLAG_MOD_TYPE(el->flags);
		const struct dsdb_attribute *schema_attr
			= dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
		if (!schema_attr) {
			ldb_asprintf_errstring(ldb,
					       "%s: attribute %s is not a valid attribute in schema",
					       __FUNCTION__, el->name);
			return LDB_ERR_OBJECT_CLASS_VIOLATION;
		}
		if (schema_attr->linkID == 0) {
			continue;
		}
		if ((schema_attr->linkID & 1) == 1) {
			struct ldb_control *ctrl;

			ctrl = ldb_request_get_control(parent,
						       DSDB_CONTROL_REPLMD_VANISH_LINKS);
			if (ctrl != NULL) {
				ctrl->critical = false;
				continue;
			}
			ctrl = ldb_request_get_control(parent,
						       DSDB_CONTROL_DBCHECK);
			if (ctrl != NULL) {
				continue;
			}

			/* Odd is for the target.  Illegal to modify */
			ldb_asprintf_errstring(ldb,
					       "attribute %s must not be modified directly, it is a linked attribute", el->name);
			return LDB_ERR_UNWILLING_TO_PERFORM;
		}
		old_el = ldb_msg_find_element(old_msg, el->name);
		switch (mod_type) {
		case LDB_FLAG_MOD_REPLACE:
			ret = replmd_modify_la_replace(module, replmd_private,
						       ac, msg, el, old_el,
						       schema_attr, t,
						       old_msg->dn,
						       parent);
			break;
		case LDB_FLAG_MOD_DELETE:
			ret = replmd_modify_la_delete(module, replmd_private,
						      ac, msg, el, old_el,
						      schema_attr, t,
						      old_msg->dn,
						      parent);
			break;
		case LDB_FLAG_MOD_ADD:
			ret = replmd_modify_la_add(module, replmd_private,
						   ac, msg, el, old_el,
						   schema_attr, t,
						   old_msg->dn,
						   parent);
			break;
		default:
			ldb_asprintf_errstring(ldb,
					       "invalid flags 0x%x for %s linked attribute",
					       el->flags, el->name);
			return LDB_ERR_UNWILLING_TO_PERFORM;
		}
		if (ret != LDB_SUCCESS) {
			return ret;
		}
		ret = dsdb_check_single_valued_link(schema_attr, el);
		if (ret != LDB_SUCCESS) {
			ldb_asprintf_errstring(ldb,
					       "Attribute %s is single valued but more than one value has been supplied",
					       el->name);
			/* Return codes as found on Windows 2012r2 */
			if (mod_type == LDB_FLAG_MOD_REPLACE) {
				return LDB_ERR_CONSTRAINT_VIOLATION;
			} else {
				return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
			}
		} else {
			el->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
		}

		if (old_el) {
			ldb_msg_remove_attr(old_msg, el->name);
		}
		ldb_msg_add_empty(old_msg, el->name, 0, &new_el);
		new_el->num_values = el->num_values;
		new_el->values = talloc_steal(msg->elements, el->values);

		/* TODO: this relises a bit too heavily on the exact
		   behaviour of ldb_msg_find_element and
		   ldb_msg_remove_element */
		old_el = ldb_msg_find_element(msg, el->name);
		if (old_el != el) {
			ldb_msg_remove_element(msg, old_el);
			i--;
		}
	}

	talloc_free(res);
	return ret;
}