diff --git a/main/manager.c b/main/manager.c
index c5a48fd4cb..650c632d1d 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -68,6 +68,7 @@
#include "asterisk/module.h"
#include "asterisk/config.h"
#include "asterisk/callerid.h"
+#include "asterisk/core_local.h"
#include "asterisk/lock.h"
#include "asterisk/cli.h"
#include "asterisk/app.h"
@@ -1041,6 +1042,33 @@
+
+
+ Raised at the end of the CoreShowChannelMap list produced by the CoreShowChannelMap command.
+
+
+ Conveys the status of the command response list
+
+
+ The total number of list items produced
+
+
+
+
+
+
+ List all channels connected to the specified channel.
+
+
+
+ The channel to get the mapping for. Requires a channel name.
+
+
+
+ List all channels currently connected to the specified channel. This can be any channel, including
+ Local channels, and Local channels will be followed through to their other half.
+
+
Reload and rotate the Asterisk logger.
@@ -6873,6 +6901,180 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
return 0;
}
+/*! \brief Helper function to add a channel name to the vector */
+static int coreshowchannelmap_add_to_map(struct ao2_container *c, const char *s)
+{
+ char *str;
+
+ str = ast_strdup(s);
+ if (!str) {
+ ast_log(LOG_ERROR, "Unable to append channel to channel map\n");
+ return 1;
+ }
+
+ /* If this is a duplicate, it will be ignored */
+ ast_str_container_add(c, str);
+
+ return 0;
+}
+
+/*! \brief Recursive function to get all channels in a bridge. Follow local channels as well */
+static int coreshowchannelmap_add_connected_channels(struct ao2_container *channel_map,
+ struct ast_channel_snapshot *channel_snapshot, struct ast_bridge_snapshot *bridge_snapshot)
+{
+ int res = 0;
+ struct ao2_iterator iter;
+ char *current_channel_uid;
+
+ iter = ao2_iterator_init(bridge_snapshot->channels, 0);
+ while ((current_channel_uid = ao2_iterator_next(&iter))) {
+ struct ast_channel_snapshot *current_channel_snapshot;
+ int add_channel_res;
+
+ /* Don't add the original channel to the list - it's either already in there,
+ * or it's the channel we want the map for */
+ if (!strcmp(current_channel_uid, channel_snapshot->base->uniqueid)) {
+ ao2_ref(current_channel_uid, -1);
+ continue;
+ }
+
+ current_channel_snapshot = ast_channel_snapshot_get_latest(current_channel_uid);
+ if (!current_channel_snapshot) {
+ ast_debug(5, "Unable to get channel snapshot\n");
+ ao2_ref(current_channel_uid, -1);
+ continue;
+ }
+
+ add_channel_res = coreshowchannelmap_add_to_map(channel_map, current_channel_snapshot->base->name);
+ if (add_channel_res) {
+ res = 1;
+ ao2_ref(current_channel_snapshot, -1);
+ ao2_ref(current_channel_uid, -1);
+ break;
+ }
+
+ /* If this is a local channel that we haven't seen yet, let's go ahead and find out what else is connected to it */
+ if (ast_begins_with(current_channel_snapshot->base->name, "Local")) {
+ struct ast_channel_snapshot *other_local_snapshot;
+ struct ast_bridge_snapshot *other_bridge_snapshot;
+ int size = strlen(current_channel_snapshot->base->name);
+ char other_local[size + 1];
+
+ /* Don't copy the trailing number - set it to 1 or 2, whichever one it currently is not */
+ ast_copy_string(other_local, current_channel_snapshot->base->name, size);
+ other_local[size - 1] = ast_ends_with(current_channel_snapshot->base->name, "1") ? '2' : '1';
+ other_local[size] = '\0';
+
+ other_local_snapshot = ast_channel_snapshot_get_latest_by_name(other_local);
+ if (!other_local_snapshot) {
+ ast_debug(5, "Unable to get other local channel snapshot\n");
+ ao2_ref(current_channel_snapshot, -1);
+ ao2_ref(current_channel_uid, -1);
+ continue;
+ }
+
+ if (coreshowchannelmap_add_to_map(channel_map, other_local_snapshot->base->name)) {
+ res = 1;
+ ao2_ref(current_channel_snapshot, -1);
+ ao2_ref(current_channel_uid, -1);
+ ao2_ref(other_local_snapshot, -1);
+ break;
+ }
+
+ other_bridge_snapshot = ast_bridge_get_snapshot_by_uniqueid(other_local_snapshot->bridge->id);
+ if (other_bridge_snapshot) {
+ res = coreshowchannelmap_add_connected_channels(channel_map, other_local_snapshot, other_bridge_snapshot);
+ }
+
+ ao2_ref(current_channel_snapshot, -1);
+ ao2_ref(current_channel_uid, -1);
+ ao2_ref(other_local_snapshot, -1);
+ ao2_ref(other_bridge_snapshot, -1);
+
+ if (res) {
+ break;
+ }
+ }
+ }
+ ao2_iterator_destroy(&iter);
+
+ return res;
+}
+
+/*! \brief Manager command "CoreShowChannelMap" - Lists all channels connected to
+ * the specified channel. */
+static int action_coreshowchannelmap(struct mansession *s, const struct message *m)
+{
+ const char *actionid = astman_get_header(m, "ActionID");
+ const char *channel_name = astman_get_header(m, "Channel");
+ char *current_channel_name;
+ char id_text[256];
+ int total = 0;
+ struct ao2_container *channel_map;
+ struct ao2_iterator i;
+ RAII_VAR(struct ast_bridge_snapshot *, bridge_snapshot, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel_snapshot *, channel_snapshot, NULL, ao2_cleanup);
+
+ if (!ast_strlen_zero(actionid)) {
+ snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid);
+ } else {
+ id_text[0] = '\0';
+ }
+
+ if (ast_strlen_zero(channel_name)) {
+ astman_send_error(s, m, "CoreShowChannelMap requires a channel.\n");
+ return 0;
+ }
+
+ channel_snapshot = ast_channel_snapshot_get_latest_by_name(channel_name);
+ if (!channel_snapshot) {
+ astman_send_error(s, m, "Could not get channel snapshot\n");
+ return 0;
+ }
+
+ bridge_snapshot = ast_bridge_get_snapshot_by_uniqueid(channel_snapshot->bridge->id);
+ if (!bridge_snapshot) {
+ astman_send_listack(s, m, "Channel map will follow", "start");
+ astman_send_list_complete_start(s, m, "CoreShowChannelMapComplete", 0);
+ astman_send_list_complete_end(s);
+ return 0;
+ }
+
+ channel_map = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK | AO2_CONTAINER_ALLOC_OPT_DUPS_OBJ_REJECT, 1);
+ if (!channel_map) {
+ astman_send_error(s, m, "Could not create channel map\n");
+ return 0;
+ }
+
+ astman_send_listack(s, m, "Channel map will follow", "start");
+
+ if (coreshowchannelmap_add_connected_channels(channel_map, channel_snapshot, bridge_snapshot)) {
+ astman_send_error(s, m, "Could not complete channel map\n");
+ ao2_ref(channel_map, -1);
+ return 0;
+ }
+
+ i = ao2_iterator_init(channel_map, 0);
+ while ((current_channel_name = ao2_iterator_next(&i))) {
+ astman_append(s,
+ "Event: CoreShowChannelMap\r\n"
+ "%s"
+ "Channel: %s\r\n"
+ "ConnectedChannel: %s\r\n\n",
+ id_text,
+ channel_name,
+ current_channel_name);
+ total++;
+ }
+ ao2_iterator_destroy(&i);
+
+ ao2_ref(channel_map, -1);
+ astman_send_list_complete_start(s, m, "CoreShowChannelMapComplete", total);
+ astman_send_list_complete_end(s);
+
+ return 0;
+}
+
/*! \brief Manager command "LoggerRotate" - reloads and rotates the logger in
* the same manner as the CLI command 'logger rotate'. */
static int action_loggerrotate(struct mansession *s, const struct message *m)
@@ -9450,6 +9652,7 @@ static void manager_shutdown(void)
ast_manager_unregister("Reload");
ast_manager_unregister("LoggerRotate");
ast_manager_unregister("CoreShowChannels");
+ ast_manager_unregister("CoreShowChannelMap");
ast_manager_unregister("ModuleLoad");
ast_manager_unregister("ModuleCheck");
ast_manager_unregister("AOCMessage");
@@ -9665,6 +9868,7 @@ static int __init_manager(int reload, int by_external_config)
ast_manager_register_xml_core("Reload", EVENT_FLAG_CONFIG | EVENT_FLAG_SYSTEM, action_reload);
ast_manager_register_xml_core("LoggerRotate", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_loggerrotate);
ast_manager_register_xml_core("CoreShowChannels", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_coreshowchannels);
+ ast_manager_register_xml_core("CoreShowChannelMap", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_coreshowchannelmap);
ast_manager_register_xml_core("ModuleLoad", EVENT_FLAG_SYSTEM, manager_moduleload);
ast_manager_register_xml_core("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck);
ast_manager_register_xml_core("AOCMessage", EVENT_FLAG_AOC, action_aocmessage);