diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h index 39933c7266..03ef9479f9 100644 --- a/include/asterisk/astobj2.h +++ b/include/asterisk/astobj2.h @@ -1419,6 +1419,28 @@ int ao2_container_count(struct ao2_container *c); */ int ao2_container_dup(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags); +/*! + * \brief Copy object references associated with src container weakproxies into the dest container. + * + * \param dest Container to copy src strong object references into. + * \param src Container to copy all weak object references from. + * \param flags OBJ_NOLOCK if a lock is already held on both containers. + * Otherwise, the src container is locked first. + * + * \pre The dest container must be empty. If the duplication fails, the + * dest container will be returned empty. + * + * \note This can potentially be expensive because a malloc is + * needed for every object in the src container. + * + * \note Every object inside the container is locked by \ref ao2_weakproxy_get_object. + * Any weakproxy in \ref src with no associated object is ignored. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int ao2_container_dup_weakproxy_objs(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags); + /*! * \brief Create a clone/copy of the given container. * \since 11.0 diff --git a/main/astobj2_container.c b/main/astobj2_container.c index 51978674e1..9a837bde75 100644 --- a/main/astobj2_container.c +++ b/main/astobj2_container.c @@ -697,6 +697,59 @@ int ao2_container_dup(struct ao2_container *dest, struct ao2_container *src, enu return res; } +/*! + * \brief Copy obj associated with a weakproxy into the arg container. + * + * \param proxy pointer to the weakproxy. + * \param arg callback argument from ao2_callback() + * \param flags flags from ao2_callback() + * + * \retval 0 on success. + * \retval CMP_STOP|CMP_MATCH on error. + */ +static int dup_weakproxy_cb(void *proxy, void *arg, int flags) +{ + void *obj = ao2_weakproxy_get_object(proxy, 0); + struct ao2_container *dest = arg; + int ret; + + if (!obj) { + return 0; + } + + ret = ao2_t_link_flags(dest, obj, OBJ_NOLOCK, NULL) ? 0 : (CMP_MATCH | CMP_STOP); + ao2_ref(obj, -1); + + return ret; +} + +int ao2_container_dup_weakproxy_objs(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags) +{ + void *obj; + int res = 0; + + if (!(flags & OBJ_NOLOCK)) { + ao2_rdlock(src); + ao2_wrlock(dest); + } + obj = ao2_callback(src, OBJ_NOLOCK, dup_weakproxy_cb, dest); + if (obj) { + /* Failed to put this obj into the dest container. */ + ao2_t_ref(obj, -1, "Failed to put this object into the dest container."); + + /* Remove all items from the dest container. */ + ao2_t_callback(dest, OBJ_NOLOCK | OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, + NULL, NULL); + res = -1; + } + if (!(flags & OBJ_NOLOCK)) { + ao2_unlock(dest); + ao2_unlock(src); + } + + return res; +} + struct ao2_container *__ao2_container_clone(struct ao2_container *orig, enum search_flags flags, const char *tag, const char *file, int line, const char *func) { struct ao2_container *clone; diff --git a/tests/test_astobj2_weaken.c b/tests/test_astobj2_weaken.c index 2755b6e52d..13f48ac19c 100644 --- a/tests/test_astobj2_weaken.c +++ b/tests/test_astobj2_weaken.c @@ -262,9 +262,166 @@ fail_cleanup: return AST_TEST_FAIL; } +struct strong_str { + char *value; +}; + +struct weakproxy_str { + AO2_WEAKPROXY(); + char value[0]; +}; + +static struct strong_str *alloc_str(struct ao2_container *weakcontainer, const char *value) +{ + struct strong_str *strong = ao2_t_alloc(sizeof(*strong), NULL, value); + struct weakproxy_str *weak = ao2_weakproxy_alloc(sizeof(*weak) + strlen(value) + 1, NULL); + + if (!weak || !strong) { + goto error_return; + } + + strcpy(weak->value, value); /*SAFE*/ + strong->value = weak->value; + + if (ao2_weakproxy_set_object(weak, strong, 0)) { + goto error_return; + } + + if (!ao2_link(weakcontainer, weak)) { + goto error_return; + } + + ao2_ref(weak, -1); + return strong; + +error_return: + ao2_cleanup(weak); + ao2_cleanup(strong); + + return NULL; +} + +AO2_STRING_FIELD_HASH_FN(weakproxy_str, value); +AO2_STRING_FIELD_CMP_FN(weakproxy_str, value); +AO2_STRING_FIELD_SORT_FN(strong_str, value); + +#define ITERATOR_CHECK_NEXT(iter, var, expected) \ + do { \ + var = ao2_iterator_next(iter); \ + ast_test_validate_cleanup(test, var == expected, ret, cleanup); \ + ao2_cleanup(var); \ + } while (0) + +#define WEAKFIND_CHECK(c, key, var, expected) \ + do { \ + var = ao2_weakproxy_find(c, key, OBJ_SEARCH_KEY, ""); \ + ast_test_validate_cleanup(test, var == expected, ret, cleanup); \ + ao2_cleanup(var); \ + } while (0) + +AST_TEST_DEFINE(astobj2_weak_container) +{ + int ret = AST_TEST_FAIL; + + struct strong_str *strong1 = NULL; + struct strong_str *strong2 = NULL; + struct strong_str *strong3 = NULL; + + struct strong_str *strong = NULL; + + struct ao2_container *weakcontainer = NULL; + struct ao2_container *dupcontainer = NULL; + + struct ao2_iterator iter; + + switch (cmd) { + case TEST_INIT: + info->name = "astobj2_weak_container"; + info->category = "/main/astobj2/"; + info->summary = "Test ao2 weak containers"; + info->description = "Test ao2 weak containers."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + weakcontainer = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 7, + weakproxy_str_hash_fn, NULL, weakproxy_str_cmp_fn); + dupcontainer = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, + strong_str_sort_fn, NULL); + + if (!weakcontainer || !dupcontainer) { + goto cleanup; + } + + strong1 = alloc_str(weakcontainer, "obj1"); + strong2 = alloc_str(weakcontainer, "obj2"); + strong3 = alloc_str(weakcontainer, "obj3"); + + if (!strong1 || !strong2 || !strong3) { + goto cleanup; + } + + if (ao2_container_dup_weakproxy_objs(dupcontainer, weakcontainer, 0)) { + goto cleanup; + } + + iter = ao2_iterator_init(dupcontainer, 0); + ITERATOR_CHECK_NEXT(&iter, strong, strong1); + ITERATOR_CHECK_NEXT(&iter, strong, strong2); + ITERATOR_CHECK_NEXT(&iter, strong, strong3); + ITERATOR_CHECK_NEXT(&iter, strong, NULL); + ao2_iterator_cleanup(&iter); + + ao2_callback(dupcontainer, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, NULL, NULL); + + WEAKFIND_CHECK(weakcontainer, "obj1", strong, strong1); + WEAKFIND_CHECK(weakcontainer, "obj2", strong, strong2); + WEAKFIND_CHECK(weakcontainer, "obj3", strong, strong3); + WEAKFIND_CHECK(weakcontainer, "unknown", strong, NULL); + + /* This will orphan "obj2" in weakcontainer. */ + ao2_replace(strong2, NULL); + + if (ao2_container_dup_weakproxy_objs(dupcontainer, weakcontainer, 0)) { + goto cleanup; + } + + ast_test_validate_cleanup(test, + ao2_container_count(weakcontainer) == ao2_container_count(dupcontainer) + 1, + ret, + cleanup); + + iter = ao2_iterator_init(dupcontainer, 0); + ITERATOR_CHECK_NEXT(&iter, strong, strong1); + ITERATOR_CHECK_NEXT(&iter, strong, strong3); + ITERATOR_CHECK_NEXT(&iter, strong, NULL); + ao2_iterator_cleanup(&iter); + + WEAKFIND_CHECK(weakcontainer, "obj1", strong, strong1); + WEAKFIND_CHECK(weakcontainer, "obj2", strong, NULL); + WEAKFIND_CHECK(weakcontainer, "obj3", strong, strong3); + WEAKFIND_CHECK(weakcontainer, "unknown", strong, NULL); + + ret = AST_TEST_PASS; + +cleanup: + ao2_cleanup(strong1); + ao2_cleanup(strong2); + ao2_cleanup(strong3); + + ao2_cleanup(weakcontainer); + ao2_cleanup(dupcontainer); + + ao2_cleanup(strong); + + return ret; +} + static int unload_module(void) { AST_TEST_UNREGISTER(astobj2_weak1); + AST_TEST_UNREGISTER(astobj2_weak_container); return 0; } @@ -272,6 +429,7 @@ static int unload_module(void) static int load_module(void) { AST_TEST_REGISTER(astobj2_weak1); + AST_TEST_REGISTER(astobj2_weak_container); return AST_MODULE_LOAD_SUCCESS; }