diff --git a/funcs/func_groupcount.c b/funcs/func_groupcount.c index f6dd5c6bab6..43b8d90bb09 100644 --- a/funcs/func_groupcount.c +++ b/funcs/func_groupcount.c @@ -27,11 +27,16 @@ #include "asterisk.h" +#include + #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 @@ -51,37 +56,231 @@ channel's current group if not specified (and non-empty). + + Find groups by regular expression + + + + + + Search for groups matching group@category. This search will look at all known groups and filter by the regex(s) provided. + If only the group_regex is specified, then return group@category results that only match the group name against the group_regex + If only the category_regex is specified, then return group@category results that only match the category name against the category_regex. + If the search regex parameters are entirely empty, all group@category will be returned. + Uses standard regular expression matching (see regex(7)). + + Find groups containing the string 'foo' + On Channel 1: + Set(GROUP()=groupName) + On Channel 2: + Set(GROUP()=foobarbaz) + On Channel 3: + Set(GROUP()=foobarbill) + On Channel 4: + Set(GROUP()=somethingelse) + On any Channel: + Assuming that channels still exist that have the above associated GROUP() assignents + Set(groups_found=${GROUP_LIST(.*foo.*)}) + Given the above: + This will find the groups 'foobarbaz' and 'foobarbill' since they both match .*foo.* regex against the group name + Variable groups_found will be set to: foobarbaz,foobarbill + + + Find groups containing category name of 'bar' + + On Channel 1: + Set(GROUP()=groupName@categoryName) + On Channel 2: + Set(GROUP()=foo@barbaz) + On Channel 3: + Set(GROUP()=foo@barbill) + On Channel 4: + Set(GROUP()=anything@somethingelse) + On any Channel: + Assuming that channels still exist that have the above associated GROUP() assignents + Set(groups_found=${GROUP_LIST(@.*bar.*)}) + + Given the above: + This will find the groups 'foo@barbaz' and 'foo@barbill' since they both match .*bar.* regex against the category name + Variable groups_found will be set to: foo@barbaz,foo@barbill + + + Find groups containing 'foo' and category name of 'bar' + + On Channel 1: + Set(GROUP()=groupName@categoryName) + On Channel 2: + Set(GROUP()=foo@barbaz) + On Channel 3: + Set(GROUP()=foo@barbill) + On Channel 4: + Set(GROUP()=anything@somethingelse) + On any Channel: + Assuming that channels still exist that have the above associated GROUP() assignents + Set(groups_found=${GROUP_LIST(.*foo.*@.*bar.*)}) + + Given the above: + This will find the groups 'foo@barbaz' and 'foo@barbill' since they both match .*foo*. against the group name and .*bar.* regex against the category name + Variable groups_found will be set to: foo@barbaz,foo@barbill + + + + GROUP + GROUP_CHANNEL_LIST + + + + + Find channels that are members of groups by regular expression + + + + + + + Search for groups matching group@category and return the channels that are members of those groups, filtering known groups by the regex(s) provided. + If only the group_regex is specified, then return group@category results that only match the group name against the group_regex + If only the category_regex is specified, then return group@category results that only match the category name against the category_regex. + If the search regex parameters are entirely empty, all channels having any kind of group membership will be returned. + Uses standard regular expression matching (see regex(7)). + + Find groups containing the string 'foo' + On Channel SIP/1234-0000001: + Set(GROUP()=groupName) + On Channel SIP/1234-0000003: + Set(GROUP()=foobarbaz) + On Channel SIP/1234-0000004: + Set(GROUP()=foobarbill) + On Channel SIP/1234-0000005: + Set(GROUP()=somethingelse) + On any Channel: + Assuming that channels still exist that have the above associated GROUP() assignents + Set(channels_found=${GROUP_CHANNEL_LIST(.*foo.*)}) + Given the above: + This will find the channels that are in groups 'foobarbaz' and 'foobarbill' since they both match .*foo.* regex against the group name + Variable channels_found will be set to: SIP/1234-0000003,SIP/1234-0000004 + + + Find groups containing category name of 'bar' + + On Channel SIP/1234-0000001: + Set(GROUP()=groupName@categoryName) + On Channel SIP/1234-0000002: + Set(GROUP()=foo@barbaz) + On Channel SIP/1234-0000003: + Set(GROUP()=foo@barbill) + On Channel SIP/1234-0000004: + Set(GROUP()=anything@somethingelse) + On any Channel: + Assuming that channels still exist that have the above associated GROUP() assignents + Set(channels_found=${GROUP_CHANNEL_LIST(@.*bar.*)}) + + Given the above: + This will find the channels that are in groups 'foo@barbaz' and 'foo@barbill' since they both match .*bar.* regex against the category name + Variable channels_found will be set to: SIP/1234-0000002,SIP/1234-0000003 + + + Find groups containing 'foo' and category name of 'bar' + + On Channel SIP/1234-0000001: + Set(GROUP()=groupName@categoryName) + On Channel SIP/1234-0000002: + Set(GROUP()=foo@barbaz) + On Channel SIP/1234-0000003: + Set(GROUP()=foo@barbill) + On Channel SIP/1234-0000004: + Set(GROUP()=anything@somethingelse) + On any Channel: + Assuming that channels still exist that have the above associated GROUP() assignents + Set(channels_found=${GROUP_CHANNEL_LIST(.*foo.*@.*bar.*)}) + + Given the above: + This will find the channels that are in groups 'foo@barbaz' and 'foo@barbill' since they both match .*foo*. against the group name and .*bar.* regex against the category name + Variable channels_found will be set to: SIP/1234-0000002,SIP/1234-0000003 + + + + GROUP + GROUP_MATCH_LIST + + Counts the number of channels in the groups matching the specified pattern. - + A standard regular expression used to match a group name. - + A standard regular expression used to match a category name. Calculates the group count for all groups that match the specified pattern. Note: category matching is applied after matching based on group. - Uses standard regular expression matching on both (see regex(7)). + Uses standard regular expression matching on both parameters (see regex(7)). Gets or sets the channel group. - + + + Group name. + Category name. category can be employed for more fine grained group management. Each channel - can only be member of exactly one group per category. + can only be member of exactly one group per category. Once a group is assigned to + a channel, per-group variables can then be assigned via GROUP_VAR() + + + GROUP_VAR + + + + + Gets or sets a variable on a channel group. + + + + Category name. + + + + + Once a GROUP() is assinged to a channel, variables can then be attached to that group. As long as the + group exists, these variables will exist. When the group no longer exists, the variables will be cleaned up + automatically. + + + These variables can be considered 'globals for a group of channels'. These variables are + like a SHARED() variable but for a collection of channels. + + + On Channel 1 + Set(GROUP()=foo) + Set(GROUP_VAR(foo,savethis)=123) + + On Channel 2 -- Assuming Channel1 is still up and Channel1 is still a member of GROUP(foo) + We can now join the same Group and read a Group Variable + Set(GROUP()=foo) + NoOp(${GROUP_VAR(foo,savethis) <-- This prints '123' + + On Channel 3 -- Assuming Channel1 is still up and Channel1 is still a member of GROUP(foo) + We do not need to join the group in order to read a Group Variable + NoOp(${GROUP_VAR(foo,savethis) <-- This prints '123' + + + GROUP + SHARED + @@ -92,15 +291,179 @@ Gets a list of the groups set on a channel. + + + Add channel group assignments + + + + + Channel to operate on. + + + Group name to set. + + + Category name to set. + + + + For more information, see the dialplan function GROUP() + + + + + Remove channel group assignments + + + + + Channel to operate on. + + + Group name to remove. + + + Category name to remove. + + + + For more information, see the dialplan function GROUP() + + + + + Dump all group information to the console + + + When executed, this will show all group assignments and group variables + in the console + + + + + Get channel group variables. + + + + + + + + + + At a minimum, either group or category must be provided. + For more information, see the dialplan function GROUP_VAR(). + + + + + + Set channel group variables. + + + + + + + + + + + At a minimum, either group or category must be provided. + For more information, see the dialplan function GROUP_VAR(). + + + + + + Show channel groups. With optional filtering + + + + + The exact group name to find, or to use a regular expression: set this parameter to: /regex/ + + + The exact category to find, or to use a regular expression: set this parameter to: /regex/ + + + + + This will return a list of channel groups that are in use. + For more information, see the dialplan function GROUP(). + + + + + + Show group channel assignments. With optional filtering + + + + + The exact group name to find, or to use a regular expression: set this parameter to: /regex/ + + + The exact category to find, or to use a regular expression: set this parameter to: /regex/ + + + + + This will return a list the channels that are within each group. + For more information, see the dialplan function GROUP(). + + + + + + Show group channel variable assignments. + + + + + + + This will return a list of groups and the variables assigned in each group. + For more information, see the dialplan function GROUP_VAR(). + + + + + + + Raised in response to a a GroupVarGet command + + + + ActionID (if any) that was passed into the GroupVarGet request + + + Name of the group that the variable is a part of + + + Name of the category that the variable is a part of + + + The variable that was requested + + + The value of the variable + + + + ***/ +static const char *dumpgroups_app = "DumpGroups"; + 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 +512,8 @@ static int group_match_count_function_read(struct ast_channel *chan, 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)); @@ -237,6 +600,539 @@ static struct ast_custom_function group_function = { .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],)\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] = ""; + int result; + 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],)=)\n"); + return -1; + } + + ast_app_group_split_group(args.groupcategory, group, sizeof(group), category, sizeof(category)); + result = ast_app_group_set_var(chan, group, category, args.varname, value); + + if (result != 0) { + /* We know we're not passing any NULL values, so our error has to be 'non-exist' */ + ast_log(LOG_WARNING, "GROUP_VAR() Variable set failed (group doesn't exist)"); + return -1; + } + + 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 AMI_SUCCESS; + } + + chan = ast_channel_get_by_name(channel); + if (!chan) { + astman_send_error(s, m, "Channel not found."); + return AMI_SUCCESS; + } + + 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) { + astman_send_ack(s, m, "Group Set"); + ast_channel_unref(chan); + return AMI_SUCCESS; + } + + astman_send_error(s, m, "Group set failed."); + ast_channel_unref(chan); + + return AMI_SUCCESS; +} + +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 AMI_SUCCESS; + } + + astman_send_error(s, m, "Group remove failed."); + + return AMI_SUCCESS; +} + +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 AMI_SUCCESS; +} + +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 AMI_SUCCESS; + } + + if (ast_strlen_zero(variable)) { + astman_send_error(s, m, "No variable specified."); + return AMI_SUCCESS; + } + + if (!category) { + category = ""; + } + + if (!value) { + value = ""; + } + + result = ast_app_group_set_var(NULL, group, category, variable, value); + + if (result != 0) { + /* We know we're not passing any NULL values, so our error has to be 'non-exist' */ + 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_or_regex = astman_get_header(m, "Group"); /* could be a regex, if starting with '/' */ + const char *category_or_regex = astman_get_header(m, "Category"); /* could be a regex, if starting with '/' */ + + const char *group_match = NULL; + const char *category_match = NULL; + + struct ast_str *group_regex_string = NULL; + struct ast_str *category_regex_string = NULL; + + regex_t regexbuf_group; + regex_t regexbuf_category; + + struct ast_group_meta *gmi = NULL; + char idText[256] = ""; + int groups = 0; + + if (!ast_strlen_zero(group_or_regex)) { + if (group_or_regex[0] == '/') { + group_regex_string = ast_str_create(strlen(group_or_regex)); + if (!group_regex_string) { + astman_send_error(s, m, "Memory Allocation Failure"); + return 0; /* Nothing to clean up */ + } + + /* Make "/regex/" into "regex" */ + if (ast_regex_string_to_regex_pattern(group_or_regex, &group_regex_string) != 0) { + astman_send_error(s, m, "Regex format invalid, Group param should be /regex/"); + ast_free(group_regex_string); + return AMI_SUCCESS; /* Nothing else to clean up */ + } + + /* if regex compilation fails, whole command fails */ + if (regcomp(®exbuf_group, ast_str_buffer(group_regex_string), REG_EXTENDED | REG_NOSUB)) { + astman_send_error_va(s, m, "Regex compile failed on: %s", group_or_regex); + ast_free(group_regex_string); + return AMI_SUCCESS; /* Nothing else to clean up */ + } + } else { + group_match = group_or_regex; + } + } + + if (!ast_strlen_zero(category_or_regex)) { + if (category_or_regex[0] == '/') { + category_regex_string = ast_str_create(strlen(category_or_regex)); + if (!category_regex_string) { + astman_send_error(s, m, "Memory Allocation Failure"); + goto done; + } + + /* Make "/regex/" into "regex" */ + if (ast_regex_string_to_regex_pattern(category_or_regex, &category_regex_string) != 0) { + astman_send_error(s, m, "Regex format invalid, Category param should be /regex/"); + ast_free(category_regex_string); + category_regex_string = NULL; + goto done; + } + + /* if regex compilation fails, whole command fails */ + if (regcomp(®exbuf_category, ast_str_buffer(category_regex_string), REG_EXTENDED | REG_NOSUB)) { + astman_send_error_va(s, m, "Regex compile failed on: %s", category_or_regex); + ast_free(category_regex_string); + category_regex_string = NULL; + goto done; + } + } else { + category_match = category_or_regex; + } + } + + 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)) { + if (group_regex_string && regexec(®exbuf_group, gmi->group, 0, NULL, 0)) { + continue; + } + + if (category_regex_string && regexec(®exbuf_category, gmi->category, 0, NULL, 0)) { + continue; + } + + if (group_match && strcmp(group_match, gmi->group)) { + continue; + } + + if (category_match && strcmp(category_match, gmi->category)) { + continue; + } + + 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); + +done: + if (group_regex_string) { + regfree(®exbuf_group); + ast_free(group_regex_string); + } + + if (category_regex_string) { + regfree(®exbuf_category); + ast_free(category_regex_string); + } + + return AMI_SUCCESS; +} + +static int manager_groups_show_channels(struct mansession *s, const struct message *m) +{ + const char *id = astman_get_header(m, "ActionID"); + const char *group_or_regex = astman_get_header(m, "Group"); /* could be a regex, if starting with '/' */ + const char *category_or_regex = astman_get_header(m, "Category"); /* could be a regex, if starting with '/' */ + + const char *group_match = NULL; + const char *category_match = NULL; + + struct ast_str *group_regex_string = NULL; + struct ast_str *category_regex_string = NULL; + + regex_t regexbuf_group; + regex_t regexbuf_category; + + struct ast_group_info *gi = NULL; + char idText[256] = ""; + int channels = 0; + + if (!ast_strlen_zero(group_or_regex)) { + if (group_or_regex[0] == '/') { + group_regex_string = ast_str_create(strlen(group_or_regex)); + if (!group_regex_string) { + astman_send_error(s, m, "Memory Allocation Failure"); + return 0; /* Nothing to clean up */ + } + + /* Make "/regex/" into "regex" */ + if (ast_regex_string_to_regex_pattern(group_or_regex, &group_regex_string) != 0) { + astman_send_error(s, m, "Regex format invalid, Group param should be /regex/"); + ast_free(group_regex_string); + return AMI_SUCCESS; /* Nothing else to clean up */ + } + + /* if regex compilation fails, whole command fails */ + if (regcomp(®exbuf_group, ast_str_buffer(group_regex_string), REG_EXTENDED | REG_NOSUB)) { + astman_send_error_va(s, m, "Regex compile failed on: %s", group_or_regex); + ast_free(group_regex_string); + return AMI_SUCCESS; /* Nothing else to clean up */ + } + } else { + group_match = group_or_regex; + } + } + + if (!ast_strlen_zero(category_or_regex)) { + if (category_or_regex[0] == '/') { + category_regex_string = ast_str_create(strlen(category_or_regex)); + if (!category_regex_string) { + astman_send_error(s, m, "Memory Allocation Failure"); + goto done; + } + + /* Make "/regex/" into "regex" */ + if (ast_regex_string_to_regex_pattern(category_or_regex, &category_regex_string) != 0) { + astman_send_error(s, m, "Regex format invalid, Category param should be /regex/"); + ast_free(category_regex_string); + category_regex_string = NULL; + goto done; + } + + /* if regex compilation fails, whole command fails */ + if (regcomp(®exbuf_category, ast_str_buffer(category_regex_string), REG_EXTENDED | REG_NOSUB)) { + astman_send_error_va(s, m, "Regex compile failed on: %s", category_or_regex); + ast_free(category_regex_string); + category_regex_string = NULL; + goto done; + } + } else { + category_match = category_or_regex; + } + } + + 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)) { + if (group_regex_string && regexec(®exbuf_group, gi->group, 0, NULL, 0)) { + continue; + } + + if (category_regex_string && regexec(®exbuf_category, gi->category, 0, NULL, 0)) { + continue; + } + + if (group_match && strcmp(group_match, gi->group)) { + continue; + } + + if (category_match && strcmp(category_match, gi->category)) { + continue; + } + + 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); + +done: + if (group_regex_string) { + regfree(®exbuf_group); + ast_free(group_regex_string); + } + + if (category_regex_string) { + regfree(®exbuf_category); + ast_free(category_regex_string); + } + + return AMI_SUCCESS; +} + +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 (!variables) { + ast_log(LOG_ERROR, "Unable to allocate new variable. Cannot proceed with GroupsShowVariables().\n"); + return AMI_SUCCESS; + } + + 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, 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 AMI_SUCCESS; +} + +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.", + .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,21 +1169,229 @@ static int group_list_function_read(struct ast_channel *chan, const char *cmd, return 0; } +static int group_match_list_function_read(struct ast_channel *chan, const char *cmd, + char *data, char *buf, size_t len) +{ + struct ast_group_meta *gmi = NULL; + struct ast_str *foundgroup_str = ast_str_create(MAX_GROUP_LEN + MAX_CATEGORY_LEN); + + buf[0] = '\0'; + + if (!foundgroup_str) { + ast_log(LOG_ERROR, "Unable to allocate new variable. Cannot proceed with GROUP_MATCH_LIST()\n"); + return -1; + } + + char groupmatch[MAX_GROUP_LEN] = ""; + char categorymatch[MAX_CATEGORY_LEN] = ""; + int groups_found = 0; + regex_t regexbuf_group; + regex_t regexbuf_category; + + 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 -1; + } + + if (regcomp(®exbuf_category, categorymatch, REG_EXTENDED | REG_NOSUB)) { + ast_log(LOG_ERROR, "Regex compile failed on: %s\n", categorymatch); + regfree(®exbuf_group); + return -1; + } + + /* 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 (groups_found > 1) { + ast_str_append(&foundgroup_str, 0, ","); + } + + ast_str_append(&foundgroup_str, 0, "%s@%s", gmi->group, gmi->category); + + groups_found++; + } + } + ast_app_group_meta_unlock(); + + snprintf(buf, len, "%s", ast_str_buffer(foundgroup_str)); + + regfree(®exbuf_group); + regfree(®exbuf_category); + + 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); + + if (!out) { + ast_log(LOG_ERROR, "Unable to allocate new variable. Cannot proceed with DumpGroups().\n"); + return -1; + } + + 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 group assignment%s\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_function = { + .name = "GROUP_MATCH_LIST", + .read = group_match_list_function_read, + .write = NULL, +}; + static struct ast_custom_function group_list_function = { .name = "GROUP_LIST", .read = group_list_function_read, .write = NULL, }; + +static int group_match_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); + + if (!out) { + ast_log(LOG_ERROR, "Unable to allocate new variable. Cannot proceed with GROUP_MATCH_CHANNEL_LIST().\n"); + return -1; + } + + buf[0] = 0; + + if (ast_strlen_zero(args.groupcategory)) { + ast_log(LOG_WARNING, "Syntax GROUP_MATCH_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 */ + } + + ast_copy_string(buf, ast_str_buffer(out), len); + } + + ast_free(out); + + return 0; +} + +static struct ast_custom_function group_match_channel_list_function = { + .name = "GROUP_MATCH_CHANNEL_LIST", + .syntax = "GROUP_MATCH_CHANNEL_LIST(group[@category])", + .synopsis = "Gets a comma delimited list of channels that are part of the given group@category.", + .desc = "Gets the list of channels in a group.\n", + .read = group_match_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_function); + res |= ast_custom_function_unregister(&group_match_channel_list_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_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,9 +1401,22 @@ static int load_module(void) 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_function); + res |= ast_custom_function_register(&group_match_channel_list_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_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 e4314509097..ee39784a0af 100644 --- a/include/asterisk/app.h +++ b/include/asterisk/app.h @@ -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 diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 99114366b36..34de0a30438 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -122,6 +122,8 @@ #ifndef _ASTERISK_CHANNEL_H #define _ASTERISK_CHANNEL_H +#include + #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. */ /*! diff --git a/main/app.c b/main/app.c index 5f5590afa91..0041d1193a0 100644 --- a/main/app.c +++ b/main/app.c @@ -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 + + + Raised when a group has been created. Note: GroupChannelAdd events will show which channels have been assigned this group@category + + + The category portion of the group@category that has been created + + + The group portion of the group@category that has been created + + + + + + + Raised when a channel is now a part of a group@category GROUP() assignment + + + Channel that is now part of this group@category + + + Uniqueid of the channel that is now part of this group@category + + + The category portion of the group@category of the GROUP() that the channel is now a part of + + + The group portion of the group@category of the GROUP() that the channel is now a part of + + + + + + + Raised in response to when a Group Variable is set using GROUP_VAR() on a group@category GROUP() + + + Name of the group that the variable was set on + + + Name of the category that the variable was set on + + + Channel (if any) that caused the variable to be set + + + The variable that was set + + + The value of the variable + + + + + + + Raised when a channel is no longer part of a group@category GROUP() assignment + + + Channel that no longer is part of this group@category + + + Uniqueid of the channel that is no longer is part of this group + + + The category portion of the group@category of the GROUP() that the channel was a part of + + + The group portion of the group@category of the GROUP() that the channel was a part of + + + + + + + Raised when a group has been completely removed. Note: GroupChannelRemove events will show which channels no longer are assigned to this group@category + + + The category portion of the group@category that is no longer present + + + The group portion of the group@category that is no longer present + + + + + ***/ + 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(®exbuf_category, category, REG_EXTENDED | REG_NOSUB)) { ast_log(LOG_ERROR, "Regex compile failed on: %s\n", category); regfree(®exbuf_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; diff --git a/main/cli.c b/main/cli.c index 2cb0722ebe8..06321d0bc47 100644 --- a/main/cli.c +++ b/main/cli.c @@ -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(®exbuf, a->argv[3], REG_EXTENDED | REG_NOSUB)) @@ -1948,6 +1949,63 @@ 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 Category\n Variables\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 +2079,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 +2109,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"), };