[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(&regexbuf_group, groupmatch, REG_EXTENDED | REG_NOSUB)) {
+		ast_log(LOG_ERROR, "Regex compile failed on: %s\n", groupmatch);
+		return 0;
+	}
+
+	if (regcomp(&regexbuf_category, categorymatch, REG_EXTENDED | REG_NOSUB)) {
+		ast_log(LOG_ERROR, "Regex compile failed on: %s\n", categorymatch);
+		regfree(&regexbuf_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(&regexbuf_group, gmi->group, 0, NULL, 0) && (ast_strlen_zero(categorymatch) || (!ast_strlen_zero(gmi->category) && !regexec(&regexbuf_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(&regexbuf_group);
+	regfree(&regexbuf_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(&regexbuf_category, category, REG_EXTENDED | REG_NOSUB)) {
 		ast_log(LOG_ERROR, "Regex compile failed on: %s\n", category);
 		regfree(&regexbuf_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