Files
asterisk/main/channelstorage.c
George Joseph 487bf8af26 channelstorage: Allow storage driver read locking to be skipped.
After PR #1498 added read locking to channelstorage_cpp_map_name_id, if ARI
channels/externalMedia was called with a custom channel id AND the
cpp_map_name_id channel storage backend is in use, a deadlock can occur when
hanging up the channel. It's actually triggered in
channel.c:__ast_channel_alloc_ap() when it gets a write lock on the
channelstorage driver then subsequently does a lookup for channel uniqueid
which now does a read lock. This is an invalid operation and causes the lock
state to get "bad". When the channels try to hang up, a write lock is
attempted again which hangs and causes the deadlock.

Now instead of the cpp_map_name_id channelstorage driver "get" APIs
automatically performing a read lock, they take a "lock" parameter which
allows a caller who already has a write lock to indicate that the "get" API
must not attempt its own lock.  This prevents the state from getting mesed up.

The ao2_legacy driver uses the ao2 container's recursive mutex so doesn't
have this issue but since it also implements the common channelstorage API,
it needed its "get" implementations updated to take the lock parameter. They
just don't use it.

Resolves: #1578
2025-11-13 09:13:45 -07:00

455 lines
15 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2024, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@sangoma.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
#include "asterisk.h"
#include "asterisk/options.h"
#include "channelstorage.h"
static AST_VECTOR(, const struct ast_channelstorage_driver *) storage_drivers;
int ast_channelstorage_register_driver(
const struct ast_channelstorage_driver *driver_type)
{
if (storage_drivers.elems == NULL) {
AST_VECTOR_INIT(&storage_drivers, 10);
}
return AST_VECTOR_APPEND(&storage_drivers, driver_type);
}
const struct ast_channelstorage_driver *ast_channelstorage_get_driver(
const char *driver_name)
{
int i;
for (i = 0; i < AST_VECTOR_SIZE(&storage_drivers); i++) {
const struct ast_channelstorage_driver *dt =
AST_VECTOR_GET(&storage_drivers, i);
if (strcasecmp(driver_name, dt->driver_name) == 0) {
return dt;
}
}
return NULL;
}
struct ast_channelstorage_instance *ast_channelstorage_open(
const struct ast_channelstorage_driver *storage_driver,
const char *instance_name)
{
struct ast_channelstorage_instance *storage_instance = NULL;
storage_instance = storage_driver->open_instance(instance_name);
if (!storage_instance) {
ast_log(LOG_ERROR, "Failed to open channel storage driver '%s'\n",
storage_driver->driver_name);
return NULL;
}
return storage_instance;
};
void ast_channelstorage_close(struct ast_channelstorage_instance *storage_instance)
{
CHANNELSTORAGE_API(storage_instance, close_instance);
};
int channelstorage_exten_cb(void *obj, void *arg, void *data, int flags)
{
struct ast_channel *chan = (struct ast_channel *)obj;
const char *context = (const char *)arg;
const char *exten = (const char *)data;
int ret = 0;
ao2_lock(chan);
if (strcasecmp(ast_channel_context(chan), context) == 0 &&
strcasecmp(ast_channel_exten(chan), exten) == 0) {
ret = CMP_MATCH | ((flags & OBJ_MULTIPLE) ? 0 : CMP_STOP);
}
ao2_unlock(chan);
return ret;
}
struct ast_channel *channelstorage_by_exten(struct ast_channelstorage_instance *driver,
const char *exten, const char *context, int rdlock)
{
char *l_exten = (char *) exten;
char *l_context = (char *) context;
return CHANNELSTORAGE_API(driver, callback, channelstorage_exten_cb, l_context, l_exten, 0, rdlock);
}
int channelstorage_name_cb(void *obj, void *arg, void *data, int flags)
{
struct ast_channel *chan = obj;
const char *name = arg;
size_t name_len = *(size_t *) data;
int ret = 0;
if (name_len == 0) {
if(strcasecmp(ast_channel_name(chan), name) == 0) {
ret = CMP_MATCH | ((flags & OBJ_MULTIPLE) ? 0 : CMP_STOP);
}
} else {
if (strncasecmp(ast_channel_name(chan), name, name_len) == 0) {
ret = CMP_MATCH | ((flags & OBJ_MULTIPLE) ? 0 : CMP_STOP);
}
}
return ret;
}
struct ast_channel *channelstorage_by_name_or_uniqueid(struct ast_channelstorage_instance *driver,
const char *name, int rdlock)
{
return CHANNELSTORAGE_API(driver, get_by_name_prefix_or_uniqueid, name, 0, rdlock);
}
struct ast_channel *channelstorage_by_name_prefix_or_uniqueid(struct ast_channelstorage_instance *driver,
const char *name, size_t name_len, int rdlock)
{
struct ast_channel *chan = NULL;
chan = CHANNELSTORAGE_API(driver, get_by_name_prefix, name, name_len, rdlock);
if (chan) {
return chan;
}
if (name_len == 0) {
chan = CHANNELSTORAGE_API(driver, get_by_uniqueid, name, rdlock);
}
return chan;
}
int channelstorage_uniqueid_cb(void *obj, void *arg, void *data, int flags)
{
struct ast_channel *chan = obj;
char *uniqueid = arg;
int ret = 0;
if(strcasecmp(ast_channel_uniqueid(chan), uniqueid) == 0) {
ret = CMP_MATCH | CMP_STOP;
}
return ret;
}
struct ast_channel *channelstorage_by_uniqueid(struct ast_channelstorage_instance *driver,
const char *uniqueid, int rdlock)
{
return CHANNELSTORAGE_API(driver, callback, channelstorage_uniqueid_cb, (char *)uniqueid, NULL, 0, rdlock);
}
#ifdef TEST_FRAMEWORK
#include "asterisk/test.h"
#include "channel_private.h"
static void mock_channel_destructor(void *obj)
{
struct ast_channel *chan = obj;
ast_string_field_free_memory(chan);
}
struct test_info {
struct ast_test *test;
struct ast_channelstorage_instance *storage_instance;
enum ast_test_result_state res;
};
static void *test_storage_thread(void *data)
{
struct test_info *test_info = data;
struct ast_test *test = test_info->test;
struct ast_channelstorage_instance *storage_instance = test_info->storage_instance;
struct ast_channel *mock_channel;
enum ast_test_result_state res = AST_TEST_PASS;
int i;
struct timeval start;
struct timeval end;
int64_t elapsed;
char search1[128];
char search2[128];
int rc = 0;
long int rand = ast_random();
struct ast_channel_iterator *iter;
int collen = 25;
int CHANNEL_COUNT = 500;
struct ast_cli_args *cli_args = ast_test_get_cli_args(test);
struct ast_channel **test_channels;
for (i = 0; i < cli_args->argc; i++) {
if (ast_begins_with(cli_args->argv[i], "channel-count=")) {
sscanf(cli_args->argv[i], "channel-count=%d", &CHANNEL_COUNT);
}
}
test_channels = ast_calloc(CHANNEL_COUNT, sizeof(*test_channels));
ast_test_status_update(test, "%*s: %8d\n", collen, "Channel Count", CHANNEL_COUNT);
start = ast_tvnow();
for (i = 0; i < CHANNEL_COUNT; i++) {
test_channels[i] = ao2_alloc(sizeof(*mock_channel), mock_channel_destructor);
ast_test_validate_cleanup(test, test_channels[i], res, done);
ast_string_field_init(test_channels[i], 128);
ast_string_field_build(test_channels[i], name, "TestChannel-%ld-%04d-something", rand, i);
snprintf(test_channels[i]->context, AST_MAX_CONTEXT, "TestContext-%ld-%04d", rand, i % 100);
snprintf(test_channels[i]->exten, AST_MAX_EXTENSION, "TestExten-%ld-%04d", rand, i % 10);
snprintf(test_channels[i]->uniqueid.unique_id, AST_MAX_UNIQUEID, "TestUniqueid-%ld-%04d-something", rand, i);
rc = CHANNELSTORAGE_API(storage_instance, insert, test_channels[i], 0, 1);
ast_test_validate_cleanup_custom(test, rc == 0, res, done, "Unable to insert channel %s\n", test_channels[i]->name);
}
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
i = CHANNELSTORAGE_API(storage_instance, active_channels, 1);
ast_test_status_update(test, "%*s: %8ld\n", collen, "create channels", elapsed);
ast_test_validate_cleanup(test, i == CHANNEL_COUNT, res, done);
start = ast_tvnow();
for (i = 0; i < CHANNEL_COUNT; i++) {
sprintf(search1, "testchannel-%ld-%04d-something", rand, i);
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_name_prefix_or_uniqueid, search1, 0, 1);
ast_test_validate_cleanup(test, mock_channel, res, done);
ast_test_validate_cleanup(test, mock_channel == test_channels[i], res, done);
ast_test_validate_cleanup(test,
strcasecmp(ast_channel_name(mock_channel), search1) == 0, res, done);
ast_channel_unref(mock_channel);
}
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "by name exact", elapsed);
start = ast_tvnow();
for (i = 0; i < CHANNEL_COUNT; i++) {
sprintf(search1, "TestUniqueid-%ld-%04d-something", rand, i);
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_uniqueid, search1, 1);
ast_test_validate_cleanup(test, mock_channel, res, done);
ast_channel_unref(mock_channel);
}
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "by uniqueid exact", elapsed);
start = ast_tvnow();
for (i = 0; i < CHANNEL_COUNT; i++) {
sprintf(search1, "TestUniqueid-%ld-%04d-something", rand, i);
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_name_prefix_or_uniqueid, search1, 0, 1);
ast_test_validate_cleanup(test, mock_channel, res, done);
ast_channel_unref(mock_channel);
}
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "by uniqueid via nm", elapsed);
start = ast_tvnow();
for (i = 0; i < CHANNEL_COUNT; i++) {
sprintf(search1, "TestChannel-%ld-%04d", rand, i);
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_name_prefix_or_uniqueid, search1, strlen(search1), 1);
ast_test_validate_cleanup(test, mock_channel, res, done);
ast_channel_unref(mock_channel);
}
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "by name prefix", elapsed);
start = ast_tvnow();
for (i = 0; i < CHANNEL_COUNT; i++) {
sprintf(search1, "TestContext-%ld-%04d", rand, i % 100);
sprintf(search2, "TestExten-%ld-%04d", rand, i % 10);
mock_channel = CHANNELSTORAGE_API(storage_instance, get_by_exten, search2, search1, 1);
ast_test_validate_cleanup(test, mock_channel, res, done);
ast_channel_unref(mock_channel);
}
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "by context/exten", elapsed);
i = 0;
start = ast_tvnow();
iter = CHANNELSTORAGE_API(storage_instance, iterator_all_new);
for (; (mock_channel = CHANNELSTORAGE_API(storage_instance, iterator_next, iter));
ast_channel_unref(mock_channel)) {
i++;
}
CHANNELSTORAGE_API(storage_instance, iterator_destroy, iter);
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "iter all chan", elapsed);
ast_test_validate_cleanup_custom(test, i == CHANNEL_COUNT, res, done,
"Expected %d channels, got %d, in container: %d\n", CHANNEL_COUNT, i,
CHANNELSTORAGE_API(storage_instance, active_channels, 1));
i = 0;
start = ast_tvnow();
sprintf(search1, "TestChannel-%ld-%03d", rand, (CHANNEL_COUNT - 11) / 10);
iter = CHANNELSTORAGE_API(storage_instance, iterator_by_name_new, search1, strlen(search1));
ast_test_validate_cleanup(test, iter != NULL, res, done);
for (; (mock_channel = CHANNELSTORAGE_API(storage_instance, iterator_next, iter));
ast_channel_unref(mock_channel)) {
ast_test_validate_cleanup_custom(test, strncmp(search1,
ast_channel_name(mock_channel), strlen(search1)) == 0, res, done, "Expected %s got %s\n",
search1, ast_channel_name(mock_channel));
i++;
}
CHANNELSTORAGE_API(storage_instance, iterator_destroy, iter);
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "iter 10 partial name", elapsed);
ast_test_validate_cleanup_custom(test, i == 10, res, done,
"Expected %d channels, got %d, in container: %d\n", 10, i,
CHANNELSTORAGE_API(storage_instance, active_channels, 1));
i = 0;
start = ast_tvnow();
sprintf(search1, "TestContext-%ld-%04d", rand, 50);
sprintf(search2, "TestExten-%ld-%04d", rand, 0);
iter = CHANNELSTORAGE_API(storage_instance, iterator_by_exten_new, search2, search1);
ast_test_validate_cleanup(test, iter != NULL, res, done);
for (; (mock_channel = CHANNELSTORAGE_API(storage_instance, iterator_next, iter));
ast_channel_unref(mock_channel)) {
ast_test_validate_cleanup_custom(test,
(strcmp(search1, mock_channel->context) == 0 &&
strcmp(search2, mock_channel->exten) == 0), res, done, "Expected %s-%s got %s-%s\n",
search1, search2, mock_channel->context, mock_channel->exten);
i++;
}
CHANNELSTORAGE_API(storage_instance, iterator_destroy, iter);
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "iter context/exten", elapsed);
ast_test_validate_cleanup_custom(test, i == (CHANNEL_COUNT / 100), res, done,
"Expected %d channels, got %d, in container: %d\n", (CHANNEL_COUNT / 100), i,
CHANNEL_COUNT);
done:
CHANNELSTORAGE_API(storage_instance, unlock);
start = ast_tvnow();
for (i = 0; i < CHANNEL_COUNT; i++) {
if (test_channels[i]) {
rc = CHANNELSTORAGE_API(storage_instance, remove, test_channels[i], 0);
ast_channel_unref(test_channels[i]);
test_channels[i] = NULL;
}
}
end = ast_tvnow();
elapsed = ast_tvdiff_us(end, start);
ast_test_status_update(test, "%*s: %8ld\n", collen, "del all channels", elapsed);
ast_test_validate_cleanup(test, i == CHANNEL_COUNT, res, done);
rc = CHANNELSTORAGE_API(storage_instance, active_channels, 1);
ast_test_validate_cleanup_custom(test, rc == 0, res, final,
"There are still %d channels in the container\n", rc);
test_info->res = res;
return NULL;
final:
iter = CHANNELSTORAGE_API(storage_instance, iterator_all_new);
for (; (mock_channel = CHANNELSTORAGE_API(storage_instance, iterator_next, iter));
ast_channel_unref(mock_channel)) {
ast_test_status_update(test, "%p %s\n", mock_channel, ast_channel_name(mock_channel));
i++;
}
CHANNELSTORAGE_API(storage_instance, iterator_destroy, iter);
test_info->res = res;
return NULL;
}
static enum ast_test_result_state test_storage(struct ast_test_info *info,
enum ast_test_command cmd, struct ast_test *test,
const char *storage_name, const char *summary)
{
const struct ast_channelstorage_driver *storage_driver;
struct test_info ti = {
.test = test,
.storage_instance = NULL,
.res = AST_TEST_PASS,
};
pthread_t thread;
int rc = 0;
switch (cmd) {
case TEST_INIT:
info->name = storage_name;
info->category = "/main/channelstorage/";
info->summary = summary;
info->description = info->summary;
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
storage_driver = ast_channelstorage_get_driver(info->name);
if (!storage_driver) {
ast_test_status_update(test, "Storage driver %s not registered\n", info->name);
return AST_TEST_NOT_RUN;
}
ti.storage_instance = ast_channelstorage_open(storage_driver, "channels_test");
ast_test_validate(test, ti.storage_instance, res);
rc = ast_pthread_create(&thread, NULL, test_storage_thread, &ti);
if (rc) {
ast_channelstorage_close(ti.storage_instance);
ast_test_status_update(test, "Failed to create thread: %s\n", strerror(rc));
return AST_TEST_FAIL;
}
pthread_join(thread, NULL);
ast_channelstorage_close(ti.storage_instance);
return ti.res;
}
#define DEFINE_STORAGE_TEST(_name) \
AST_TEST_DEFINE(_name) \
{ \
return test_storage(info, cmd, test, #_name, "Channel Storage test for " #_name); \
}
DEFINE_STORAGE_TEST(ao2_legacy)
DEFINE_STORAGE_TEST(cpp_map_name_id)
#define REGISTER_STORAGE_TEST(_name) \
({ \
if (ast_channelstorage_get_driver(#_name)) { \
AST_TEST_REGISTER(_name); \
} \
})
#endif
static void channelstorage_shutdown(void)
{
#ifdef TEST_FRAMEWORK
/* Unregistering a test that wasn't previously registered is safe */
AST_TEST_UNREGISTER(cpp_map_name_id);
AST_TEST_UNREGISTER(ao2_legacy);
#endif
}
int ast_channelstorage_init(void)
{
#ifdef TEST_FRAMEWORK
/* Tests run in the reverse order registered */
REGISTER_STORAGE_TEST(cpp_map_name_id);
AST_TEST_REGISTER(ao2_legacy);
#endif
ast_register_cleanup(channelstorage_shutdown);
return 0;
}