[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