[Asterisk-code-review] func_groupcount.c: Adding Group Variables and additional Group functions (asterisk[18])
Mark Murawski
asteriskteam at digium.com
Mon Dec 13 19:13:01 CST 2021
Mark Murawski has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/17655 )
Change subject: func_groupcount.c: Adding Group Variables and additional Group functions
......................................................................
func_groupcount.c: Adding Group Variables and additional Group functions
DumpGroups
-------------------
* New application. This will dump all channel group membership and associated
variables
Groups and Group Variables
------------------
* Group variables can be set on a group once the group is created
When a group is destroyed, all variables on that group are also destroyed
A group variable is somewhat like a global variable on a per-group basis.
GroupSet - Adds functionality to the manager to be able to set a GROUP()
on a channel.
GroupsShowChannels - Show each channel and it's associated groups
(a channel will be repeated for each group at category
it's a member of)
GroupsShowVariables - Show variables in each group at category, one event per
group, all variables are contained in each
group at category event
GroupVarSet - Set a group variable (the group must already exist)
GroupVarGet - Get a group variable
* New Manager events:
------------------
GroupCreate - Event is fired any time a group is made,
ie: Set(GROUP=x) or Set(GROUP()=x at y).
This event is only sent on when a channel is added to a group
that did not exist previously.
GroupChannelAdd - Event is fired any time a channel is added to a group
GroupChannelRemove - Event is fired any time a channel is removed from a
group
GroupDestroy - Event is fired when there are no longer any channels assigned
to the group
GroupVarSet - Event is fired when any group variable is changed
* New CLI Command
---------------
group show variables
* New Application
---------------
DumpGroups() - Show groups and group assigments (similar to DumpChan)
ASTERISK-15439 #close
Change-Id: I23e48d1cdfc8adaffdfec2e936e56143603914f2
---
M apps/app_dial.c
M funcs/func_groupcount.c
M include/asterisk/app.h
M include/asterisk/channel.h
M main/app.c
M main/cli.c
6 files changed, 1,408 insertions(+), 38 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/55/17655/1
diff --git a/apps/app_dial.c b/apps/app_dial.c
index f073af3..688773b 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -2740,7 +2740,7 @@
/* If we have an outbound group, set this peer channel to it */
if (outbound_group)
- ast_app_group_set_channel(tc, outbound_group);
+ ast_app_group_set_channel(tc, outbound_group, 0);
/* If the calling channel has the ANSWERED_ELSEWHERE flag set, inherit it. This is to support local channels */
if (ast_channel_hangupcause(chan) == AST_CAUSE_ANSWERED_ELSEWHERE)
ast_channel_hangupcause_set(tc, AST_CAUSE_ANSWERED_ELSEWHERE);
diff --git a/funcs/func_groupcount.c b/funcs/func_groupcount.c
index f6dd5c6..6d63c42 100644
--- a/funcs/func_groupcount.c
+++ b/funcs/func_groupcount.c
@@ -26,12 +26,16 @@
***/
#include "asterisk.h"
+#include "regex.h"
#include "asterisk/module.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/utils.h"
#include "asterisk/app.h"
+#include "asterisk/manager.h"
+#include "asterisk/strings.h"
+#include "asterisk/cli.h"
/*** DOCUMENTATION
<function name="GROUP_COUNT" language="en_US">
@@ -77,6 +81,13 @@
<parameter name="category">
<para>Category name.</para>
</parameter>
+ <parameter name="options">
+ <optionlist>
+ <option name="i">
+ <para>Inherit. Group membership will be kept when a channel is transferred.</para>
+ </option>
+ </optionlist>
+ </parameter>
</syntax>
<description>
<para><replaceable>category</replaceable> can be employed for more fine grained group management. Each channel
@@ -92,15 +103,174 @@
<para>Gets a list of the groups set on a channel.</para>
</description>
</function>
-
+ <manager name="GroupSet" language="en_US">
+ <synopsis>
+ Add channel group assignments
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="channel">
+ <para>Channel to operate on.</para>
+ </parameter>
+ <parameter name="group">
+ <para>Group name to set.</para>
+ </parameter>
+ <parameter name="category">
+ <para>Category name to set.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>For more information, see the dialplan function GROUP()</para>
+ </description>
+ </manager>
+ <manager name="GroupRemove" language="en_US">
+ <synopsis>
+ Remove channel group assignments
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="channel">
+ <para>Channel to operate on.</para>
+ </parameter>
+ <parameter name="group">
+ <para>Group name to remove.</para>
+ </parameter>
+ <parameter name="category">
+ <para>Category name to remove.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>For more information, see the dialplan function GROUP()</para>
+ </description>
+ </manager>
+ <application name="DumpGroups" language="en_US">
+ <synopsis>
+ Dump all group information to the console
+ </synopsis>
+ <description>
+ <para>When executed, this will show all group assignments and group variables
+ in the console</para>
+ </description>
+ </application>
+ <manager name="GroupVarGet" language="en_US">
+ <synopsis>
+ Get channel group variables.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Group" required="false" />
+ <parameter name="Category" required="false" />
+ <parameter name="Variable" required="true" />
+ </syntax>
+ <description>
+ At a minimum, either group or category must be provided.
+ For more information, see the dialplan function GROUP_VAR().
+ </description>
+ </manager>
+ <manager name="GroupVarSet" language="en_US">
+ <synopsis>
+ Set channel group variables.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ <parameter name="Group" required="false" />
+ <parameter name="Category" required="false" />
+ <parameter name="Variable" required="true" />
+ <parameter name="Value" required="false" />
+ </syntax>
+ <description>
+ At a minimum, either group or category must be provided.
+ For more information, see the dialplan function GROUP_VAR().
+ </description>
+ </manager>
+ <manager name="GroupsShow" language="en_US">
+ <synopsis>
+ Show channel groups.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ </syntax>
+ <description>
+ This will return a list of channel groups that are in use.
+ For more information, see the dialplan function GROUP().
+ </description>
+ </manager>
+ <manager name="GroupsShowChannels" language="en_US">
+ <synopsis>
+ Show group channel assignments.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ </syntax>
+ <description>
+ This will return a list the channels that are within each group.
+ For more information, see the dialplan function GROUP().
+ </description>
+ </manager>
+ <manager name="GroupsShowVariables" language="en_US">
+ <synopsis>
+ Show group channel variable assignments.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+ </syntax>
+ <description>
+ This will return a list of groups and the variables assigned in each group.
+ For more information, see the dialplan function GROUP_VAR().
+ </description>
+ </manager>
***/
+static void search_info_free(void *data);
+
+static const char *dumpgroups_app = "DumpGroups";
+
+static struct ast_datastore_info search_info = {
+ .type = "GROUP_SEARCH",
+ .destroy = search_info_free,
+};
+
+struct found_group {
+ AST_LIST_ENTRY(found_group) entries; /*!< Next group */
+ char category[MAX_CATEGORY_LEN]; /*!< Copy of group_meta, since the group may have gone away while reading results */
+ char group[MAX_GROUP_LEN];
+};
+
+AST_RWLIST_HEAD(found_groups_list, found_group);
+
+/* Datastore for search operations */
+struct search_data_store {
+ struct found_groups_list found_groups_head;
+};
+
+static void search_info_free(void *data)
+{
+ struct search_data_store *search_store = (struct search_data_store *) data;
+ struct found_groups_list *found_groups_list;
+ struct found_group *fg = NULL;
+
+ if (!data) {
+ return;
+ }
+
+ found_groups_list = &search_store->found_groups_head;
+
+ AST_RWLIST_WRLOCK(found_groups_list);
+ while ((fg = AST_LIST_REMOVE_HEAD(found_groups_list, entries))) {
+ ast_free(fg);
+ }
+ AST_RWLIST_UNLOCK(found_groups_list);
+
+ ast_free(data);
+};
+
+
static int group_count_function_read(struct ast_channel *chan, const char *cmd,
char *data, char *buf, size_t len)
{
int ret = -1;
int count = -1;
- char group[80] = "", category[80] = "";
+ char group[MAX_GROUP_LEN] = "", category[MAX_CATEGORY_LEN] = "";
if (!chan) {
ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
@@ -149,8 +319,8 @@
const char *cmd, char *data, char *buf,
size_t len)
{
- char group[80] = "";
- char category[80] = "";
+ char group[MAX_GROUP_LEN] = "";
+ char category[MAX_CATEGORY_LEN] = "";
ast_app_group_split_group(data, group, sizeof(group), category,
sizeof(category));
@@ -224,7 +394,7 @@
ast_copy_string(grpcat, value, sizeof(grpcat));
}
- if (ast_app_group_set_channel(chan, grpcat))
+ if (ast_app_group_set_channel(chan, grpcat, 0))
ast_log(LOG_WARNING,
"Setting a group requires an argument (group name)\n");
@@ -237,6 +407,341 @@
.write = group_function_write,
};
+
+/* GROUP_VAR and related */
+
+static int group_var_function_read(struct ast_channel *chan, const char *cmd,
+ char *data, char *buf, size_t len)
+{
+ char group[MAX_GROUP_LEN] = "";
+ char category[MAX_CATEGORY_LEN] = "";
+ const char *variable_value;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(groupcategory);
+ AST_APP_ARG(varname);
+ );
+ AST_STANDARD_APP_ARGS(args, data);
+
+ buf[0] = 0;
+
+ if (ast_strlen_zero(args.groupcategory) || ast_strlen_zero(args.varname)) {
+ ast_log(LOG_WARNING, "Syntax GROUP_VAR(group[@category],<varname>)\n");
+ return -1;
+ }
+
+ if (ast_app_group_split_group(args.groupcategory, group, sizeof(group), category, sizeof(category)))
+ return -1;
+
+ variable_value = ast_app_group_get_var(group, category, args.varname);
+
+ if (variable_value) {
+ ast_copy_string(buf, variable_value, len);
+ }
+
+ return 0;
+}
+
+static int group_var_function_write(struct ast_channel *chan, const char *cmd,
+ char *data, const char *value)
+{
+ char group[MAX_GROUP_LEN] = "";
+ char category[MAX_CATEGORY_LEN] = "";
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(groupcategory);
+ AST_APP_ARG(varname);
+ );
+ AST_STANDARD_APP_ARGS(args, data);
+
+ if (!value) {
+ value = "";
+ }
+
+ if (ast_strlen_zero(args.groupcategory) || ast_strlen_zero(args.varname)) {
+ ast_log(LOG_WARNING, "Syntax GROUP_VAR(group[@category],<varname>)=<value>)\n");
+ return -1;
+ }
+
+ ast_app_group_split_group(args.groupcategory, group, sizeof(group), category, sizeof(category));
+ ast_app_group_set_var(chan, group, category, args.varname, value);
+
+ return 0;
+}
+
+static int manager_group_set(struct mansession *s, const struct message *m)
+{
+ const char *channel = astman_get_header(m, "Channel");
+ const char *group = astman_get_header(m, "Group");
+ const char *category = astman_get_header(m, "Category");
+
+ char *group_category;
+ struct ast_channel *chan = NULL;
+
+ if (ast_strlen_zero(channel)) {
+ astman_send_error(s, m, "Channel not specified.");
+ return 0;
+ }
+
+ chan = ast_channel_get_by_name(channel);
+ if (!chan) {
+ astman_send_error(s, m, "Channel not found.");
+ return 0;
+ }
+
+ if (!group) {
+ group = "";
+ }
+
+ if (!category) {
+ category = "";
+ }
+
+ group_category = ast_malloc(strlen(group) + strlen(category) + 2); /* 1 for @, 1 for NULL*/
+ sprintf(group_category, "%s@%s", group, category);
+
+ if (ast_app_group_set_channel(chan, group_category, 0) == 0) {
+ astman_send_ack(s, m, "Group Set");
+ ast_channel_unref(chan);
+ return 0;
+ }
+
+ astman_send_error(s, m, "Group set failed.");
+ ast_channel_unref(chan);
+ return -1;
+}
+
+static int manager_group_remove(struct mansession *s, const struct message *m)
+{
+ const char *group = astman_get_header(m, "Group");
+ const char *category = astman_get_header(m, "Category");
+
+ if (ast_app_group_remove_all_channels(group, category) == 0) {
+ astman_send_ack(s, m, "Group Removed");
+ return 0;
+ }
+
+ astman_send_error(s, m, "Group remove failed.");
+ return -1;
+}
+
+static int manager_group_var_get(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+ const char *group = astman_get_header(m, "Group");
+ const char *category = astman_get_header(m, "Category");
+ const char *variable = astman_get_header(m, "Variable");
+ const char *variable_value;
+ char idText[256] = "";
+
+ if (ast_strlen_zero(group)) {
+ astman_send_error(s, m, "No group specified.");
+ return 0;
+ }
+
+ if (ast_strlen_zero(variable)) {
+ astman_send_error(s, m, "No variable specified.");
+ return 0;
+ }
+
+ if (!ast_strlen_zero(id))
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+
+ if (!category) {
+ category = "";
+ }
+
+ variable_value = ast_app_group_get_var(group, category, variable);
+
+ if (!variable_value) {
+ astman_send_error(s, m, "Group variable not found");
+ return 0;
+ }
+
+ astman_send_ack(s, m, "Result will follow");
+ astman_append(s, "Event: GroupVarGetResponse\r\n"
+ "Group: %s\r\n"
+ "Category: %s\r\n"
+ "Variable: %s\r\n"
+ "Value: %s\r\n"
+ "%s"
+ "\r\n",
+ group, category, variable, variable_value, idText);
+
+ return 0;
+}
+
+static int manager_group_var_set(struct mansession *s, const struct message *m)
+{
+ const char *group = astman_get_header(m, "Group");
+ const char *category = astman_get_header(m, "Category");
+ const char *variable = astman_get_header(m, "Variable");
+ const char *value = astman_get_header(m, "Value");
+ int result = 0;
+
+ if (ast_strlen_zero(group)) {
+ astman_send_error(s, m, "No group specified.");
+ return 0;
+ }
+
+ if (ast_strlen_zero(variable)) {
+ astman_send_error(s, m, "No variable specified.");
+ return 0;
+ }
+
+ if (!category) {
+ category = "";
+ }
+
+ if (!value) {
+ value = "";
+ }
+
+ result = ast_app_group_set_var(NULL, group, category, variable, value);
+
+ if (!result) {
+ astman_send_error(s, m, "Variable set failed (group doesn't exist)");
+ return 0;
+ }
+
+ astman_send_ack(s, m, "Variable Set");
+ return 0;
+}
+
+static int manager_groups_show(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+// const char *group = astman_get_header(m, "Group"); // todo: filtering
+// const char *category = astman_get_header(m, "Category"); // todo: filtering
+ struct ast_group_meta *gmi = NULL;
+ char idText[256] = "";
+ int groups = 0;
+
+ if (!ast_strlen_zero(id)) {
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+ }
+
+ astman_send_listack(s, m, "Groups will follow", "start");
+
+ ast_app_group_meta_rdlock();
+ for (gmi = ast_app_group_meta_head(); gmi; gmi = AST_LIST_NEXT(gmi, group_meta_list)) {
+ astman_append(s,
+ "Event: GroupsShow\r\n"
+ "Group: %s\r\n"
+ "Category: %s\r\n"
+ "%s"
+ "\r\n", gmi->group, gmi->category, idText);
+
+ groups++;
+ }
+ ast_app_group_meta_unlock();
+
+ astman_append(s,
+ "Event: GroupsShowComplete\r\n"
+ "EventList: Complete\r\n"
+ "ListItems: %d\r\n"
+ "%s"
+ "\r\n", groups, idText);
+
+ return 0;
+}
+
+static int manager_groups_show_channels(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+// const char *group = astman_get_header(m, "Group"); // todo: filtering
+// const char *category = astman_get_header(m, "Category"); // todo: filtering
+ struct ast_group_info *gi = NULL;
+ char idText[256] = "";
+ int channels = 0;
+
+ if (!ast_strlen_zero(id)) {
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+ }
+
+ astman_send_listack(s, m, "Group channels will follow", "start");
+
+ ast_app_group_list_rdlock();
+ for (gi = ast_app_group_list_head(); gi; gi = AST_LIST_NEXT(gi, group_list)) {
+ astman_append(s,
+ "Event: GroupsShowChannels\r\n"
+ "Group: %s\r\n"
+ "Category: %s\r\n"
+ "Channel: %s\r\n"
+ "%s"
+ "\r\n", gi->group, gi->category, ast_channel_name(gi->chan), idText);
+
+ channels++;
+ }
+ ast_app_group_list_unlock();
+
+ astman_append(s,
+ "Event: GroupsShowChannelsComplete\r\n"
+ "EventList: Complete\r\n"
+ "ListItems: %d\r\n"
+ "%s"
+ "\r\n", channels, idText);
+
+ return 0;
+}
+
+static int manager_groups_show_variables(struct mansession *s, const struct message *m)
+{
+ const char *id = astman_get_header(m, "ActionID");
+ struct ast_group_meta *gmi = NULL;
+ char action_id[256] = "";
+ int groups = 0;
+
+ struct varshead *headp;
+ struct ast_var_t *vardata;
+ struct ast_str *variables = ast_str_create(100);
+
+ if (!ast_strlen_zero(id)) {
+ snprintf(action_id, sizeof(action_id), "ActionID: %s\r\n", id);
+}
+
+ astman_send_listack(s, m, "Group variables will follow", "start");
+
+ ast_app_group_meta_rdlock();
+ for (gmi = ast_app_group_meta_head(); gmi; gmi = AST_LIST_NEXT(gmi, group_meta_list)) {
+ headp = &gmi->varshead;
+ ast_str_reset(variables);
+
+ AST_LIST_TRAVERSE(headp, vardata, entries) {
+ ast_str_append(&variables, 0, "Variable(%s): %s\r\n", ast_var_name(vardata), ast_var_value(vardata));
+ }
+
+ astman_append(s,
+ "Event: GroupsShowVariables\r\n"
+ "Group: %s\r\n"
+ "Category: %s\r\n"
+ "%s"
+ "%s"
+ "\r\n", gmi->group, (gmi->category ? gmi->category : ""), action_id, ast_str_buffer((variables)));
+
+
+ groups++;
+ }
+ ast_app_group_meta_unlock();
+
+ astman_append(s,
+ "Event: GroupsShowVariablesComplete\r\n"
+ "EventList: Complete\r\n"
+ "ListItems: %d\r\n"
+ "%s"
+ "\r\n", groups, action_id);
+
+ ast_free(variables);
+ return 0;
+}
+
+static struct ast_custom_function group_var_function = {
+ .name = "GROUP_VAR",
+ .syntax = "GROUP_VAR(groupname[@category],var)",
+ .synopsis = "Gets or sets a channel group variable.",
+ .desc = "Gets or sets a channel group variable.\n",
+ .read = group_var_function_read,
+ .write = group_var_function_write,
+};
+
static int group_list_function_read(struct ast_channel *chan, const char *cmd,
char *data, char *buf, size_t len)
{
@@ -273,20 +778,321 @@
return 0;
}
+static int group_list_function_start_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_datastore *data_store = ast_channel_datastore_find(chan, &search_info, NULL);
+ struct search_data_store *search_store;
+ struct found_groups_list *found_groups_list;
+ struct ast_group_meta *gmi = NULL;
+ struct found_group *fg = NULL;
+
+ char groupmatch[MAX_GROUP_LEN] = "";
+ char categorymatch[MAX_CATEGORY_LEN] = "";
+ int groups = 0;
+ char groups_ret[1024] = "";
+ regex_t regexbuf_group;
+ regex_t regexbuf_category;
+
+ buf[0] = '0'; buf[1] = 0;
+
+ if (!data_store) {
+ ast_debug(1, "Channel %s has no group search datastore, so we're allocating one.\n", ast_channel_name(chan));
+ data_store = ast_datastore_alloc(&search_info, NULL);
+
+ if (!data_store) {
+ ast_log(LOG_ERROR, "Unable to allocate new group search datastore. Group search will fail.\n");
+ return -1;
+ }
+
+ data_store->data = ast_calloc(1, sizeof(struct search_data_store));
+ if (!data_store->data) {
+ ast_log(LOG_ERROR, "Unable to allocate new group search datastore list head(s). Group search will fail.\n");
+ ast_datastore_free(data_store);
+ return -1;
+ }
+
+ ast_channel_datastore_add(chan, data_store);
+
+ search_store = (struct search_data_store *) data_store->data;
+ found_groups_list = &search_store->found_groups_head;
+ AST_RWLIST_HEAD_INIT(found_groups_list);
+ }
+ else {
+ search_store = (struct search_data_store *) data_store->data;
+ found_groups_list = &search_store->found_groups_head;
+ }
+
+ /* clear old search (if any) */
+ AST_RWLIST_WRLOCK(found_groups_list);
+ while ((fg = AST_LIST_REMOVE_HEAD(found_groups_list, entries))) {
+ ast_free(fg);
+ }
+ AST_RWLIST_UNLOCK(found_groups_list);
+
+ ast_app_group_split_group(data, groupmatch, sizeof(groupmatch), categorymatch, sizeof(categorymatch));
+
+ /* if regex compilation fails, return zero matches */
+ if (regcomp(®exbuf_group, groupmatch, REG_EXTENDED | REG_NOSUB)) {
+ ast_log(LOG_ERROR, "Regex compile failed on: %s\n", groupmatch);
+ return 0;
+ }
+
+ if (regcomp(®exbuf_category, categorymatch, REG_EXTENDED | REG_NOSUB)) {
+ ast_log(LOG_ERROR, "Regex compile failed on: %s\n", categorymatch);
+ regfree(®exbuf_group);
+ return 0;
+ }
+
+ /* Traverse all groups, and keep track of what we find */
+ ast_app_group_meta_rdlock();
+ for (gmi = ast_app_group_meta_head(); gmi; gmi = AST_LIST_NEXT(gmi, group_meta_list)) {
+ if (!regexec(®exbuf_group, gmi->group, 0, NULL, 0) && (ast_strlen_zero(categorymatch) || (!ast_strlen_zero(gmi->category) && !regexec(®exbuf_category, gmi->category, 0, NULL, 0)))) {
+ if (!(fg = ast_calloc(1, sizeof(struct found_group)))) {
+ ast_app_group_meta_unlock();
+ return -1;
+ }
+
+ strncpy(fg->category, gmi->category, sizeof(fg->category));
+ strncpy(fg->group, gmi->group, sizeof(fg->group));
+
+ AST_RWLIST_INSERT_TAIL(found_groups_list, fg, entries);
+ groups++;
+ }
+ }
+ ast_app_group_meta_unlock();
+
+ snprintf(groups_ret, sizeof(groups_ret), "%d", groups);
+ ast_copy_string(buf, groups_ret, len);
+
+ regfree(®exbuf_group);
+ regfree(®exbuf_category);
+
+ return 0;
+}
+
+static int group_list_function_next_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_datastore *data_store = ast_channel_datastore_find(chan, &search_info, NULL);
+ struct search_data_store *search_store;
+ struct found_groups_list *found_groups_list;
+ struct found_group *fg = NULL;
+ struct ast_str *foundgroup_str;
+
+ if (!data_store) {
+ ast_log(LOG_WARNING, "GROUP_MATCH_LIST_NEXT() called without starting a search using GROUP_MATCH_LIST_START()\n");
+ return -1;
+ }
+
+ search_store = (struct search_data_store *) data_store->data;
+ found_groups_list = &search_store->found_groups_head;
+
+ AST_RWLIST_WRLOCK(found_groups_list);
+
+ if (AST_LIST_EMPTY(found_groups_list)) {
+ ast_log(LOG_WARNING, "GROUP_MATCH_LIST_NEXT() called after the last item was already asked for\n");
+ *buf = 0;
+ AST_RWLIST_UNLOCK(found_groups_list);
+ return 0;
+ }
+
+ /* we don't need this item anymore after being read... another search will clear this list anyway */
+ fg = AST_LIST_REMOVE_HEAD(found_groups_list, entries);
+ AST_RWLIST_UNLOCK(found_groups_list);
+
+ /* Does this group still exist? */
+ if (ast_app_group_get_count(fg->group, fg->category) == 0) {
+ *buf = 0;
+ ast_free(fg);
+ return 0;
+ }
+
+ foundgroup_str = ast_str_create(MAX_GROUP_LEN + MAX_CATEGORY_LEN);
+ ast_str_append(&foundgroup_str, 0, "%s@%s", fg->group, fg->category);
+ ast_copy_string(buf, ast_str_buffer(foundgroup_str), len);
+
+ ast_free(fg);
+ ast_free(foundgroup_str);
+
+ return 0;
+}
+
+static int dumpgroups_exec(struct ast_channel *chan, const char *data)
+{
+#define FORMAT_STRING_CHANNELS "%-25s %-20s %-20s\n"
+#define FORMAT_STRING_GROUPS "%-20s %-20s\n"
+#define FORMAT_STRING_VAR " %s=%s\n"
+
+ static char *line = "================================================================================";
+ static char *thinline = "-----------------------------------";
+
+ struct ast_group_info *gi = NULL;
+ struct ast_group_meta *gmi = NULL;
+ struct varshead *headp;
+ struct ast_var_t *variable = NULL;
+ int numgroups = 0;
+ int numchans = 0;
+ struct ast_str *out = ast_str_create(4096);
+
+ ast_verbose("%s\n", line);
+ ast_verbose(FORMAT_STRING_CHANNELS, "Channel", "Group", "Category\n");
+
+ ast_app_group_list_rdlock();
+ gi = ast_app_group_list_head();
+ while (gi) {
+ ast_str_append(&out, 0, FORMAT_STRING_CHANNELS, ast_channel_name(gi->chan), gi->group, (ast_strlen_zero(gi->category) ? "(default)" : gi->category));
+ numchans++;
+ gi = AST_LIST_NEXT(gi, group_list);
+ }
+
+ ast_app_group_list_unlock();
+
+ ast_str_append(&out, 0, "%d active channel%s in groups\n", numchans, ESS(numchans));
+
+ ast_str_append(&out, 0, "%s\n", thinline);
+ /****************** Group Variables ******************/
+
+ ast_str_append(&out, 0, "Group Variables Category\n");
+
+ /* Print group variables */
+ ast_app_group_meta_rdlock();
+ gmi = ast_app_group_meta_head();
+ while (gmi) {
+ ast_str_append(&out, 0, FORMAT_STRING_GROUPS, gmi->group, (strcmp(gmi->category, "") ? gmi->category : "(Default)"));
+ numgroups++;
+ headp = &gmi->varshead;
+
+ AST_LIST_TRAVERSE(headp, variable, entries) {
+ ast_str_append(&out, 0, FORMAT_STRING_VAR, ast_var_name(variable), ast_var_value(variable));
+ }
+
+ gmi = AST_LIST_NEXT(gmi, group_meta_list);
+ }
+ ast_app_group_meta_unlock();
+
+ ast_str_append(&out, 0, "%s\n", line);
+
+ ast_verbose("%s\n", ast_str_buffer(out));
+
+ ast_free(out);
+
+ return 0;
+
+#undef FORMAT_STRING_CHANNELS
+#undef FORMAT_STRING_GROUPS
+#undef FORMAT_STRING_VAR
+}
+
+static struct ast_custom_function group_match_list_start_function = {
+ .name = "GROUP_MATCH_LIST_START",
+ .syntax = "GROUP_MATCH_LIST_START(group_regex[@category_regex])",
+ .synopsis =
+ "Start a find of groups by regular expression. Each call to GROUP_MATCH_LIST_NEXT() will return a matched group.\n"
+ "The return value is the number of groups found.\n"
+ "Note: the search is executed and stored at the time of calling this function.\n"
+ "If GROUP() assignments change during successive calls to _NEXT, you will need to call _START again to see the new groups\n"
+ "An empty string is returned to indicate the end of the list.\n"
+ "Uses standard regular expression matching (see regex(7)).",
+ .desc = "Find groups by regular expression search.\n",
+ .read = group_list_function_start_read,
+ .write = NULL,
+};
+
+static struct ast_custom_function group_match_list_next_function = {
+ .name = "GROUP_MATCH_LIST_NEXT",
+ .syntax = "GROUP_MATCH_LIST_NEXT()",
+ .synopsis =
+ "Get the next matched group from a GROUP_MATCH_LIST_START() search\n"
+ "An empty string is returned to indicate the end of the list",
+ .desc = "Find groups by regular expression search.\n",
+ .read = group_list_function_next_read,
+ .write = NULL,
+};
+
static struct ast_custom_function group_list_function = {
.name = "GROUP_LIST",
.read = group_list_function_read,
.write = NULL,
};
+
+static int group_channel_list_function_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ char group[MAX_GROUP_LEN] = "";
+ char category[MAX_CATEGORY_LEN] = "";
+ struct ast_group_info *gi = NULL;
+ struct ast_str *out = ast_str_create(1024);
+ int out_len = 0;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(groupcategory);
+ );
+ AST_STANDARD_APP_ARGS(args, data);
+
+ buf[0] = 0;
+
+ if (ast_strlen_zero(args.groupcategory)) {
+ ast_log(LOG_WARNING, "Syntax GROUP_CHANNEL_LIST(group[@category])\n");
+ return -1;
+ }
+
+ if (ast_app_group_split_group(args.groupcategory, group, sizeof(group), category, sizeof(category))) {
+ return -1;
+ }
+
+ ast_app_group_list_rdlock();
+ gi = ast_app_group_list_head();
+ while (gi) {
+ if (!strcasecmp(gi->group, group) && (ast_strlen_zero(category) || (!ast_strlen_zero(gi->category) && !strcasecmp(gi->category, category)))) {
+ ast_str_append(&out, 0, "%s,", ast_channel_name(gi->chan));
+ }
+
+ gi = AST_LIST_NEXT(gi, group_list);
+ }
+ ast_app_group_list_unlock();
+
+ out_len = strlen(ast_str_buffer(out));
+
+ if (out_len) {
+ if (out_len <= len) {
+ len -= 1; /* rid , only if we didn't go over the buffer limit */
+ }
+
+ strncpy(buf, ast_str_buffer(out), len);
+ }
+
+ ast_free(out);
+
+ return 0;
+}
+
+static struct ast_custom_function group_channel_list_function = {
+ .name = "GROUP_CHANNEL_LIST",
+ .syntax = "GROUP_CHANNEL_LIST(group[@category])",
+ .synopsis = "Gets a comma delimited list of channels that are part of the given group at category.",
+ .desc = "Gets the list of channels in a group.\n",
+ .read = group_channel_list_function_read,
+};
+
static int unload_module(void)
{
int res = 0;
res |= ast_custom_function_unregister(&group_count_function);
res |= ast_custom_function_unregister(&group_match_count_function);
+ res |= ast_custom_function_unregister(&group_match_list_start_function);
+ res |= ast_custom_function_unregister(&group_match_list_next_function);
res |= ast_custom_function_unregister(&group_list_function);
+ res |= ast_custom_function_unregister(&group_var_function);
res |= ast_custom_function_unregister(&group_function);
+ res |= ast_custom_function_unregister(&group_channel_list_function);
+
+ res |= ast_unregister_application(dumpgroups_app);
+
+ res |= ast_manager_unregister("GroupVarGet");
+ res |= ast_manager_unregister("GroupVarSet");
+ res |= ast_manager_unregister("GroupsShow");
+ res |= ast_manager_unregister("GroupsShowChannels");
+ res |= ast_manager_unregister("GroupsShowVariables");
return res;
}
@@ -297,8 +1103,22 @@
res |= ast_custom_function_register(&group_count_function);
res |= ast_custom_function_register(&group_match_count_function);
+ res |= ast_custom_function_register(&group_match_list_start_function);
+ res |= ast_custom_function_register(&group_match_list_next_function);
res |= ast_custom_function_register(&group_list_function);
+ res |= ast_custom_function_register(&group_var_function);
res |= ast_custom_function_register(&group_function);
+ res |= ast_custom_function_register(&group_channel_list_function);
+
+ res |= ast_register_application_xml(dumpgroups_app, dumpgroups_exec);
+
+ res |= ast_manager_register_xml("GroupSet", EVENT_FLAG_CALL, manager_group_set);
+ res |= ast_manager_register_xml("GroupRemove", EVENT_FLAG_CALL, manager_group_remove);
+ res |= ast_manager_register_xml("GroupVarGet", EVENT_FLAG_CALL, manager_group_var_get);
+ res |= ast_manager_register_xml("GroupVarSet", EVENT_FLAG_CALL, manager_group_var_set);
+ res |= ast_manager_register_xml("GroupsShow", EVENT_FLAG_REPORTING, manager_groups_show);
+ res |= ast_manager_register_xml("GroupsShowChannels", EVENT_FLAG_REPORTING, manager_groups_show_channels);
+ res |= ast_manager_register_xml("GroupsShowVariables", EVENT_FLAG_REPORTING, manager_groups_show_variables);
return res;
}
diff --git a/include/asterisk/app.h b/include/asterisk/app.h
index 55b7386..2aa03a0 100644
--- a/include/asterisk/app.h
+++ b/include/asterisk/app.h
@@ -1217,8 +1217,26 @@
/*! \brief Split a group string into group and category, returning a default category if none is provided. */
int ast_app_group_split_group(const char *data, char *group, int group_max, char *category, int category_max);
+/*! \brief Remove a channel from a group meta assignment */
+int ast_app_group_remove_channel(struct ast_channel *chan, char *group, char *category);
+
+/*! \brief Add a channel to a group meta assignment, create a group meta item if it doesn't exist */
+int ast_app_group_add_channel(struct ast_channel *chan, char *group, char *category);
+
+/*! \brief Remove channel assignments for the specified group */
+int ast_app_group_remove_all_channels(const char *group, const char *category);
+
+/*! \brief Rename a group at category while retaining all the channel memberships */
+int ast_app_group_rename(const char *old_group, const char *old_category, const char *new_group, const char *new_category);
+
/*! \brief Set the group for a channel, splitting the provided data into group and category, if specified. */
-int ast_app_group_set_channel(struct ast_channel *chan, const char *data);
+int ast_app_group_set_channel(struct ast_channel *chan, const char *data, int inherit);
+
+/*! \brief Set a group variable for a group at category */
+int ast_app_group_set_var(struct ast_channel *chan, const char *group, const char *category, const char *name, const char *value);
+
+/*! \brief Get a group variable from a group at category */
+const char* ast_app_group_get_var(const char *group, const char *category, const char *name);
/*! \brief Get the current channel count of the specified group and category. */
int ast_app_group_get_count(const char *group, const char *category);
@@ -1244,6 +1262,18 @@
/*! \brief Unlock the group count list */
int ast_app_group_list_unlock(void);
+/*! \brief Write Lock the group meta list */
+int ast_app_group_meta_wrlock(void);
+
+/*! \brief Read Lock the group meta list */
+int ast_app_group_meta_rdlock(void);
+
+/*! \brief Get the head of the group meta list */
+struct ast_group_meta *ast_app_group_meta_head(void);
+
+/*! \brief Unlock the group meta list */
+int ast_app_group_meta_unlock(void);
+
/*!
\brief Define an application argument
\param name The name of the argument
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 59d25a8..b7d6b81 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -126,6 +126,7 @@
#include "asterisk/abstract_jb.h"
#include "asterisk/astobj2.h"
#include "asterisk/poll-compat.h"
+#include "regex.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
@@ -173,6 +174,9 @@
#define MAX_MUSICCLASS 80 /*!< Max length of the music class setting */
#define AST_MAX_USER_FIELD 256 /*!< Max length of the channel user field */
+#define MAX_GROUP_LEN 80 /*!< Max length of a channel group */
+#define MAX_CATEGORY_LEN 80 /*!< Max length of a channel group category */
+
#include "asterisk/frame.h"
#include "asterisk/chanvars.h"
#include "asterisk/config.h"
@@ -2919,6 +2923,19 @@
AST_LIST_ENTRY(ast_group_info) group_list;
};
+/*! \brief list of groups currently in use, with a pointer to a list of channels within the groups
+ */
+struct ast_group_meta {
+ int num_channels; /*!< number of channels in this group */
+ struct varshead varshead; /*!< A linked list for group variables. See \ref AstGroupVar */
+
+ AST_LIST_ENTRY(ast_group_info) channels_list; /*!< List of channels in this group */
+ AST_LIST_ENTRY(ast_group_meta) group_meta_list; /*!< Next group */
+
+ char category[MAX_CATEGORY_LEN];
+ char group[MAX_GROUP_LEN];
+};
+
#define ast_channel_lock(chan) ao2_lock(chan)
#define ast_channel_unlock(chan) ao2_unlock(chan)
#define ast_channel_trylock(chan) ao2_trylock(chan)
@@ -3133,6 +3150,35 @@
*/
struct ast_channel *ast_channel_get_by_exten(const char *exten, const char *context);
+/*!
+ * \brief Find a channel by a regex string
+ *
+ * \arg regex_string the regex pattern to search for
+ *
+ * Return a channel that where the regex pattern matches the channel name
+ *
+ * \retval a channel that matches the regex pattern
+ * \retval NULL if no channel was found or pattern is bad
+ *
+ * \since 11
+ */
+struct ast_channel *ast_channel_get_by_regex(const char *regex_string);
+
+/*!
+ * \brief Find a channel by a regex pattern
+ *
+ * \arg regex the compiled regex pattern to search for
+ *
+ * Return a channel that where the regex pattern matches the channel name
+ *
+ * \retval a channel that matches the regex pattern
+ * \retval NULL if no channel was found
+ *
+ * \since 11
+ */
+struct ast_channel *ast_channel_get_by_regex_compiled(regex_t *regex);
+
+
/*! @} End channel search functions. */
/*!
diff --git a/main/app.c b/main/app.c
index c20e370..fb3b34f 100644
--- a/main/app.c
+++ b/main/app.c
@@ -60,7 +60,9 @@
#include "asterisk/lock.h"
#include "asterisk/indications.h"
#include "asterisk/linkedlists.h"
+#include "asterisk/dlinkedlists.h"
#include "asterisk/threadstorage.h"
+#include "asterisk/manager.h"
#include "asterisk/test.h"
#include "asterisk/module.h"
#include "asterisk/astobj2.h"
@@ -118,10 +120,37 @@
return NULL;
}
+struct group_data_store;
+
+/* \brief a single group assignment entry */
+struct group_list_entry {
+ struct ast_str *group; /*!< A group assignment is group at category, this is the group part */
+ struct ast_str *category; /*!< This is the category part */
+ int transfer_fixup:1; /*!< Whether we will run a fixup on masquerade */
+
+ struct group_data_store *group_store; /*!< The group_data_store we live on */
+
+ AST_DLLIST_ENTRY(group_list_entry) entries; /*!< Next group */
+};
+
+AST_DLLIST_HEAD_NOLOCK(group_list, group_list_entry); /*!< All GROUP assignments */
+
+/* Datastore for GROUP() entry storage */
+struct group_data_store {
+ void *datastore; /*!< Pointer to the datastore that was allocated in */
+ struct ast_channel *chan; /*!< Channel this datastore is on */
+
+ struct group_list group_list_head; /*!< All the GROUPs that this channel is in */
+
+ AST_DLLIST_ENTRY(group_data_store) entries; /*!< Next GROUP datastore */
+};
+
+AST_RWDLLIST_HEAD_STATIC(group_stores_list, group_data_store);
#define AST_MAX_FORMATS 10
-static AST_RWLIST_HEAD_STATIC(groups, ast_group_info);
+static AST_RWLIST_HEAD_STATIC(groups, ast_group_info); /*!< List of channels that are in groups */
+static AST_RWLIST_HEAD_STATIC(groups_meta, ast_group_meta); /*!< List of groups and their metadata */
/*!
* \brief This function presents a dialtone and reads an extension into 'collect'
@@ -2077,10 +2106,10 @@
return res;
}
-int ast_app_group_set_channel(struct ast_channel *chan, const char *data)
+int ast_app_group_set_channel(struct ast_channel *chan, const char *data, int inherit)
{
int res = 0;
- char group[80] = "", category[80] = "";
+ char group[MAX_GROUP_LEN] = "", category[MAX_CATEGORY_LEN] = "";
struct ast_group_info *gi = NULL;
size_t len = 0;
@@ -2094,9 +2123,13 @@
len += strlen(category) + 1;
}
+ /* Remove previous group assignment within this category if there is one */
AST_RWLIST_WRLOCK(&groups);
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
if ((gi->chan == chan) && ((ast_strlen_zero(category) && ast_strlen_zero(gi->category)) || (!ast_strlen_zero(gi->category) && !strcasecmp(gi->category, category)))) {
+ /* Find our group meta data, remove the entire group metadata if we're the last channel */
+ ast_app_group_remove_channel(chan, gi->group, gi->category);
+
AST_RWLIST_REMOVE_CURRENT(group_list);
ast_free(gi);
break;
@@ -2115,6 +2148,8 @@
strcpy(gi->category, category);
}
AST_RWLIST_INSERT_TAIL(&groups, gi, group_list);
+
+ ast_app_group_add_channel(chan, group, category);
} else {
res = -1;
}
@@ -2124,24 +2159,149 @@
return res;
}
+/*!
+ * \brief Set a group variable for a group at category
+ *
+ * \param chan channel (if any) that is setting the group variable (can be NULL)
+ * \param group group to set the variable on (cannot be null)
+ * \param category category to set the variable on (cannot be null)
+ * \param name name of the variable to set (cannot be null)
+ * \param value value of the variable to set (cannot be null)
+ *
+ * \retval 1 On success
+ * \retval 0 On failure (group at category not found)
+ */
+int ast_app_group_set_var(struct ast_channel *chan, const char *group, const char *category, const char *name, const char *value) {
+ struct ast_group_meta *gmi = NULL;
+
+ struct varshead *headp;
+ struct ast_var_t *newvariable = NULL;
+
+ if (!group || !name) {
+ ast_log(LOG_WARNING, "<%s> GROUP assignment failed for %s@%s, group/name cannot be NULL, group variable '%s' not set\n", ast_channel_name(chan), group, category, name);
+ return 0;
+ }
+
+ if (!category) {
+ category = "";
+ }
+
+ if (!value) {
+ value = "";
+ }
+
+ /* Find our group meta data */
+ AST_RWLIST_WRLOCK(&groups_meta);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups_meta, gmi, group_meta_list) {
+ if ((strcasecmp(gmi->group, group) != 0) || (strcasecmp(gmi->category, category) != 0)) {
+ continue;
+ }
+
+ headp = &gmi->varshead;
+
+ AST_LIST_TRAVERSE(headp, newvariable, entries) {
+ if (strcasecmp(ast_var_name(newvariable), name) == 0) {
+ /* there is already such a variable, delete it */
+ AST_LIST_REMOVE(headp, newvariable, entries);
+ ast_var_delete(newvariable);
+ break;
+ }
+ }
+
+ newvariable = ast_var_assign(name, value);
+
+ AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+ manager_event(EVENT_FLAG_DIALPLAN, "GroupVarSet",
+ "Channel: %s\r\n"
+ "Category: %s\r\n"
+ "Group: %s\r\n"
+ "Variable: %s\r\n"
+ "Value: %s\r\n"
+ "Uniqueid: %s\r\n",
+ chan ? ast_channel_name(chan) : "none",
+ category, group, name, value,
+ chan ? ast_channel_uniqueid(chan) : "none");
+
+ break; /* We only have one list item per group at category */
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&groups_meta);
+
+ if (newvariable == NULL) {
+ ast_log(LOG_WARNING, "<%s> GROUP assignment %s@%s doesn't exist, group variable '%s' not set\n", ast_channel_name(chan), group, category, name);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*!
+ * \brief Get a group variable for a group at category
+ *
+ * \param group group to get the variable from
+ * \param category category to get the variable from
+ * \param name name of the variable to get
+ *
+ * \retval NOT NULL On success return char* of variable contents
+ * \retval NULL On failure (variable in group at category not found)
+ */
+const char* ast_app_group_get_var(const char *group, const char *category, const char *name) {
+ struct ast_group_meta *gmi = NULL;
+
+ struct varshead *headp;
+ const char *variable;
+ struct ast_var_t *ast_var;
+
+ if (!group || !name) {
+ return NULL;
+ }
+
+ if (!category) {
+ category = "";
+ }
+
+ /* Find our group meta data */
+ AST_RWLIST_RDLOCK(&groups_meta);
+ AST_RWLIST_TRAVERSE(&groups_meta, gmi, group_meta_list) {
+ if ((strcasecmp(gmi->group, group) != 0) || (strcasecmp(gmi->category, category) != 0)) {
+ continue;
+ }
+
+ headp = &gmi->varshead;
+
+ AST_LIST_TRAVERSE(headp, ast_var, entries) {
+ variable = ast_var_name(ast_var);
+
+ if (!strcasecmp(variable, name)) {
+ /* found it */
+ AST_RWLIST_UNLOCK(&groups_meta);
+ return ast_var_value(ast_var);
+ }
+ }
+ }
+ AST_RWLIST_UNLOCK(&groups_meta);
+
+ return NULL;
+}
+
int ast_app_group_get_count(const char *group, const char *category)
{
- struct ast_group_info *gi = NULL;
- int count = 0;
+ struct ast_group_info *gi = NULL;
+ int count = 0;
- if (ast_strlen_zero(group)) {
- return 0;
- }
+ if (ast_strlen_zero(group)) {
+ return 0;
+ }
- AST_RWLIST_RDLOCK(&groups);
- AST_RWLIST_TRAVERSE(&groups, gi, group_list) {
- if (!strcasecmp(gi->group, group) && (ast_strlen_zero(category) || (!ast_strlen_zero(gi->category) && !strcasecmp(gi->category, category)))) {
- count++;
- }
- }
- AST_RWLIST_UNLOCK(&groups);
+ AST_RWLIST_RDLOCK(&groups);
+ AST_RWLIST_TRAVERSE(&groups, gi, group_list) {
+ if (!strcasecmp(gi->group, group) && (ast_strlen_zero(category) || (!ast_strlen_zero(gi->category) && !strcasecmp(gi->category, category)))) {
+ count++;
+ }
+ }
+ AST_RWLIST_UNLOCK(&groups);
- return count;
+ return count;
}
int ast_app_group_match_get_count(const char *groupmatch, const char *category)
@@ -2162,6 +2322,7 @@
return 0;
}
+ /* if regex compilation fails, return zero matches */
if (!ast_strlen_zero(category) && regcomp(®exbuf_category, category, REG_EXTENDED | REG_NOSUB)) {
ast_log(LOG_ERROR, "Regex compile failed on: %s\n", category);
regfree(®exbuf_group);
@@ -2186,33 +2347,240 @@
int ast_app_group_update(struct ast_channel *old, struct ast_channel *new)
{
- struct ast_group_info *gi = NULL;
+ struct ast_group_info *gi = NULL;
+ struct ast_group_info *gi_new = NULL;
- AST_RWLIST_WRLOCK(&groups);
- AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
- if (gi->chan == old) {
+ AST_RWLIST_WRLOCK(&groups);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
+ /* keep channel groups on transfer */
+ if (gi->chan == old) {
+ /* only move group if it doesn't already exist on new */
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi_new, group_list) {
+ if (gi_new->chan == old && !strcasecmp(gi_new->group, gi->group) && !strcasecmp(gi_new->category, gi->category)) {
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+
gi->chan = new;
- } else if (gi->chan == new) {
- AST_RWLIST_REMOVE_CURRENT(group_list);
- ast_free(gi);
- }
- }
- AST_RWLIST_TRAVERSE_SAFE_END;
- AST_RWLIST_UNLOCK(&groups);
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&groups);
- return 0;
+ return 0;
+}
+
+/* Remove a channel from a group meta assignment */
+/* Right now this just removes all the group metadata for a group at category if this is the last channel in the group at category */
+/* Ideally we would have direct pointers from the channel group assignments into the metadata struct, so we don't have to search */
+int ast_app_group_remove_channel(struct ast_channel *chan, char *group, char *category) {
+ struct ast_group_meta *gmi = NULL; /*!< Group metadatas */
+
+ struct varshead *headp;
+ struct ast_var_t *vardata;
+
+ int destroy = 0;
+
+ if (!category) {
+ category = "";
+ }
+
+ AST_RWLIST_WRLOCK(&groups_meta);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups_meta, gmi, group_meta_list) {
+
+ if (!strcasecmp(gmi->group, group) && !strcasecmp(gmi->category, category) && (--gmi->num_channels <= 0)) {
+ /* Remove all group variables */
+ headp = &gmi->varshead;
+
+ while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) {
+ ast_var_delete(vardata);
+ }
+
+ AST_RWLIST_REMOVE_CURRENT(group_meta_list);
+ ast_free(gmi);
+
+ destroy = 1;
+ break; /* We only have one list item per group at category */
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&groups_meta);
+
+ manager_event(EVENT_FLAG_DIALPLAN, "GroupChannelRemove",
+ "Channel: %s\r\n"
+ "Category: %s\r\n"
+ "Group: %s\r\n"
+ "Uniqueid: %s\r\n",
+ ast_channel_name(chan),
+ category, group,
+ ast_channel_uniqueid(chan));
+
+ if (destroy) {
+ manager_event(EVENT_FLAG_DIALPLAN, "GroupDestroy",
+ "Category: %s\r\n"
+ "Group: %s\r\n",
+ category, group);
+ }
+
+ return 1;
+}
+
+/* Add a channel to a group meta assignment, create a group meta item if it doesn't exist */
+int ast_app_group_add_channel(struct ast_channel *chan, char *group, char *category) {
+ struct ast_group_meta *gmi = NULL; /*!< Group metadatas */
+
+ AST_RWLIST_WRLOCK(&groups_meta);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups_meta, gmi, group_meta_list) {
+ if (!strcasecmp(gmi->group, group) && !strcasecmp(gmi->category, category)) {
+ break; /* We only have one list item per group at category */
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+
+ if (!gmi) {
+ if (!(gmi = ast_calloc(1, sizeof(struct ast_group_meta)))) {
+ AST_RWLIST_UNLOCK(&groups_meta);
+ return -1;
+ }
+
+ strcpy(gmi->group, group);
+
+ if (!ast_strlen_zero(category)) {
+ strcpy(gmi->category, category);
+ }
+
+ AST_RWLIST_INSERT_TAIL(&groups_meta, gmi, group_meta_list);
+
+ manager_event(EVENT_FLAG_DIALPLAN, "GroupCreate",
+ "Category: %s\r\n"
+ "Group: %s\r\n",
+ category, group);
+ }
+
+ gmi->num_channels++;
+
+ AST_RWLIST_UNLOCK(&groups_meta);
+
+ manager_event(EVENT_FLAG_DIALPLAN, "GroupChannelAdd",
+ "Channel: %s\r\n"
+ "Category: %s\r\n"
+ "Group: %s\r\n"
+ "Uniqueid: %s\r\n",
+ ast_channel_name(chan),
+ category, group,
+ ast_channel_uniqueid(chan));
+
+ return 1;
+}
+
+/* Remove all channels from the given group */
+int ast_app_group_remove_all_channels(const char *group, const char *category) {
+ struct ast_group_info *gi = NULL;
+
+ int channels_found = 0;
+
+ if (!category) {
+ category = "";
+ }
+
+ /* Traverse group at category channel assignments */
+ AST_RWLIST_WRLOCK(&groups);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
+ if (strcasecmp(gi->group, group) || strcasecmp(gi->category, category)) {
+ /* Need to match group at category exactly */
+ continue;
+ }
+
+ ast_app_group_remove_channel(gi->chan, gi->group, gi->category);
+ AST_RWLIST_REMOVE_CURRENT(group_list);
+ ast_free(gi);
+
+ channels_found++;
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&groups);
+
+ return !(channels_found > 0);
+}
+
+int ast_app_group_rename(const char *old_group, const char *old_category, const char *new_group, const char *new_category) {
+ struct ast_group_info *gi = NULL;
+ struct ast_group_meta *gmi = NULL; /*!< Group metadatas */
+
+ int channels_found = 0;
+
+ if (!old_category) {
+ old_category = "";
+ }
+
+ if (!new_category) {
+ new_category = "";
+ }
+
+ /* Traverse group at category channel assignments */
+ AST_RWLIST_WRLOCK(&groups);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
+ if (strcasecmp(gi->group, old_group) || strcasecmp(gi->category, old_category)) {
+ /* Need to match group at category exactly */
+ continue;
+ }
+
+ strncpy(gi->group, new_group, MAX_GROUP_LEN);
+ strncpy(gi->category, new_category, MAX_CATEGORY_LEN);
+ channels_found++;
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&groups);
+
+
+ /* Group Variables */
+ AST_RWLIST_WRLOCK(&groups_meta);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups_meta, gmi, group_meta_list) {
+ if (strcasecmp(gmi->group, old_group) || strcasecmp(gmi->category, old_category)) {
+ /* Need to match group at category exactly */
+ continue;
+ }
+
+ strncpy(gmi->group, new_group, MAX_GROUP_LEN);
+ strncpy(gmi->category, new_category, MAX_CATEGORY_LEN);
+ channels_found++;
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&groups_meta);
+
+ if (channels_found) {
+ manager_event(EVENT_FLAG_DIALPLAN, "GroupRename",
+ "OldGroup: %s\r\n"
+ "OldCategory: %s\r\n"
+ "NewGroup: %s\r\n"
+ "NewCategory: %s\r\n",
+ old_group,
+ old_category,
+ new_group,
+ new_category);
+ }
+
+ return !(channels_found > 0);
}
int ast_app_group_discard(struct ast_channel *chan)
{
struct ast_group_info *gi = NULL;
+ /* Find and remove all groups associated to this channel */
AST_RWLIST_WRLOCK(&groups);
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
- if (gi->chan == chan) {
- AST_RWLIST_REMOVE_CURRENT(group_list);
- ast_free(gi);
+ if (gi->chan != chan) {
+ continue;
}
+
+ /* Find our group meta data, remove the entire group metadata if we're the last channel */
+ ast_app_group_remove_channel(chan, gi->group, gi->category);
+
+ /* Remove this group assignment for this channel */
+ AST_RWLIST_REMOVE_CURRENT(group_list);
+ ast_free(gi);
}
AST_RWLIST_TRAVERSE_SAFE_END;
AST_RWLIST_UNLOCK(&groups);
@@ -2240,6 +2608,26 @@
return AST_RWLIST_UNLOCK(&groups);
}
+int ast_app_group_meta_wrlock(void)
+{
+ return AST_RWLIST_WRLOCK(&groups_meta);
+}
+
+int ast_app_group_meta_rdlock(void)
+{
+ return AST_RWLIST_RDLOCK(&groups_meta);
+}
+
+struct ast_group_meta *ast_app_group_meta_head(void)
+{
+ return AST_RWLIST_FIRST(&groups_meta);
+}
+
+int ast_app_group_meta_unlock(void)
+{
+ return AST_RWLIST_UNLOCK(&groups_meta);
+}
+
unsigned int __ast_app_separate_args(char *buf, char delim, int remove_chars, char **array, int arraylen)
{
int argc;
diff --git a/main/cli.c b/main/cli.c
index dc13019..f4034d5 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -1862,6 +1862,34 @@
return ret;
}
+int ast_app_group_all_callback(ao2_callback_fn *callback, int flags);
+
+#include "asterisk/dlinkedlists.h"
+
+struct group_list_entry {
+ struct ast_str *group; /*!< A group assignment is group at category, this is the group part */
+ struct ast_str *category; /*!< This is the category part */
+ int transfer_fixup:1; /*!< Whether we will run a fixup on masquerade */
+
+ struct group_data_store *group_store; /*!< The group_data_store we live on. (Type: struct tie_data_store *) */
+
+ AST_DLLIST_ENTRY(group_list_entry) entries; /*!< Next group */
+};
+
+int group_show_channels_callback(void *obj, void *arg, int flags);
+
+int group_show_channels_callback(void *obj, void *arg, int flags) {
+#define FORMAT_STRING "%-25s %-20s %-20s\n"
+
+ struct ast_channel *chan = obj;
+ struct group_list_entry *ge = arg;
+
+ ast_cli(flags, FORMAT_STRING, ast_channel_name(chan), ast_str_buffer(ge->group), (!ge->category ? "(default)" : ast_str_buffer(ge->category)));
+
+ return 0;
+#undef FORMAT_STRING
+}
+
static char *group_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
#define FORMAT_STRING "%-25s %-20s %-20s\n"
@@ -1916,6 +1944,62 @@
#undef FORMAT_STRING
}
+static char *group_show_variables(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_STRING "%-20s %-20s\n"
+#define FORMAT_STRING_VAR " %s=%s\n"
+
+ struct ast_group_meta *gmi = NULL;
+ struct varshead *headp;
+ struct ast_var_t *variable = NULL;
+ int numgroups = 0;
+ char group[80];
+ char category[80];
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "group show variables";
+ e->usage =
+ "Usage: group show variables [group@[category]]\n"
+ " Lists all currently active groups and their variables.\n"
+ " Optional group, or group at category can be used to only show a specific group\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc < 3 || a->argc > 4)
+ return CLI_SHOWUSAGE;
+
+ if (a->argc == 4) {
+ if (ast_app_group_split_group(a->argv[3], group, sizeof(group), category, sizeof(category))) {
+ return NULL;
+ }
+ }
+
+ ast_cli(a->fd, "Group Variables Category\n");
+
+ /* Print group variables */
+ ast_app_group_meta_rdlock();
+ gmi = ast_app_group_meta_head();
+ while (gmi) {
+ ast_cli(a->fd, FORMAT_STRING, gmi->group, (strcmp(gmi->category, "") ? gmi->category : "(Default)"));
+ numgroups++;
+ headp = &gmi->varshead;
+
+ AST_LIST_TRAVERSE(headp, variable, entries) {
+ ast_cli(a->fd, FORMAT_STRING_VAR, ast_var_name(variable), ast_var_value(variable));
+ }
+
+ gmi = AST_LIST_NEXT(gmi, group_meta_list);
+ }
+ ast_app_group_meta_unlock();
+
+ ast_cli(a->fd, "%d active group%s\n", numgroups, ESS(numgroups));
+ return CLI_SUCCESS;
+#undef FORMAT_STRING
+}
+
static char *handle_cli_wait_fullybooted(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
switch (cmd) {
@@ -2002,6 +2086,8 @@
AST_CLI_DEFINE(group_show_channels, "Display active channels with group(s)"),
+ AST_CLI_DEFINE(group_show_variables, "Display active channels with group(s), along with group variables"),
+
AST_CLI_DEFINE(handle_help, "Display help list, or specific help on a command"),
AST_CLI_DEFINE(handle_logger_mute, "Toggle logging output to a console"),
--
To view, visit https://gerrit.asterisk.org/c/asterisk/+/17655
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings
Gerrit-Project: asterisk
Gerrit-Branch: 18
Gerrit-Change-Id: I23e48d1cdfc8adaffdfec2e936e56143603914f2
Gerrit-Change-Number: 17655
Gerrit-PatchSet: 1
Gerrit-Owner: Mark Murawski <markm at intellasoft.net>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20211213/fc824426/attachment-0001.html>
More information about the asterisk-code-review
mailing list