commonlib/list: Add list_move()
This function transfers all elements from one list head to another. The The destination list head takes ownership of all nodes from the source list head. The source list head is reinitialized to an empty list. This is useful for efficiently moving list contents without element-wise relinking, particularly in contexts like device tree overlay application where node structures are incorporated from a temporary tree. BUG=b:434080284 TEST=emerge-rauru coreboot libpayload BRANCH=none Change-Id: I143394e381fa72bcba692b7727f57dfc09fda70e Signed-off-by: Yu-Ping Wu <yupingso@google.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/91557 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Jakub "Kuba" Czapiga <czapiga@google.com> Reviewed-by: Karthik Ramasubramanian <kramasub@google.com> Reviewed-by: Julius Werner <jwerner@chromium.org> Reviewed-by: Subrata Banik <subratabanik@google.com>
This commit is contained in:
parent
00e3b9989c
commit
89048780c0
3 changed files with 89 additions and 1 deletions
|
|
@ -72,6 +72,17 @@ static inline struct list_node *list_last(const struct list_node *head)
|
|||
// Get the number of list elements.
|
||||
size_t list_length(const struct list_node *head);
|
||||
|
||||
/*
|
||||
* Move all elements from list src_head to list dst_head.
|
||||
*
|
||||
* @param dst_head Destination list to fill. This MUST be empty.
|
||||
* @param src_head Source list to be moved. This will be cleared after the call.
|
||||
*
|
||||
* After the call, the ownership of the src list elements passes to the dst list.
|
||||
* Callers must NOT free the src list elements afterwards.
|
||||
*/
|
||||
void list_move(struct list_node *dst_head, struct list_node *src_head);
|
||||
|
||||
/*
|
||||
* Explanation of `ptr` initialization:
|
||||
* 1. head.next != NULL: This means the list isn't empty. As the implementation ensures that
|
||||
|
|
|
|||
|
|
@ -7,11 +7,16 @@
|
|||
#define NEXT(ptr) ((ptr)->_internal_do_not_access.next)
|
||||
#define PREV(ptr) ((ptr)->_internal_do_not_access.prev)
|
||||
|
||||
static inline void list_clear(struct list_node *head)
|
||||
{
|
||||
PREV(head) = NEXT(head) = head;
|
||||
}
|
||||
|
||||
void _list_init(struct list_node *head)
|
||||
{
|
||||
if (!NEXT(head)) {
|
||||
assert(!PREV(head));
|
||||
PREV(head) = NEXT(head) = head;
|
||||
list_clear(head);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,5 +62,23 @@ size_t list_length(const struct list_node *head)
|
|||
return len;
|
||||
}
|
||||
|
||||
void list_move(struct list_node *dst_head, struct list_node *src_head)
|
||||
{
|
||||
assert(list_is_empty(dst_head));
|
||||
|
||||
if (list_is_empty(src_head))
|
||||
return;
|
||||
|
||||
NEXT(dst_head) = NEXT(src_head);
|
||||
PREV(dst_head) = PREV(src_head);
|
||||
|
||||
/* The elements formerly in src_head's list must now point back to dst_head. */
|
||||
NEXT(PREV(dst_head)) = dst_head;
|
||||
PREV(NEXT(dst_head)) = dst_head;
|
||||
|
||||
/* Clear src_head to be empty. */
|
||||
list_clear(src_head);
|
||||
}
|
||||
|
||||
#undef NEXT
|
||||
#undef PREV
|
||||
|
|
|
|||
|
|
@ -176,6 +176,57 @@ static void test_list_append(void **state)
|
|||
assert_int_equal(3, list_length(&head));
|
||||
}
|
||||
|
||||
static void test_list_move(void **state)
|
||||
{
|
||||
size_t idx;
|
||||
struct test_container *node;
|
||||
struct list_node src_head = {};
|
||||
struct list_node dst_head = {};
|
||||
struct test_container nodes[] = {
|
||||
{1}, {2}, {3}
|
||||
};
|
||||
|
||||
for (idx = 0; idx < ARRAY_SIZE(nodes); ++idx)
|
||||
list_append(&nodes[idx].list_node, &src_head);
|
||||
|
||||
list_move(&dst_head, &src_head);
|
||||
|
||||
/* Check src_head is cleared. */
|
||||
assert_true(list_is_empty(&src_head));
|
||||
|
||||
/* Check dst_head has all elements */
|
||||
idx = 0;
|
||||
list_for_each(node, dst_head, list_node) {
|
||||
assert_true(idx < ARRAY_SIZE(nodes));
|
||||
assert_ptr_equal(node, &nodes[idx]);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
static void test_list_move_empty(void **state)
|
||||
{
|
||||
struct list_node src_head = {};
|
||||
struct list_node dst_head = {};
|
||||
|
||||
/* Test moving an empty list. */
|
||||
list_move(&dst_head, &src_head);
|
||||
|
||||
assert_true(list_is_empty(&src_head));
|
||||
assert_true(list_is_empty(&dst_head));
|
||||
}
|
||||
|
||||
static void test_list_move_invalid(void **state)
|
||||
{
|
||||
struct list_node src_head = {};
|
||||
struct list_node dst_head = {};
|
||||
struct test_container c = { .value = 1 };
|
||||
|
||||
list_append(&c.list_node, &dst_head);
|
||||
|
||||
/* Test moving to a non-empty dst. */
|
||||
expect_assert_failure(list_move(&dst_head, &src_head));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const struct CMUnitTest tests[] = {
|
||||
|
|
@ -187,6 +238,9 @@ int main(void)
|
|||
cmocka_unit_test(test_list_remove),
|
||||
cmocka_unit_test(test_list_remove_head),
|
||||
cmocka_unit_test(test_list_append),
|
||||
cmocka_unit_test(test_list_move),
|
||||
cmocka_unit_test(test_list_move_empty),
|
||||
cmocka_unit_test(test_list_move_invalid),
|
||||
};
|
||||
|
||||
return cb_run_group_tests(tests, NULL, NULL);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue