diff --git a/src/commonlib/include/commonlib/list.h b/src/commonlib/include/commonlib/list.h index a6c87ceee2..33875bc10b 100644 --- a/src/commonlib/include/commonlib/list.h +++ b/src/commonlib/include/commonlib/list.h @@ -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 diff --git a/src/commonlib/list.c b/src/commonlib/list.c index 93a9b12887..31c6a1d06d 100644 --- a/src/commonlib/list.c +++ b/src/commonlib/list.c @@ -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 diff --git a/tests/commonlib/list-test.c b/tests/commonlib/list-test.c index e4dbd8e14a..0c0e59530b 100644 --- a/tests/commonlib/list-test.c +++ b/tests/commonlib/list-test.c @@ -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);