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

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 18 </div>
<div style="display:none"> Gerrit-Change-Id: I23e48d1cdfc8adaffdfec2e936e56143603914f2 </div>
<div style="display:none"> Gerrit-Change-Number: 17655 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Mark Murawski <markm@intellasoft.net> </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>