[asterisk-dev] Arbitrary hangup handlers [patch]

Mark Murawski markm at intellasoft.net
Mon Sep 13 15:55:50 CDT 2010


I've just finished writing a hangup handler module. And associated 
helper function ast_pbx_exten_parse(), which I think is needed to 
separate the functionality of parsing a gosub target from actually going 
to the target.  It's been malloc debugged and valgrind debugged.

Overview of module:
I find the 'h' extensions to be limited, cumbersome, and error prone 
when writing very complex dialplan, especially when using lots of gotos 
and gosubs.  Say you have a main dialplan entry point context called 
callqueue.  If at any point after callqueue runs, you want to run a 
hangup handler specific for the callqueue dialplan, you have to put an 
'h' extension in every single context you plan to possibly jump to after 
entering callqueue.  This solves that problem, and gives the added 
benefit of cascading hangup handlers.

For proof of concept development convenience, I used an existing 
function for setting up the hangup handlers, but it should probably be 
it's own function
And for more convenience, I have the bulk of the code in channel.c, but 
I'm not totally sure where it should belong.
Some other stuff needs adjusting as well... right now there's fixed 
sized buffers for context,exten,priority... and there's several spots 
that didn't get tabs...
If this gets the thumbs up, I'll fix the formatting and post a reviewboard.

Without further ado, here it is:

Usage:
context callqueueHangupHandler {
   s => {
     // cleanup here
   }
}

context callqueue {
   s => {
     Set(CHANNEL(addhanguphandler)="callqueueHangupHandler,s,1");
     gosub(foo...)
     goto bar..;
   }
}

Execution:
1. callqueue: set(channel(addhanguphandler...)
2. gosub
3. goto bar
4. channel hangs up
5. channel launches dialplan @callqueueHangupHandler


Cascading hangup handlers are also possible, where as with standard 
dialplan h extensions, they are not:

context test {
   s => {
     goto fooTest, s, 1;
   }
}

context fooTest {
   s => {
     GoSub(barTest,s,1);
   }

   h => {
     NoOp(footest hangup);
   }
}

context barTest {
   s => {
     NoOp(something in bar);
     Hangup();
   }

   h => {
     NoOp(bartest hangup);
   }
}

Execution:
     -- Goto (fooTest,s,1)
     -- Executing [s at fooTest:1] Gosub("SIP/tipton-local-0000000b", 
"barTest,s,1") in new stack
     -- Executing [s at barTest:1] NoOp("SIP/tipton-local-0000000b", 
"something in bar") in new stack
     -- Executing [s at barTest:2] Hangup("SIP/tipton-local-0000000b", "") 
in new stack
   == Spawn extension (barTest, s, 2) exited non-zero on 
'SIP/intellasoft-tipton-local-0000000b'
     -- Executing [h at barTest:1] NoOp("SIP/tipton-local-0000000b", 
"bartest hangup") in new stack

Only the deepest 'h' exten will execute

Where as with this new method, you could call addhanguphandler as many 
times as you like, and they will all execute in the order of most 
recently added will execute first.

Set(CHANNEL(addhanguphandler)="callqueueHangupHandler,s,1");
Set(CHANNEL(addhanguphandler)="someotherHangupHandler,s,1");
Set(CHANNEL(addhanguphandler)="yetanotherHangupHandler,s,1");


Code (based off of 1.6.0.26)
----------
Index: main/channel.c
===================================================================
--- main/channel.c    (revision 252521)
+++ main/channel.c    (working copy)
@@ -202,6 +202,157 @@
      return var;
  }

