pdr: Introduce pldm_entity_association_pdr_add_check()

pldm_entity_association_pdr_add_check() replaces
pldm_entity_association_pdr_add(), which requires assert() for error
handling. pldm_entity_association_pdr_add_check() instead returns a
value that indicates success or failure.

Note that a failure of pldm_entity_association_pdr_add_check() cannot
guarantee consistency of the PDR repository. Multiple fallible
actions that mutate the PDR repository are required and there is
currently no method to unwind the mutations up to the point of failure.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I6853932b314260d4b0e2b3545a2a067b781a7ffe
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 25c95cf..ac4efb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,10 @@
 
 ## [Unreleased]
 
+### Added
+
+1. pdr: Introduce pldm_entity_association_pdr_add_check()
+
 ### Changed
 
 1. pdr: Allow record_handle to be NULL for pldm_pdr_add_check()
diff --git a/include/libpldm/pdr.h b/include/libpldm/pdr.h
index e929914..ada8b27 100644
--- a/include/libpldm/pdr.h
+++ b/include/libpldm/pdr.h
@@ -469,6 +469,24 @@
 				     pldm_pdr *repo, bool is_remote,
 				     uint16_t terminus_handle);
 
+/** @brief Convert entity association tree to PDR, or return an error
+ *
+ *  No conversion takes place if one or both of tree or repo are NULL.
+ *
+ *  If an error is returned then the state and consistency of the PDR repository is undefined.
+ *
+ *  @param[in] tree - opaque pointer to entity association tree
+ *  @param[in] repo - PDR repo where entity association records should be added
+ *  @param[in] is_remote - if true, then the PDR is not from this terminus
+ *  @param[in] terminus_handle - terminus handle of the terminus
+ *
+ *  @return 0 on success, -EINVAL if the arguments are invalid, -ENOMEM if an internal memory
+ *  allocation fails, or -EOVERFLOW if a record handle could not be allocated
+ */
+int pldm_entity_association_pdr_add_check(pldm_entity_association_tree *tree,
+					  pldm_pdr *repo, bool is_remote,
+					  uint16_t terminus_handle);
+
 /** @brief Add entity association pdr from node
  *
  *  @param[in] node - opaque pointer acting as a handle to an entity node
diff --git a/src/pdr.c b/src/pdr.c
index 56c07b5..a4277cc 100644
--- a/src/pdr.c
+++ b/src/pdr.c
@@ -797,7 +797,7 @@
 	return false;
 }
 
-static void entity_association_pdr_add_children(
+static int entity_association_pdr_add_children(
 	pldm_entity_node *curr, pldm_pdr *repo, uint16_t size,
 	uint8_t contained_count, uint8_t association_type, bool is_remote,
 	uint16_t terminus_handle, uint32_t record_handle)
@@ -842,19 +842,20 @@
 		node = node->next_sibling;
 	}
 
-	pldm_pdr_add(repo, pdr, size, record_handle, is_remote,
-		     terminus_handle);
+	return pldm_pdr_add_check(repo, pdr, size, is_remote, terminus_handle,
+				  &record_handle);
 }
 
-static void entity_association_pdr_add_entry(pldm_entity_node *curr,
-					     pldm_pdr *repo, bool is_remote,
-					     uint16_t terminus_handle,
-					     uint32_t record_handle)
+static int entity_association_pdr_add_entry(pldm_entity_node *curr,
+					    pldm_pdr *repo, bool is_remote,
+					    uint16_t terminus_handle,
+					    uint32_t record_handle)
 {
 	uint8_t num_logical_children = pldm_entity_get_num_children(
 		curr, PLDM_ENTITY_ASSOCIAION_LOGICAL);
 	uint8_t num_physical_children = pldm_entity_get_num_children(
 		curr, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+	int rc;
 
 	if (num_logical_children) {
 		uint16_t logical_pdr_size =
@@ -862,10 +863,13 @@
 			sizeof(uint8_t) + sizeof(pldm_entity) +
 			sizeof(uint8_t) +
 			(num_logical_children * sizeof(pldm_entity));
-		entity_association_pdr_add_children(
+		rc = entity_association_pdr_add_children(
 			curr, repo, logical_pdr_size, num_logical_children,
 			PLDM_ENTITY_ASSOCIAION_LOGICAL, is_remote,
 			terminus_handle, record_handle);
+		if (rc < 0) {
+			return rc;
+		}
 	}
 
 	if (num_physical_children) {
@@ -874,11 +878,16 @@
 			sizeof(uint8_t) + sizeof(pldm_entity) +
 			sizeof(uint8_t) +
 			(num_physical_children * sizeof(pldm_entity));
-		entity_association_pdr_add_children(
+		rc = entity_association_pdr_add_children(
 			curr, repo, physical_pdr_size, num_physical_children,
 			PLDM_ENTITY_ASSOCIAION_PHYSICAL, is_remote,
 			terminus_handle, record_handle);
+		if (rc < 0) {
+			return rc;
+		}
 	}
+
+	return 0;
 }
 
 LIBPLDM_ABI_DEPRECATED
@@ -897,27 +906,36 @@
 	return false;
 }
 
-static void entity_association_pdr_add(pldm_entity_node *curr, pldm_pdr *repo,
-				       pldm_entity **entities,
-				       size_t num_entities, bool is_remote,
-				       uint16_t terminus_handle,
-				       uint32_t record_handle)
+static int entity_association_pdr_add(pldm_entity_node *curr, pldm_pdr *repo,
+				      pldm_entity **entities,
+				      size_t num_entities, bool is_remote,
+				      uint16_t terminus_handle,
+				      uint32_t record_handle)
 {
+	int rc;
+
 	if (curr == NULL) {
-		return;
+		return 0;
 	}
-	bool to_add = true;
-	to_add = is_present(curr->entity, entities, num_entities);
-	if (to_add) {
-		entity_association_pdr_add_entry(
+
+	if (is_present(curr->entity, entities, num_entities)) {
+		rc = entity_association_pdr_add_entry(
 			curr, repo, is_remote, terminus_handle, record_handle);
+		if (rc) {
+			return rc;
+		}
 	}
-	entity_association_pdr_add(curr->next_sibling, repo, entities,
-				   num_entities, is_remote, terminus_handle,
-				   record_handle);
-	entity_association_pdr_add(curr->first_child, repo, entities,
-				   num_entities, is_remote, terminus_handle,
-				   record_handle);
+
+	rc = entity_association_pdr_add(curr->next_sibling, repo, entities,
+					num_entities, is_remote,
+					terminus_handle, record_handle);
+	if (rc) {
+		return rc;
+	}
+
+	return entity_association_pdr_add(curr->first_child, repo, entities,
+					  num_entities, is_remote,
+					  terminus_handle, record_handle);
 }
 
 LIBPLDM_ABI_STABLE
@@ -925,12 +943,23 @@
 				     pldm_pdr *repo, bool is_remote,
 				     uint16_t terminus_handle)
 {
+	int rc = pldm_entity_association_pdr_add_check(tree, repo, is_remote,
+						       terminus_handle);
+	(void)rc;
+	assert(!rc);
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_entity_association_pdr_add_check(pldm_entity_association_tree *tree,
+					  pldm_pdr *repo, bool is_remote,
+					  uint16_t terminus_handle)
+{
 	if (!tree || !repo) {
-		return;
+		return 0;
 	}
 
-	entity_association_pdr_add(tree->root, repo, NULL, 0, is_remote,
-				   terminus_handle, 0);
+	return entity_association_pdr_add(tree->root, repo, NULL, 0, is_remote,
+					  terminus_handle, 0);
 }
 
 LIBPLDM_ABI_DEPRECATED