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 */ +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_TAIL(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); @@ -8679,6 +8730,17 @@ return __ast_goto_if_exists(chan, context, exten, priority, 1); } +/*! \brief check for vailidity of a goto target and do the goto + * + * \param chan Channel on which to test target validity + * \param goto_target Goto target string. ([[context,]extension,]priority) see below for examples + * + * \note goto_target valid format: priority + * \note goto_target valid format: exten,priority + * \note goto_target valid format: context,exten,priority + * + * \note This function is depricated. Use ast_pbx_exten_parse in combination with ast_explicit_goto or ast_async_goto + */ static int pbx_parseable_goto(struct ast_channel *chan, const char *goto_string, int async) { char *exten, *pri, *context; @@ -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);