static int replmd_process_la_group(struct ldb_module *module,
				   struct replmd_private *replmd_private,
				   struct la_group *la_group)
{
	struct la_entry *la = NULL;
	struct la_entry *prev = NULL;
	int ret;
	TALLOC_CTX *tmp_ctx = NULL;
	struct la_entry *first_la = DLIST_TAIL(la_group->la_entries);
	struct ldb_message *msg = NULL;
	enum deletion_state deletion_state = OBJECT_NOT_DELETED;
	struct ldb_context *ldb = ldb_module_get_ctx(module);
	const struct dsdb_attribute *attr = NULL;
	struct ldb_message_element *old_el = NULL;
	struct parsed_dn *pdn_list = NULL;
	replmd_link_changed change_type;
	uint32_t num_changes = 0;
	time_t t;
	uint64_t seq_num = 0;

	tmp_ctx = talloc_new(la_group);
	if (tmp_ctx == NULL) {
		return ldb_oom(ldb);
	}

	/*
	 * get the attribute being modified and the search result for the
	 * source object
	 */
	ret = replmd_get_la_entry_source(module, first_la, tmp_ctx, &attr,
					 &msg);

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

	/*
	 * Check for deleted objects per MS-DRSR 4.1.10.6.14
	 * ProcessLinkValue, because link updates are not applied to
	 * recycled and tombstone objects.  We don't have to delete
	 * any existing link, that should have happened when the
	 * object deletion was replicated or initiated.
	 *
	 * This needs isDeleted and isRecycled to be included as
	 * attributes in the search and so in msg if set.
	 */
	replmd_deletion_state(module, msg, &deletion_state, NULL);

	if (deletion_state >= OBJECT_RECYCLED) {
		TALLOC_FREE(tmp_ctx);
		return LDB_SUCCESS;
	}

	/*
	 * Now that we know the deletion_state, remove the extra
	 * attributes added for that purpose.  We need to do this
	 * otherwise in the case of isDeleted: FALSE the modify will
	 * fail with:
	 *
	 * Failed to apply linked attribute change 'attribute 'isDeleted':
	 * invalid modify flags on
	 * 'CN=g1_1527570609273,CN=Users,DC=samba,DC=example,DC=com':
	 * 0x0'
	 *
	 * This is becaue isDeleted is a Boolean, so FALSE is a
	 * legitimate value (set by Samba's deletetest.py)
	 */
	ldb_msg_remove_attr(msg, "isDeleted");
	ldb_msg_remove_attr(msg, "isRecycled");

	/* get the msg->element[] for the link attribute being processed */
	old_el = ldb_msg_find_element(msg, attr->lDAPDisplayName);
	if (old_el == NULL) {
		ret = ldb_msg_add_empty(msg, attr->lDAPDisplayName,
					LDB_FLAG_MOD_REPLACE, &old_el);
		if (ret != LDB_SUCCESS) {
			ldb_module_oom(module);
			return LDB_ERR_OPERATIONS_ERROR;
		}
	} else {
		old_el->flags = LDB_FLAG_MOD_REPLACE;
	}

	/*
	 * go through and process the link target value(s) for this particular
	 * source object and attribute. For optimization, the same msg is used
	 * across multiple calls to replmd_process_linked_attribute().
	 * Note that we should not add or remove any msg attributes inside the
	 * loop (we should only add/modify *values* for the attribute being
	 * processed). Otherwise msg->elements is realloc'd and old_el/pdn_list
	 * pointers will be invalidated
	 */
	for (la = DLIST_TAIL(la_group->la_entries); la; la=prev) {
		prev = DLIST_PREV(la);
		DLIST_REMOVE(la_group->la_entries, la);

		/*
		 * parse the existing links (this can be costly for a large
		 * group, so we try to minimize the times we do it)
		 */
		if (pdn_list == NULL) {
			ret = get_parsed_dns_trusted_fallback(module,
							replmd_private,
							tmp_ctx, old_el,
							&pdn_list,
							attr->syntax->ldap_oid,
							NULL);

			if (ret != LDB_SUCCESS) {
				return ret;
			}
		}
		ret = replmd_process_linked_attribute(module, tmp_ctx,
						      replmd_private,
						      msg->dn, attr, la, NULL,
						      msg->elements, old_el,
						      pdn_list, &change_type);
		if (ret != LDB_SUCCESS) {
			replmd_txn_cleanup(replmd_private);
			return ret;
		}

		/*
		 * Adding a link reallocs memory, and so invalidates all the
		 * pointers in pdn_list. Reparse the PDNs on the next loop
		 */
		if (change_type == LINK_CHANGE_ADDED) {
			TALLOC_FREE(pdn_list);
		}

		if (change_type != LINK_CHANGE_NONE) {
			num_changes++;
		}

		if ((++replmd_private->num_processed % 8192) == 0) {
			DBG_NOTICE("Processed %u/%u linked attributes\n",
				   replmd_private->num_processed,
				   replmd_private->total_links);
		}
	}

	/*
	 * it's possible we're already up-to-date and so don't need to modify
	 * the object at all (e.g. doing a 'drs replicate --full-sync')
	 */
	if (num_changes == 0) {
		TALLOC_FREE(tmp_ctx);
		return LDB_SUCCESS;
	}

	/*
	 * Note that adding the whenChanged/etc attributes below will realloc
	 * msg->elements, invalidating the existing element/parsed-DN pointers
	 */
	old_el = NULL;
	TALLOC_FREE(pdn_list);

	/* update whenChanged/uSNChanged as the object has changed */
	t = time(NULL);
	ret = ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ,
				  &seq_num);
	if (ret != LDB_SUCCESS) {
		return ret;
	}

	ret = add_time_element(msg, "whenChanged", t);
	if (ret != LDB_SUCCESS) {
		ldb_operr(ldb);
		return ret;
	}

	ret = add_uint64_element(ldb, msg, "uSNChanged", seq_num);
	if (ret != LDB_SUCCESS) {
		ldb_operr(ldb);
		return ret;
	}

	/* apply the link changes to the source object */
	ret = linked_attr_modify(module, msg, NULL);
	if (ret != LDB_SUCCESS) {
		ldb_debug(ldb, LDB_DEBUG_WARNING,
			  "Failed to apply linked attribute change "
			  "Error: '%s' DN: '%s' Attribute: '%s'\n",
			  ldb_errstring(ldb),
			  ldb_dn_get_linearized(msg->dn),
			  attr->lDAPDisplayName);
		TALLOC_FREE(tmp_ctx);
		return ret;
	}

	TALLOC_FREE(tmp_ctx);
	return LDB_SUCCESS;
}