+static void hangup_handlers_free(void *data);
+
+static struct ast_datastore_info hanguphandler_info = {
+    .type = "HANGUP_HANDLERS",
+    .destroy = hangup_handlers_free,
+};
+
+/* Individual hangup handlers within the hanguphandler datastore */
+struct hanguphandler_item {
+        char context[1024];
+        char exten[1024];
+        char priority[1024];
+        AST_LIST_ENTRY(hanguphandler_item) entries;
+};
+
+AST_LIST_HEAD(hanguphandlers_list, hanguphandler_item);
+
+/* Datastore contents for hangup handlers */
+struct hanguphandler_data_store {
+    struct hanguphandlers_list hanguphandlers_head;
+};
+
+static void hangup_handlers_free(void *data) {
+    struct hanguphandler_data_store *hanguphandlers_store = (struct 
hanguphandler_data_store *) data;
+    struct hanguphandlers_list *hanguphandlers_list;
+        struct hanguphandler_item *hanguphandler_item = NULL;
+
+    if (!data) {
+        return;
+    }
+
+    hanguphandlers_list = &hanguphandlers_store->hanguphandlers_head;
+
+    while ((hanguphandler_item = 
AST_LIST_REMOVE_HEAD(hanguphandlers_list, entries))) {
+        ast_log(LOG_NOTICE, "Freeing hangup handler: %s,%s,%s\n", 
hanguphandler_item->context, hanguphandler_item->exten, 
hanguphandler_item->priority);
+                ast_free(hanguphandler_item);
+    }
+
+    ast_free(data);
+}
+
+/*! helper function to set extension and priority (duped from pbx.c) */
+static void set_ext_pri(struct ast_channel *c, const char *exten, int pri)
+{
+    ast_channel_lock(c);
+    ast_copy_string(c->exten, exten, sizeof(c->exten));
+    c->priority = pri;
+    ast_channel_unlock(c);
+}
+
+/* lock channel before calling */
+int ast_channel_register_hangup_handler(struct ast_channel *chan, const 
char *context, const char *exten, const char *priority)
+{
+    struct ast_datastore *hanguphandlers_store;
+    struct hanguphandler_data_store *hanguphandlers_store_data;
+    struct hanguphandlers_list *hanguphandlers_list;
+    struct hanguphandler_item *hanguphandler_item;
+
+    if (!(hanguphandlers_store = ast_channel_datastore_find(chan, 
&hanguphandler_info, NULL))) {
+        hanguphandlers_store = 
ast_channel_datastore_alloc(&hanguphandler_info, NULL);
+
+        if (!hanguphandlers_store) {
+            ast_log(LOG_ERROR, "Unable to allocate new hanguphandler 
datastore.  Hangup handler will not run.\n");
+            return -1;
+        }
+
+        hanguphandlers_store->data = ast_calloc(1, sizeof(struct 
hanguphandler_data_store));
+        if (!hanguphandlers_store->data) {
+            ast_log(LOG_ERROR, "Unable to allocate hangup handler 
datastore list head. Hangup handler will not run.\n");
+            ast_channel_datastore_free(hanguphandlers_store);
+            ast_channel_unlock(chan);
+            return -1;
+        }
+
+        ast_channel_datastore_add(chan, hanguphandlers_store);
+    }
+
+    hanguphandlers_store_data = (struct hanguphandler_data_store *) 
hanguphandlers_store->data;
+
+    hanguphandlers_list = &hanguphandlers_store_data->hanguphandlers_head;
+    hanguphandler_item = ast_calloc(1, sizeof(struct hanguphandler_item));
+
+    if (!hanguphandler_item) {
+        ast_log(LOG_ERROR, "Unable to allocate hangup handler item. 
Hangup handler will not run.\n");
+        return -1;
+    }
+
+    strncpy(hanguphandler_item->context,  context,  
sizeof(hanguphandler_item->context));
+    strncpy(hanguphandler_item->exten,    exten,    
sizeof(hanguphandler_item->exten));
+    strncpy(hanguphandler_item->priority, priority, 
sizeof(hanguphandler_item->priority));
+
+    AST_LIST_INSERT_HEAD(hanguphandlers_list, hanguphandler_item, entries);
+    return 0;
+}
+
+int ast_channel_run_registered_hangup_handlers(struct ast_channel *chan)
+{
+    struct ast_datastore *hanguphandlers_store = 
ast_channel_datastore_find(chan, &hanguphandler_info, NULL);
+    struct hanguphandler_data_store *hanguphandlers_store_data = 
hanguphandlers_store ? hanguphandlers_store->data : NULL;
+    struct hanguphandlers_list *hanguphandlers_list;
+    struct hanguphandler_item *hanguphandler_item = NULL;
+
+    int ipriority;
+    int res;
+    int found;
+
+    if (!hanguphandlers_store) {
+        return 0;
+    }
+
+    hanguphandlers_list = &hanguphandlers_store_data->hanguphandlers_head;
+
+    while ((hanguphandler_item = 
AST_LIST_REMOVE_HEAD(hanguphandlers_list, entries))) {
+        ast_log(LOG_NOTICE, "Starting hangup handler: %s,%s,%s\n", 
hanguphandler_item->context, hanguphandler_item->exten, 
hanguphandler_item->priority);
+
+        ipriority = atoi(hanguphandler_item->priority);
+        if (ipriority == 0) {
+                ipriority = ast_findlabel_extension(chan, 
hanguphandler_item->context, hanguphandler_item->exten, 
hanguphandler_item->priority, chan->cid.cid_num);
+
+                if (!ipriority) {
+                      ast_log(LOG_WARNING, "Target does not exist for 
registered hangup handler: %s,%s,%s\n", hanguphandler_item->context, 
hanguphandler_item->exten, hanguphandler_item->priority);
+                      continue;
+                }
+        }
+        else {
+                    if (!ast_exists_extension(chan, 
hanguphandler_item->context, hanguphandler_item->exten, ipriority, 
chan->cid.cid_num)) {
+                            ast_log(LOG_WARNING, "Target does not exist 
for registered hangup handler: %s,%s,%s\n", hanguphandler_item->context, 
hanguphandler_item->exten, hanguphandler_item->priority);
+                                continue;
+                    }
+        }
+
+                ast_copy_string(chan->context, 
hanguphandler_item->context, sizeof(chan->context));
+        set_ext_pri(chan, hanguphandler_item->exten, ipriority);
+
+        while ((res = ast_spawn_extension(chan, chan->context, 
chan->exten, chan->priority, chan->cid.cid_num, &found, 1)) == 0) {
+            chan->priority++;
+        }
+
+        if (found && res) {
+            /* Something bad happened, or a hangup has been requested. */
+            ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on 
'%s'\n", chan->context, chan->exten, chan->priority, chan->name);
+            ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero 
on '%s'\n", chan->context, chan->exten, chan->priority, chan->name);
+            return -1;
+        }
+
+
+    }
+
+    return 0;
+}
+

Index: main/pbx.c
===================================================================
--- main/pbx.c    (revision 252521)
+++ main/pbx.c    (working copy)
@@ -3871,6 +3871,11 @@
              ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero 
on '%s'\n", c->context, c->exten, c->priority, c->name);
          }
      }
+
+    if ((!args || !args->no_hangup_chan) && !ast_test_flag(c, 
AST_FLAG_BRIDGE_HANGUP_RUN)) {
+        ast_channel_run_registered_hangup_handlers(c);
+    }
+
      ast_set2_flag(c, autoloopflag, AST_FLAG_IN_AUTOLOOP);
      ast_clear_flag(c, AST_FLAG_BRIDGE_HANGUP_RUN); /* from one round 
to the next, make sure this gets cleared */
      pbx_destroy(c->pbx);
@@ -8741,3 +8803,76 @@
  {
      return pbx_parseable_goto(chan, goto_string, 1);
  }
+
+/*! \brief return the pieces of a goto style target if it's valid.
+ *
+ *  \param chan         Channel on which to test target validity
+ *  \param goto_target  Goto target string. 
([[context,]extension,]priority) see below for examples
+ *  \param context      Parsed context (buffer must be pre allocated)
+ *  \param context_len  Size of context buffer
+ *  \param exten        Parsed exten (buffer must be pre allocated)
+ *  \param exten_len    Size of exten buffer
+ *  \param priority     Parsed priority (buffer must be pre allocated)
+ *  \param priority_len Size of priority buffer
+ *
+ *  \return -1 on failure
+ *  \return 0 on success
+ *
+ *  \example goto_target valid format: priority
+ *  \example goto_target valid format: exten,priority
+ *  \example goto_target valid format: context,exten,priority
+ */
+int ast_pbx_exten_parse(struct ast_channel *chan, const char 
*goto_target, char *context, int context_len, char *exten, int 
exten_len, char *priority, int priority_len)
+{
+    char *target = ast_strdupa(goto_target); /* Target must be writable 
for AST_STANDARD_RAW_ARGS */
+    int ipriority;
+    AST_DECLARE_APP_ARGS(args,
+        AST_APP_ARG(context);
+        AST_APP_ARG(exten);
+        AST_APP_ARG(priority);
+    );
+
+        AST_STANDARD_RAW_ARGS(args, target);
+
+    if (ast_strlen_zero(goto_target)) {
+        ast_log(LOG_WARNING, "goto_target cannot be empty 
([[context,]extension,]priority)\n");
+        return -1;
+    }
+
+    if (ast_strlen_zero(args.exten)) {
+        /* Only a priority in this one */
+        args.priority = args.context;
+        args.exten    = chan->exten;
+        args.context  = chan->context;
+    }
+    else if (ast_strlen_zero(args.priority)) {
+        /* Only an extension and priority in this one */
+        args.priority = args.exten;
+        args.exten    = args.context;
+        args.context  = chan->context;
+    }
+
+        ast_log(LOG_NOTICE, "locating context: %s exten: %s priority: 
%s\n", args.context, args.exten, args.priority);
+
+        ipriority = atoi(args.priority);
+        if (ipriority > 0) {
+            if (!ast_exists_extension(chan, args.context, args.exten, 
ipriority, chan->cid.cid_num)) {
+                  ast_log(LOG_WARNING, "priority based goto target not 
found: %s\n", goto_target);
+                *context = *exten = *priority = 0;
+                return -1;
+            }
+        }
+        else {
+          if (!ast_findlabel_extension(chan, args.context, args.exten, 
args.priority, chan->cid.cid_num)) {
+              ast_log(LOG_WARNING, "label based goto target not found: 
%s\n", goto_target);
+                  *context = *exten = *priority = 0;
+              return -1;
+          }
+        }
+
+        strncpy(context,  args.context,  context_len);
+        strncpy(exten,    args.exten,    exten_len);
+        strncpy(priority, args.priority, priority_len);
+
+        return 0;
+}

Index: funcs/func_channel.c
===================================================================
--- funcs/func_channel.c    (revision 252521)
+++ funcs/func_channel.c    (working copy)
@@ -108,7 +114,23 @@
      int ret = 0;
      signed char gainset;

-    if (!strcasecmp(data, "language"))
+    if (!strcasecmp(data, "addhanguphandler")) {
+              char context[1024];
+                char exten[1024];
+                char priority[1024];
+
+        ast_channel_lock(chan);
+
+        if (ast_pbx_exten_parse(chan, value, context, sizeof(context), 
exten, sizeof(exten), priority, sizeof(priority)) < 0) {
+            ast_log(LOG_ERROR, "Invalid value for 
CHANNEL(addhanguphandler). Gosub address is invalid: '%s'\n", (char *) 
value);
+            ast_channel_unlock(chan);
+            return -1;
+        }
+
+                ast_channel_register_hangup_handler(chan, (const char 
*) context, (const char *) exten, (const char *) priority);
+                ast_channel_unlock(chan);
+    }
+    else if (!strcasecmp(data, "language"))
          locked_string_field_set(chan, language, value);
      else if (!strcasecmp(data, "musicclass"))
          locked_string_field_set(chan, musicclass, value);



More information about the asterisk-dev mailing list