func_groupcount.c: Adding Group Variables + 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, but it's 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@category
                         it's a member of)
   GroupsShowVariables - Show variables in each group@category, one event per
                         group, all variables are contained in each
                         group@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@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)

Resolves: #291
This commit is contained in:
Mark Murawski 2024-03-18 12:41:03 -04:00
parent 34196f8796
commit 6da9f031b7
5 changed files with 1923 additions and 29 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1196,9 +1196,48 @@ struct ast_group_info;
/*! \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@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);
/*!
* \brief Set a group variable for a group@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 0 On success
* \retval -1 On failure (group@category not found)
* \retval -2 On failure (input validation error)
*/
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 for a group@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@category not found)
*/
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);
@ -1223,6 +1262,15 @@ struct ast_group_info *ast_app_group_list_head(void);
/*! \brief Unlock the group count list */
int ast_app_group_list_unlock(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

View File

@ -122,6 +122,8 @@ References:
#ifndef _ASTERISK_CHANNEL_H
#define _ASTERISK_CHANNEL_H
#include <regex.h>
#include "asterisk/alertpipe.h"
#include "asterisk/abstract_jb.h"
#include "asterisk/astobj2.h"
@ -173,6 +175,9 @@ extern "C" {
#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 +2924,19 @@ struct ast_group_info {
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 +3151,35 @@ struct ast_channel *ast_channel_get_by_name_prefix(const char *name, size_t name
*/
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 18
*/
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 18
*/
struct ast_channel *ast_channel_get_by_regex_compiled(regex_t *regex);
/*! @} End channel search functions. */
/*!

View File

@ -61,6 +61,7 @@
#include "asterisk/indications.h"
#include "asterisk/linkedlists.h"
#include "asterisk/threadstorage.h"
#include "asterisk/manager.h"
#include "asterisk/test.h"
#include "asterisk/module.h"
#include "asterisk/astobj2.h"
@ -69,6 +70,95 @@
#include "asterisk/json.h"
#include "asterisk/format_cache.h"
/*** DOCUMENTATION
<managerEvent language="en_US" name="GroupCreate">
<managerEventInstance class="EVENT_FLAG_REPORTING">
<synopsis>Raised when a group has been created. Note: GroupChannelAdd events will show which channels have been assigned this group@category</synopsis>
<syntax>
<parameter name="Category">
<para>The category portion of the group@category that has been created</para>
</parameter>
<parameter name="Group">
<para>The group portion of the group@category that has been created</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="GroupChannelAdd">
<managerEventInstance class="EVENT_FLAG_REPORTING">
<synopsis>Raised when a channel is now a part of a group@category GROUP() assignment</synopsis>
<syntax>
<parameter name="Channel">
<para>Channel that is now part of this group@category</para>
</parameter>
<parameter name="Uniqueid">
<para>Uniqueid of the channel that is now part of this group@category</para>
</parameter>
<parameter name="Category">
<para>The category portion of the group@category of the GROUP() that the channel is now a part of</para>
</parameter>
<parameter name="Group">
<para>The group portion of the group@category of the GROUP() that the channel is now a part of</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="GroupVarSet">
<managerEventInstance class="EVENT_FLAG_REPORTING">
<synopsis>Raised in response to when a Group Variable is set using GROUP_VAR() on a group@category GROUP()</synopsis>
<syntax>
<parameter name="Group">
<para>Name of the group that the variable was set on</para>
</parameter>
<parameter name="Category">
<para>Name of the category that the variable was set on</para>
</parameter>
<parameter name="Channel">
<para>Channel (if any) that caused the variable to be set</para>
</parameter>
<parameter name="Variable">
<para>The variable that was set</para>
</parameter>
<parameter name="Value">
<para>The value of the variable</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="GroupChannelRemove">
<managerEventInstance class="EVENT_FLAG_REPORTING">
<synopsis>Raised when a channel is no longer part of a group@category GROUP() assignment</synopsis>
<syntax>
<parameter name="Channel">
<para>Channel that no longer is part of this group@category</para>
</parameter>
<parameter name="Uniqueid">
<para>Uniqueid of the channel that is no longer is part of this group</para>
</parameter>
<parameter name="Category">
<para>The category portion of the group@category of the GROUP() that the channel was a part of</para>
</parameter>
<parameter name="Group">
<para>The group portion of the group@category of the GROUP() that the channel was a part of</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="GroupDestroy">
<managerEventInstance class="EVENT_FLAG_REPORTING">
<synopsis>Raised when a group has been completely removed. Note: GroupChannelRemove events will show which channels no longer are assigned to this group@category</synopsis>
<syntax>
<parameter name="Category">
<para>The category portion of the group@category that is no longer present</para>
</parameter>
<parameter name="Group">
<para>The group portion of the group@category that is no longer present</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
***/
AST_THREADSTORAGE_PUBLIC(ast_str_thread_global_buf);
static pthread_t shaun_of_the_dead_thread = AST_PTHREADT_NULL;
@ -118,10 +208,36 @@ static void *shaun_of_the_dead(void *data)
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@category, this is the group part */
struct ast_str *category; /*!< This is the category part */
struct group_data_store *group_store; /*!< The group_data_store we live on */
AST_LIST_ENTRY(group_list_entry) entries; /*!< Next group */
};
AST_LIST_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_LIST_ENTRY(group_data_store) entries; /*!< Next GROUP datastore */
};
AST_RWLIST_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'
@ -2183,7 +2299,7 @@ int ast_app_group_split_group(const char *data, char *group, int group_max, char
int ast_app_group_set_channel(struct ast_channel *chan, const char *data)
{
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;
@ -2197,9 +2313,13 @@ int ast_app_group_set_channel(struct ast_channel *chan, const char *data)
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;
@ -2218,6 +2338,8 @@ int ast_app_group_set_channel(struct ast_channel *chan, const char *data)
strcpy(gi->category, category);
}
AST_RWLIST_INSERT_TAIL(&groups, gi, group_list);
ast_app_group_add_channel(chan, group, category);
} else {
res = -1;
}
@ -2227,6 +2349,111 @@ int ast_app_group_set_channel(struct ast_channel *chan, const char *data)
return res;
}
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 -2;
}
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@category */
}
AST_RWLIST_TRAVERSE_SAFE_END;
AST_RWLIST_UNLOCK(&groups_meta);
if (!newvariable) {
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 -1;
}
return 0;
}
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;
@ -2265,6 +2492,7 @@ int ast_app_group_match_get_count(const char *groupmatch, const char *category)
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);
@ -2290,14 +2518,21 @@ int ast_app_group_match_get_count(const char *groupmatch, const char *category)
int ast_app_group_update(struct ast_channel *old, struct ast_channel *new)
{
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) {
/* 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;
@ -2306,16 +2541,219 @@ int ast_app_group_update(struct ast_channel *old, struct ast_channel *new)
return 0;
}
/* Remove a channel from a group meta assignment */
/* Right now this just removes all the group metadata for a group@category if this is the last channel in the group@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@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@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;
}
ast_copy_string(gmi->group, group, MAX_GROUP_LEN);
if (!ast_strlen_zero(category)) {
ast_copy_string(gmi->category, category, MAX_CATEGORY_LEN);
}
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@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@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@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@category exactly */
continue;
}
ast_copy_string(gi->group, new_group, MAX_GROUP_LEN);
ast_copy_string(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@category exactly */
continue;
}
strncpy(gmi->group, new_group, MAX_GROUP_LEN - 1);
strncpy(gmi->category, new_category, MAX_CATEGORY_LEN - 1);
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);
@ -2343,6 +2781,21 @@ int ast_app_group_list_unlock(void)
return AST_RWLIST_UNLOCK(&groups);
}
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;

View File

@ -1916,8 +1916,9 @@ static char *group_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cl
return NULL;
}
if (a->argc < 3 || a->argc > 4)
if (a->argc < 3 || a->argc > 4) {
return CLI_SHOWUSAGE;
}
if (a->argc == 4) {
if (regcomp(&regexbuf, a->argv[3], REG_EXTENDED | REG_NOSUB))
@ -1948,6 +1949,64 @@ static char *group_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cl
#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@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\n Category\nVariables\n");
ast_cli(a->fd, "------------------------------\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) {
@ -2021,7 +2080,6 @@ static struct ast_cli_entry cli_cli[] = {
AST_CLI_DEFINE(handle_debug, "Set level of debug chattiness"),
AST_CLI_DEFINE(handle_trace, "Set level of trace chattiness"),
AST_CLI_DEFINE(handle_verbose, "Set level of verbose chattiness"),
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"),
@ -2052,6 +2110,7 @@ static struct ast_cli_entry cli_channels_cli[] = {
AST_CLI_DEFINE(handle_showchan, "Display information on a specific channel"),
AST_CLI_DEFINE(handle_core_set_debug_channel, "Enable/disable debugging on a channel"),
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_softhangup, "Request a hangup on a given channel"),
};