Replace most uses of ast_register_atexit with ast_register_cleanup.
[asterisk/asterisk.git] / main / app.c
index 0619151..f3fc298 100644 (file)
  * \author Mark Spencer <markster@digium.com>
  */
 
+/** \example
+ * \par This is an example of how to develop an app.
+ * Application Skeleton is an example of creating an application for Asterisk.
+ * \verbinclude app_skel.c
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
 #include "asterisk.h"
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
@@ -30,10 +40,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #endif
-#include <regex.h>
-#include <sys/file.h> /* added this to allow to compile, sorry! */
-#include <signal.h>
+#include <regex.h>          /* for regcomp(3) */
+#include <sys/file.h>       /* for flock(2) */
+#include <signal.h>         /* for pthread_sigmask(3) */
 #include <stdlib.h>         /* for closefrom(3) */
+#include <sys/types.h>
+#include <sys/wait.h>       /* for waitpid(2) */
+#ifndef HAVE_CLOSEFROM
+#include <dirent.h>         /* for opendir(3)   */
+#endif
 #ifdef HAVE_CAP
 #include <sys/capability.h>
 #endif /* HAVE_CAP */
@@ -49,9 +64,107 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/indications.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/threadstorage.h"
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/json.h"
+#include "asterisk/format_cache.h"
+
+#define MWI_TOPIC_BUCKETS 57
 
 AST_THREADSTORAGE_PUBLIC(ast_str_thread_global_buf);
 
+static pthread_t shaun_of_the_dead_thread = AST_PTHREADT_NULL;
+
+struct zombie {
+       pid_t pid;
+       AST_LIST_ENTRY(zombie) list;
+};
+
+static AST_LIST_HEAD_STATIC(zombies, zombie);
+
+/*
+ * @{ \brief Define \ref stasis topic objects
+ */
+static struct stasis_topic *mwi_topic_all;
+static struct stasis_cache *mwi_state_cache;
+static struct stasis_caching_topic *mwi_topic_cached;
+static struct stasis_topic_pool *mwi_topic_pool;
+
+static struct stasis_topic *queue_topic_all;
+static struct stasis_topic_pool *queue_topic_pool;
+/* @} */
+
+/*! \brief Convert a MWI \ref stasis_message to a \ref ast_event */
+static struct ast_event *mwi_to_event(struct stasis_message *message)
+{
+       struct ast_event *event;
+       struct ast_mwi_state *mwi_state;
+       char *mailbox;
+       char *context;
+
+       if (!message) {
+               return NULL;
+       }
+
+       mwi_state = stasis_message_data(message);
+
+       /* Strip off @context */
+       context = mailbox = ast_strdupa(mwi_state->uniqueid);
+       strsep(&context, "@");
+       if (ast_strlen_zero(context)) {
+               context = "default";
+       }
+
+       event = ast_event_new(AST_EVENT_MWI,
+                               AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
+                               AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
+                               AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, mwi_state->new_msgs,
+                               AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, mwi_state->old_msgs,
+                               AST_EVENT_IE_EID, AST_EVENT_IE_PLTYPE_RAW, &mwi_state->eid, sizeof(mwi_state->eid),
+                               AST_EVENT_IE_END);
+
+       return event;
+}
+
+/*
+ * @{ \brief Define \ref stasis message types for MWI
+ */
+STASIS_MESSAGE_TYPE_DEFN(ast_mwi_state_type,
+       .to_event = mwi_to_event, );
+STASIS_MESSAGE_TYPE_DEFN(ast_mwi_vm_app_type);
+/* @} */
+
+
+
+static void *shaun_of_the_dead(void *data)
+{
+       struct zombie *cur;
+       int status;
+       for (;;) {
+               if (!AST_LIST_EMPTY(&zombies)) {
+                       /* Don't allow cancellation while we have a lock. */
+                       pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+                       AST_LIST_LOCK(&zombies);
+                       AST_LIST_TRAVERSE_SAFE_BEGIN(&zombies, cur, list) {
+                               if (waitpid(cur->pid, &status, WNOHANG) != 0) {
+                                       AST_LIST_REMOVE_CURRENT(list);
+                                       ast_free(cur);
+                               }
+                       }
+                       AST_LIST_TRAVERSE_SAFE_END
+                       AST_LIST_UNLOCK(&zombies);
+                       pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+               }
+               pthread_testcancel();
+               /* Wait for 60 seconds, without engaging in a busy loop. */
+               ast_poll(NULL, 0, AST_LIST_FIRST(&zombies) ? 5000 : 60000);
+       }
+       return NULL;
+}
+
 
 #define AST_MAX_FORMATS 10
 
@@ -67,7 +180,7 @@ static AST_RWLIST_HEAD_STATIC(groups, ast_group_info);
  * \param collect
  * \param size
  * \param maxlen
- * \param timeout timeout in seconds
+ * \param timeout timeout in milliseconds
  *
  * \return 0 if extension does not exist, 1 if extension exists
 */
@@ -80,13 +193,15 @@ int ast_app_dtget(struct ast_channel *chan, const char *context, char *collect,
                maxlen = size;
        }
 
-       if (!timeout && chan->pbx) {
-               timeout = chan->pbx->dtimeoutms / 1000.0;
-       } else if (!timeout) {
-               timeout = 5;
+       if (!timeout) {
+               if (ast_channel_pbx(chan) && ast_channel_pbx(chan)->dtimeoutms) {
+                       timeout = ast_channel_pbx(chan)->dtimeoutms;
+               } else {
+                       timeout = 5000;
+               }
        }
 
-       if ((ts = ast_get_indication_tone(chan->zone, "dial"))) {
+       if ((ts = ast_get_indication_tone(ast_channel_zone(chan), "dial"))) {
                res = ast_playtones_start(chan, 0, ts->data, 0);
                ts = ast_tone_zone_sound_unref(ts);
        } else {
@@ -105,13 +220,15 @@ int ast_app_dtget(struct ast_channel *chan, const char *context, char *collect,
                        break;
                }
                collect[x++] = res;
-               if (!ast_matchmore_extension(chan, context, collect, 1, chan->cid.cid_num)) {
+               if (!ast_matchmore_extension(chan, context, collect, 1,
+                       S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
                        break;
                }
        }
 
        if (res >= 0) {
-               res = ast_exists_extension(chan, context, collect, 1, chan->cid.cid_num) ? 1 : 0;
+               res = ast_exists_extension(chan, context, collect, 1,
+                       S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL)) ? 1 : 0;
        }
 
        return res;
@@ -123,7 +240,7 @@ int ast_app_dtget(struct ast_channel *chan, const char *context, char *collect,
  * \param prompt The file to stream to the channel
  * \param s The string to read in to.  Must be at least the size of your length
  * \param maxlen How many digits to read (maximum)
- * \param timeout set timeout to 0 for "standard" timeouts. Set timeout to -1 for 
+ * \param timeout set timeout to 0 for "standard" timeouts. Set timeout to -1 for
  *      "ludicrous time" (essentially never times out) */
 enum ast_getdata_result ast_app_getdata(struct ast_channel *c, const char *prompt, char *s, int maxlen, int timeout)
 {
@@ -141,14 +258,14 @@ enum ast_getdata_result ast_app_getdata(struct ast_channel *c, const char *promp
        filename = ast_strdupa(prompt);
        while ((front = strsep(&filename, "&"))) {
                if (!ast_strlen_zero(front)) {
-                       res = ast_streamfile(c, front, c->language);
+                       res = ast_streamfile(c, front, ast_channel_language(c));
                        if (res)
                                continue;
                }
                if (ast_strlen_zero(filename)) {
                        /* set timeouts for the last prompt */
-                       fto = c->pbx ? c->pbx->rtimeoutms : 6000;
-                       to = c->pbx ? c->pbx->dtimeoutms : 2000;
+                       fto = ast_channel_pbx(c) ? ast_channel_pbx(c)->rtimeoutms : 6000;
+                       to = ast_channel_pbx(c) ? ast_channel_pbx(c)->dtimeoutms : 2000;
 
                        if (timeout > 0) {
                                fto = to = timeout;
@@ -161,7 +278,7 @@ enum ast_getdata_result ast_app_getdata(struct ast_channel *c, const char *promp
                         * get rid of the long timeout between
                         * prompts, and make it 50ms */
                        fto = 50;
-                       to = c->pbx ? c->pbx->dtimeoutms : 2000;
+                       to = ast_channel_pbx(c) ? ast_channel_pbx(c)->dtimeoutms : 2000;
                }
                res = ast_readstring(c, s, maxlen, to, fto, "#");
                if (res == AST_GETDATA_EMPTY_END_TERMINATED) {
@@ -183,7 +300,7 @@ int ast_app_getdata_full(struct ast_channel *c, const char *prompt, char *s, int
        int res, to = 2000, fto = 6000;
 
        if (!ast_strlen_zero(prompt)) {
-               res = ast_streamfile(c, prompt, c->language);
+               res = ast_streamfile(c, prompt, ast_channel_language(c));
                if (res < 0) {
                        return res;
                }
@@ -201,93 +318,419 @@ int ast_app_getdata_full(struct ast_channel *c, const char *prompt, char *s, int
        return res;
 }
 
-int ast_app_run_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char * const macro_name, const char * const macro_args)
+int ast_app_exec_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char *macro_args)
 {
        struct ast_app *macro_app;
        int res;
-       char buf[1024];
 
        macro_app = pbx_findapp("Macro");
        if (!macro_app) {
-               ast_log(LOG_WARNING, "Cannot run macro '%s' because the 'Macro' application in not available\n", macro_name);
+               ast_log(LOG_WARNING,
+                       "Cannot run 'Macro(%s)'.  The application is not available.\n", macro_args);
                return -1;
        }
-       snprintf(buf, sizeof(buf), "%s%s%s", macro_name, ast_strlen_zero(macro_args) ? "" : ",", S_OR(macro_args, ""));
        if (autoservice_chan) {
                ast_autoservice_start(autoservice_chan);
        }
-       res = pbx_exec(macro_chan, macro_app, buf);
+
+       ast_debug(4, "%s Original location: %s,%s,%d\n", ast_channel_name(macro_chan),
+               ast_channel_context(macro_chan), ast_channel_exten(macro_chan),
+               ast_channel_priority(macro_chan));
+
+       res = pbx_exec(macro_chan, macro_app, macro_args);
+       ast_debug(4, "Macro exited with status %d\n", res);
+
+       /*
+        * Assume anything negative from Macro is an error.
+        * Anything else is success.
+        */
+       if (res < 0) {
+               res = -1;
+       } else {
+               res = 0;
+       }
+
+       ast_debug(4, "%s Ending location: %s,%s,%d\n", ast_channel_name(macro_chan),
+               ast_channel_context(macro_chan), ast_channel_exten(macro_chan),
+               ast_channel_priority(macro_chan));
+
        if (autoservice_chan) {
                ast_autoservice_stop(autoservice_chan);
        }
        return res;
 }
 
-static int (*ast_has_voicemail_func)(const char *mailbox, const char *folder) = NULL;
-static int (*ast_inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs) = NULL;
-static int (*ast_inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs) = NULL;
-static int (*ast_sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context) = NULL;
-static int (*ast_messagecount_func)(const char *context, const char *mailbox, const char *folder) = NULL;
+int ast_app_run_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char *macro_name, const char *macro_args)
+{
+       int res;
+       char *args_str;
+       size_t args_len;
+
+       if (ast_strlen_zero(macro_args)) {
+               return ast_app_exec_macro(autoservice_chan, macro_chan, macro_name);
+       }
+
+       /* Create the Macro application argument string. */
+       args_len = strlen(macro_name) + strlen(macro_args) + 2;
+       args_str = ast_malloc(args_len);
+       if (!args_str) {
+               return -1;
+       }
+       snprintf(args_str, args_len, "%s,%s", macro_name, macro_args);
+
+       res = ast_app_exec_macro(autoservice_chan, macro_chan, args_str);
+       ast_free(args_str);
+       return res;
+}
+
+static const struct ast_app_stack_funcs *app_stack_callbacks;
+
+void ast_install_stack_functions(const struct ast_app_stack_funcs *funcs)
+{
+       app_stack_callbacks = funcs;
+}
+
+const char *ast_app_expand_sub_args(struct ast_channel *chan, const char *args)
+{
+       const struct ast_app_stack_funcs *funcs;
+       const char *new_args;
+
+       funcs = app_stack_callbacks;
+       if (!funcs || !funcs->expand_sub_args) {
+               ast_log(LOG_WARNING,
+                       "Cannot expand 'Gosub(%s)' arguments.  The app_stack module is not available.\n",
+                       args);
+               return NULL;
+       }
+       ast_module_ref(funcs->module);
+
+       new_args = funcs->expand_sub_args(chan, args);
+       ast_module_unref(funcs->module);
+       return new_args;
+}
+
+int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_args, int ignore_hangup)
+{
+       const struct ast_app_stack_funcs *funcs;
+       int res;
+
+       funcs = app_stack_callbacks;
+       if (!funcs || !funcs->run_sub) {
+               ast_log(LOG_WARNING,
+                       "Cannot run 'Gosub(%s)'.  The app_stack module is not available.\n",
+                       sub_args);
+               return -1;
+       }
+       ast_module_ref(funcs->module);
+
+       if (autoservice_chan) {
+               ast_autoservice_start(autoservice_chan);
+       }
+
+       res = funcs->run_sub(sub_chan, sub_args, ignore_hangup);
+       ast_module_unref(funcs->module);
+
+       if (autoservice_chan) {
+               ast_autoservice_stop(autoservice_chan);
+       }
+       return res;
+}
+
+int ast_app_run_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const char *sub_location, const char *sub_args, int ignore_hangup)
+{
+       int res;
+       char *args_str;
+       size_t args_len;
+
+       if (ast_strlen_zero(sub_args)) {
+               return ast_app_exec_sub(autoservice_chan, sub_chan, sub_location, ignore_hangup);
+       }
+
+       /* Create the Gosub application argument string. */
+       args_len = strlen(sub_location) + strlen(sub_args) + 3;
+       args_str = ast_malloc(args_len);
+       if (!args_str) {
+               return -1;
+       }
+       snprintf(args_str, args_len, "%s(%s)", sub_location, sub_args);
+
+       res = ast_app_exec_sub(autoservice_chan, sub_chan, args_str, ignore_hangup);
+       ast_free(args_str);
+       return res;
+}
+
+/*! \brief The container for the voicemail provider */
+static AO2_GLOBAL_OBJ_STATIC(vm_provider);
 
-void ast_install_vm_functions(int (*has_voicemail_func)(const char *mailbox, const char *folder),
-                             int (*inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs),
-                             int (*inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs),
-                             int (*messagecount_func)(const char *context, const char *mailbox, const char *folder),
-                             int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context))
+/*! Voicemail not registered warning */
+static int vm_warnings;
+
+int ast_vm_is_registered(void)
 {
-       ast_has_voicemail_func = has_voicemail_func;
-       ast_inboxcount_func = inboxcount_func;
-       ast_inboxcount2_func = inboxcount2_func;
-       ast_messagecount_func = messagecount_func;
-       ast_sayname_func = sayname_func;
+       struct ast_vm_functions *table;
+       int is_registered;
+
+       table = ao2_global_obj_ref(vm_provider);
+       is_registered = table ? 1 : 0;
+       ao2_cleanup(table);
+       return is_registered;
 }
 
-void ast_uninstall_vm_functions(void)
+int __ast_vm_register(const struct ast_vm_functions *vm_table, struct ast_module *module)
 {
-       ast_has_voicemail_func = NULL;
-       ast_inboxcount_func = NULL;
-       ast_inboxcount2_func = NULL;
-       ast_messagecount_func = NULL;
-       ast_sayname_func = NULL;
+       RAII_VAR(struct ast_vm_functions *, table, NULL, ao2_cleanup);
+
+       if (!vm_table->module_name) {
+               ast_log(LOG_ERROR, "Voicemail provider missing required information.\n");
+               return -1;
+       }
+       if (vm_table->module_version != VM_MODULE_VERSION) {
+               ast_log(LOG_ERROR, "Voicemail provider '%s' has incorrect version\n",
+                       vm_table->module_name);
+               return -1;
+       }
+
+       table = ao2_global_obj_ref(vm_provider);
+       if (table) {
+               ast_log(LOG_WARNING, "Voicemail provider already registered by %s.\n",
+                       table->module_name);
+               return -1;
+       }
+
+       table = ao2_alloc_options(sizeof(*table), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
+       if (!table) {
+               return -1;
+       }
+       *table = *vm_table;
+       table->module = module;
+
+       ao2_global_obj_replace_unref(vm_provider, table);
+       return 0;
+}
+
+void ast_vm_unregister(const char *module_name)
+{
+       struct ast_vm_functions *table;
+
+       table = ao2_global_obj_ref(vm_provider);
+       if (table && !strcmp(table->module_name, module_name)) {
+               ao2_global_obj_release(vm_provider);
+       }
+       ao2_cleanup(table);
+}
+
+#ifdef TEST_FRAMEWORK
+/*! \brief Holding container for the voicemail provider used while testing */
+static AO2_GLOBAL_OBJ_STATIC(vm_provider_holder);
+static int provider_is_swapped = 0;
+
+void ast_vm_test_swap_table_in(const struct ast_vm_functions *vm_table)
+{
+       RAII_VAR(struct ast_vm_functions *, holding_table, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_vm_functions *, new_table, NULL, ao2_cleanup);
+
+       if (provider_is_swapped) {
+               ast_log(LOG_ERROR, "Attempted to swap in test function table without swapping out old test table.\n");
+               return;
+       }
+
+       holding_table = ao2_global_obj_ref(vm_provider);
+
+       if (holding_table) {
+               ao2_global_obj_replace_unref(vm_provider_holder, holding_table);
+       }
+
+       new_table = ao2_alloc_options(sizeof(*new_table), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
+       if (!new_table) {
+               return;
+       }
+       *new_table = *vm_table;
+
+       ao2_global_obj_replace_unref(vm_provider, new_table);
+       provider_is_swapped = 1;
 }
 
-int ast_app_has_voicemail(const char *mailbox, const char *folder)
+void ast_vm_test_swap_table_out(void)
 {
-       static int warned = 0;
-       if (ast_has_voicemail_func) {
-               return ast_has_voicemail_func(mailbox, folder);
+       RAII_VAR(struct ast_vm_functions *, held_table, NULL, ao2_cleanup);
+
+       if (!provider_is_swapped) {
+               ast_log(LOG_ERROR, "Attempted to swap out test function table, but none is currently installed.\n");
+               return;
        }
 
-       if (warned++ % 10 == 0) {
-               ast_verb(3, "Message check requested for mailbox %s/folder %s but voicemail not loaded.\n", mailbox, folder ? folder : "INBOX");
+       held_table = ao2_global_obj_ref(vm_provider_holder);
+       if (!held_table) {
+               return;
        }
+
+       ao2_global_obj_replace_unref(vm_provider, held_table);
+       ao2_global_obj_release(vm_provider_holder);
+       provider_is_swapped = 0;
+}
+#endif
+
+/*! \brief The container for the voicemail greeter provider */
+static AO2_GLOBAL_OBJ_STATIC(vm_greeter_provider);
+
+/*! Voicemail greeter not registered warning */
+static int vm_greeter_warnings;
+
+int ast_vm_greeter_is_registered(void)
+{
+       struct ast_vm_greeter_functions *table;
+       int is_registered;
+
+       table = ao2_global_obj_ref(vm_greeter_provider);
+       is_registered = table ? 1 : 0;
+       ao2_cleanup(table);
+       return is_registered;
+}
+
+int __ast_vm_greeter_register(const struct ast_vm_greeter_functions *vm_table, struct ast_module *module)
+{
+       RAII_VAR(struct ast_vm_greeter_functions *, table, NULL, ao2_cleanup);
+
+       if (!vm_table->module_name) {
+               ast_log(LOG_ERROR, "Voicemail greeter provider missing required information.\n");
+               return -1;
+       }
+       if (vm_table->module_version != VM_GREETER_MODULE_VERSION) {
+               ast_log(LOG_ERROR, "Voicemail greeter provider '%s' has incorrect version\n",
+                       vm_table->module_name);
+               return -1;
+       }
+
+       table = ao2_global_obj_ref(vm_greeter_provider);
+       if (table) {
+               ast_log(LOG_WARNING, "Voicemail greeter provider already registered by %s.\n",
+                       table->module_name);
+               return -1;
+       }
+
+       table = ao2_alloc_options(sizeof(*table), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
+       if (!table) {
+               return -1;
+       }
+       *table = *vm_table;
+       table->module = module;
+
+       ao2_global_obj_replace_unref(vm_greeter_provider, table);
        return 0;
 }
 
+void ast_vm_greeter_unregister(const char *module_name)
+{
+       struct ast_vm_greeter_functions *table;
 
-int ast_app_inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
+       table = ao2_global_obj_ref(vm_greeter_provider);
+       if (table && !strcmp(table->module_name, module_name)) {
+               ao2_global_obj_release(vm_greeter_provider);
+       }
+       ao2_cleanup(table);
+}
+
+#ifdef TEST_FRAMEWORK
+static ast_vm_test_create_user_fn *ast_vm_test_create_user_func = NULL;
+static ast_vm_test_destroy_user_fn *ast_vm_test_destroy_user_func = NULL;
+
+void ast_install_vm_test_functions(ast_vm_test_create_user_fn *vm_test_create_user_func,
+       ast_vm_test_destroy_user_fn *vm_test_destroy_user_func)
 {
-       static int warned = 0;
+       ast_vm_test_create_user_func = vm_test_create_user_func;
+       ast_vm_test_destroy_user_func = vm_test_destroy_user_func;
+}
+
+void ast_uninstall_vm_test_functions(void)
+{
+       ast_vm_test_create_user_func = NULL;
+       ast_vm_test_destroy_user_func = NULL;
+}
+#endif
+
+static void vm_warn_no_provider(void)
+{
+       if (vm_warnings++ % 10 == 0) {
+               ast_verb(3, "No voicemail provider registered.\n");
+       }
+}
+
+#define VM_API_CALL(res, api_call, api_parms)                                                          \
+       do {                                                                                                                                    \
+               struct ast_vm_functions *table;                                                                         \
+               table = ao2_global_obj_ref(vm_provider);                                                        \
+               if (!table) {                                                                                                           \
+                       vm_warn_no_provider();                                                                                  \
+               } else if (table->api_call) {                                                                           \
+                       ast_module_ref(table->module);                                                                  \
+                       (res) = table->api_call api_parms;                                                              \
+                       ast_module_unref(table->module);                                                                \
+               }                                                                                                                                       \
+               ao2_cleanup(table);                                                                                                     \
+       } while (0)
+
+static void vm_greeter_warn_no_provider(void)
+{
+       if (vm_greeter_warnings++ % 10 == 0) {
+               ast_verb(3, "No voicemail greeter provider registered.\n");
+       }
+}
+
+#define VM_GREETER_API_CALL(res, api_call, api_parms)                                          \
+       do {                                                                                                                                    \
+               struct ast_vm_greeter_functions *table;                                                         \
+               table = ao2_global_obj_ref(vm_greeter_provider);                                        \
+               if (!table) {                                                                                                           \
+                       vm_greeter_warn_no_provider();                                                                  \
+               } else if (table->api_call) {                                                                           \
+                       ast_module_ref(table->module);                                                                  \
+                       (res) = table->api_call api_parms;                                                              \
+                       ast_module_unref(table->module);                                                                \
+               }                                                                                                                                       \
+               ao2_cleanup(table);                                                                                                     \
+       } while (0)
+
+int ast_app_has_voicemail(const char *mailboxes, const char *folder)
+{
+       int res = 0;
+
+       VM_API_CALL(res, has_voicemail, (mailboxes, folder));
+       return res;
+}
+
+/*!
+ * \internal
+ * \brief Function used as a callback for ast_copy_recording_to_vm when a real one isn't installed.
+ * \param vm_rec_data Stores crucial information about the voicemail that will basically just be used
+ * to figure out what the name of the recipient was supposed to be
+ */
+int ast_app_copy_recording_to_vm(struct ast_vm_recording_data *vm_rec_data)
+{
+       int res = -1;
+
+       VM_API_CALL(res, copy_recording_to_vm, (vm_rec_data));
+       return res;
+}
+
+int ast_app_inboxcount(const char *mailboxes, int *newmsgs, int *oldmsgs)
+{
+       int res = 0;
+
        if (newmsgs) {
                *newmsgs = 0;
        }
        if (oldmsgs) {
                *oldmsgs = 0;
        }
-       if (ast_inboxcount_func) {
-               return ast_inboxcount_func(mailbox, newmsgs, oldmsgs);
-       }
 
-       if (warned++ % 10 == 0) {
-               ast_verb(3, "Message count requested for mailbox %s but voicemail not loaded.\n", mailbox);
-       }
-
-       return 0;
+       VM_API_CALL(res, inboxcount, (mailboxes, newmsgs, oldmsgs));
+       return res;
 }
 
-int ast_app_inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
+int ast_app_inboxcount2(const char *mailboxes, int *urgentmsgs, int *newmsgs, int *oldmsgs)
 {
-       static int warned = 0;
+       int res = 0;
+
        if (newmsgs) {
                *newmsgs = 0;
        }
@@ -297,69 +740,153 @@ int ast_app_inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int
        if (urgentmsgs) {
                *urgentmsgs = 0;
        }
-       if (ast_inboxcount_func) {
-               return ast_inboxcount2_func(mailbox, urgentmsgs, newmsgs, oldmsgs);
-       }
 
-       if (warned++ % 10 == 0) {
-               ast_verb(3, "Message count requested for mailbox %s but voicemail not loaded.\n", mailbox);
-       }
+       VM_API_CALL(res, inboxcount2, (mailboxes, urgentmsgs, newmsgs, oldmsgs));
+       return res;
+}
 
-       return 0;
+int ast_app_sayname(struct ast_channel *chan, const char *mailbox_id)
+{
+       int res = -1;
+
+       VM_GREETER_API_CALL(res, sayname, (chan, mailbox_id));
+       return res;
 }
 
-int ast_app_sayname(struct ast_channel *chan, const char *mailbox, const char *context)
+int ast_app_messagecount(const char *mailbox_id, const char *folder)
 {
-       if (ast_sayname_func) {
-               return ast_sayname_func(chan, mailbox, context);
-       }
-       return -1;
+       int res = 0;
+
+       VM_API_CALL(res, messagecount, (mailbox_id, folder));
+       return res;
 }
 
-int ast_app_messagecount(const char *context, const char *mailbox, const char *folder)
+const char *ast_vm_index_to_foldername(int id)
 {
-       static int warned = 0;
-       if (ast_messagecount_func) {
-               return ast_messagecount_func(context, mailbox, folder);
-       }
+       const char *res = NULL;
+
+       VM_API_CALL(res, index_to_foldername, (id));
+       return res;
+}
+
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_create(const char *mailbox,
+       const char *context,
+       const char *folder,
+       int descending,
+       enum ast_vm_snapshot_sort_val sort_val,
+       int combine_INBOX_and_OLD)
+{
+       struct ast_vm_mailbox_snapshot *res = NULL;
+
+       VM_API_CALL(res, mailbox_snapshot_create, (mailbox, context, folder, descending,
+               sort_val, combine_INBOX_and_OLD));
+       return res;
+}
+
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot)
+{
+       struct ast_vm_mailbox_snapshot *res = NULL;
 
-       if (!warned) {
-               warned++;
-               ast_verb(3, "Message count requested for mailbox %s@%s/%s but voicemail not loaded.\n", mailbox, context, folder);
+       VM_API_CALL(res, mailbox_snapshot_destroy, (mailbox_snapshot));
+       return res;
+}
+
+int ast_vm_msg_move(const char *mailbox,
+       const char *context,
+       size_t num_msgs,
+       const char *oldfolder,
+       const char *old_msg_ids[],
+       const char *newfolder)
+{
+       int res = 0;
+
+       VM_API_CALL(res, msg_move, (mailbox, context, num_msgs, oldfolder, old_msg_ids,
+               newfolder));
+       return res;
+}
+
+int ast_vm_msg_remove(const char *mailbox,
+       const char *context,
+       size_t num_msgs,
+       const char *folder,
+       const char *msgs[])
+{
+       int res = 0;
+
+       VM_API_CALL(res, msg_remove, (mailbox, context, num_msgs, folder, msgs));
+       return res;
+}
+
+int ast_vm_msg_forward(const char *from_mailbox,
+       const char *from_context,
+       const char *from_folder,
+       const char *to_mailbox,
+       const char *to_context,
+       const char *to_folder,
+       size_t num_msgs,
+       const char *msg_ids[],
+       int delete_old)
+{
+       int res = 0;
+
+       VM_API_CALL(res, msg_forward, (from_mailbox, from_context, from_folder, to_mailbox,
+               to_context, to_folder, num_msgs, msg_ids, delete_old));
+       return res;
+}
+
+int ast_vm_msg_play(struct ast_channel *chan,
+       const char *mailbox,
+       const char *context,
+       const char *folder,
+       const char *msg_num,
+       ast_vm_msg_play_cb *cb)
+{
+       int res = 0;
+
+       VM_API_CALL(res, msg_play, (chan, mailbox, context, folder, msg_num, cb));
+       return res;
+}
+
+#ifdef TEST_FRAMEWORK
+int ast_vm_test_create_user(const char *context, const char *mailbox)
+{
+       if (ast_vm_test_create_user_func) {
+               return ast_vm_test_create_user_func(context, mailbox);
        }
+       return 0;
+}
 
+int ast_vm_test_destroy_user(const char *context, const char *mailbox)
+{
+       if (ast_vm_test_destroy_user_func) {
+               return ast_vm_test_destroy_user_func(context, mailbox);
+       }
        return 0;
 }
+#endif
 
-int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const char *digits, int between, unsigned int duration) 
+int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const char *digits, int between, unsigned int duration)
 {
        const char *ptr;
-       int res = 0;
+       int res;
        struct ast_silence_generator *silgen = NULL;
 
        if (!between) {
                between = 100;
        }
 
-       if (peer) {
-               res = ast_autoservice_start(peer);
-       }
-
-       if (!res) {
-               res = ast_waitfor(chan, 100);
-       }
-
-       /* ast_waitfor will return the number of remaining ms on success */
-       if (res < 0) {
-               if (peer) {
-                       ast_autoservice_stop(peer);
-               }
-               return res;
+       if (peer && ast_autoservice_start(peer)) {
+               return -1;
        }
 
+       /* Need a quiet time before sending digits. */
        if (ast_opt_transmit_silence) {
                silgen = ast_channel_start_silence_generator(chan);
        }
+       res = ast_safe_sleep(chan, 100);
+       if (res) {
+               goto dtmf_stream_cleanup;
+       }
 
        for (ptr = digits; *ptr; ptr++) {
                if (*ptr == 'w') {
@@ -367,12 +894,17 @@ int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const ch
                        if ((res = ast_safe_sleep(chan, 500))) {
                                break;
                        }
+               } else if (*ptr == 'W') {
+                       /* 'W' -- wait a second */
+                       if ((res = ast_safe_sleep(chan, 1000))) {
+                               break;
+                       }
                } else if (strchr("0123456789*#abcdfABCDF", *ptr)) {
-                       /* Character represents valid DTMF */
                        if (*ptr == 'f' || *ptr == 'F') {
                                /* ignore return values if not supported by channel */
                                ast_indicate(chan, AST_CONTROL_FLASH);
                        } else {
+                               /* Character represents valid DTMF */
                                ast_senddigit(chan, *ptr, duration);
                        }
                        /* pause between digits */
@@ -384,17 +916,13 @@ int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const ch
                }
        }
 
-       if (peer) {
-               /* Stop autoservice on the peer channel, but don't overwrite any error condition
-                  that has occurred previously while acting on the primary channel */
-               if (ast_autoservice_stop(peer) && !res) {
-                       res = -1;
-               }
-       }
-
+dtmf_stream_cleanup:
        if (silgen) {
                ast_channel_stop_silence_generator(chan, silgen);
        }
+       if (peer && ast_autoservice_stop(peer)) {
+               res = -1;
+       }
 
        return res;
 }
@@ -403,7 +931,7 @@ struct linear_state {
        int fd;
        int autoclose;
        int allowoverride;
-       int origwfmt;
+       struct ast_format *origwfmt;
 };
 
 static void linear_release(struct ast_channel *chan, void *params)
@@ -411,8 +939,10 @@ static void linear_release(struct ast_channel *chan, void *params)
        struct linear_state *ls = params;
 
        if (ls->origwfmt && ast_set_write_format(chan, ls->origwfmt)) {
-               ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, ls->origwfmt);
+               ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%s'\n",
+                       ast_channel_name(chan), ast_format_get_name(ls->origwfmt));
        }
+       ao2_cleanup(ls->origwfmt);
 
        if (ls->autoclose) {
                close(ls->fd);
@@ -427,12 +957,13 @@ static int linear_generator(struct ast_channel *chan, void *data, int len, int s
        struct linear_state *ls = data;
        struct ast_frame f = {
                .frametype = AST_FRAME_VOICE,
-               .subclass.codec = AST_FORMAT_SLINEAR,
                .data.ptr = buf + AST_FRIENDLY_OFFSET / 2,
                .offset = AST_FRIENDLY_OFFSET,
        };
        int res;
 
+       f.subclass.format = ast_format_slin;
+
        len = samples * 2;
        if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
                ast_log(LOG_WARNING, "Can't generate %d bytes of data!\n" , len);
@@ -460,15 +991,16 @@ static void *linear_alloc(struct ast_channel *chan, void *params)
 
        /* In this case, params is already malloc'd */
        if (ls->allowoverride) {
-               ast_set_flag(chan, AST_FLAG_WRITE_INT);
+               ast_set_flag(ast_channel_flags(chan), AST_FLAG_WRITE_INT);
        } else {
-               ast_clear_flag(chan, AST_FLAG_WRITE_INT);
+               ast_clear_flag(ast_channel_flags(chan), AST_FLAG_WRITE_INT);
        }
 
-       ls->origwfmt = chan->writeformat;
+       ls->origwfmt = ao2_bump(ast_channel_writeformat(chan));
 
-       if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
-               ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name);
+       if (ast_set_write_format(chan, ast_format_slin)) {
+               ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", ast_channel_name(chan));
+               ao2_cleanup(ls->origwfmt);
                ast_free(ls);
                ls = params = NULL;
        }
@@ -478,9 +1010,9 @@ static void *linear_alloc(struct ast_channel *chan, void *params)
 
 static struct ast_generator linearstream =
 {
-       alloc: linear_alloc,
-       release: linear_release,
-       generate: linear_generator,
+       .alloc = linear_alloc,
+       .release = linear_release,
+       .generate = linear_generator,
 };
 
 int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, int allowoverride)
@@ -513,10 +1045,17 @@ int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, in
        return res;
 }
 
-int ast_control_streamfile(struct ast_channel *chan, const char *file,
-                          const char *fwd, const char *rev,
-                          const char *stop, const char *suspend,
-                          const char *restart, int skipms, long *offsetms)
+static int control_streamfile(struct ast_channel *chan,
+       const char *file,
+       const char *fwd,
+       const char *rev,
+       const char *stop,
+       const char *suspend,
+       const char *restart,
+       int skipms,
+       long *offsetms,
+       const char *lang,
+       ast_waitstream_fr_cb cb)
 {
        char *breaks = NULL;
        char *end = NULL;
@@ -525,9 +1064,15 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
        long pause_restart_point = 0;
        long offset = 0;
 
+       if (!file) {
+               return -1;
+       }
        if (offsetms) {
                offset = *offsetms * 8; /* XXX Assumes 8kHz */
        }
+       if (lang == NULL) {
+               lang = ast_channel_language(chan);
+       }
 
        if (stop) {
                blen += strlen(stop);
@@ -540,7 +1085,7 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
        }
 
        if (blen > 2) {
-               breaks = alloca(blen + 1);
+               breaks = ast_alloca(blen + 1);
                breaks[0] = '\0';
                if (stop) {
                        strcat(breaks, stop);
@@ -552,25 +1097,20 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
                        strcat(breaks, restart);
                }
        }
-       if (chan->_state != AST_STATE_UP) {
-               res = ast_answer(chan);
-       }
 
-       if (file) {
-               if ((end = strchr(file, ':'))) {
-                       if (!strcasecmp(end, ":end")) {
-                               *end = '\0';
-                               end++;
-                       }
+       if ((end = strchr(file, ':'))) {
+               if (!strcasecmp(end, ":end")) {
+                       *end = '\0';
+                       end++;
                }
        }
 
        for (;;) {
                ast_stopstream(chan);
-               res = ast_streamfile(chan, file, chan->language);
+               res = ast_streamfile(chan, file, lang);
                if (!res) {
                        if (pause_restart_point) {
-                               ast_seekstream(chan->stream, pause_restart_point, SEEK_SET);
+                               ast_seekstream(ast_channel_stream(chan), pause_restart_point, SEEK_SET);
                                pause_restart_point = 0;
                        }
                        else if (end || offset < 0) {
@@ -579,15 +1119,19 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
                                }
                                ast_verb(3, "ControlPlayback seek to offset %ld from end\n", offset);
 
-                               ast_seekstream(chan->stream, offset, SEEK_END);
+                               ast_seekstream(ast_channel_stream(chan), offset, SEEK_END);
                                end = NULL;
                                offset = 0;
                        } else if (offset) {
                                ast_verb(3, "ControlPlayback seek to offset %ld\n", offset);
-                               ast_seekstream(chan->stream, offset, SEEK_SET);
+                               ast_seekstream(ast_channel_stream(chan), offset, SEEK_SET);
                                offset = 0;
                        }
-                       res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms);
+                       if (cb) {
+                               res = ast_waitstream_fr_w_cb(chan, breaks, fwd, rev, skipms, cb);
+                       } else {
+                               res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms);
+                       }
                }
 
                if (res < 1) {
@@ -595,24 +1139,37 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
                }
 
                /* We go at next loop if we got the restart char */
-               if (restart && strchr(restart, res)) {
+               if ((restart && strchr(restart, res)) || res == AST_CONTROL_STREAM_RESTART) {
                        ast_debug(1, "we'll restart the stream here at next loop\n");
                        pause_restart_point = 0;
+                       ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n"
+                               "Control: %s\r\n",
+                               ast_channel_name(chan),
+                               "Restart");
                        continue;
                }
 
-               if (suspend && strchr(suspend, res)) {
-                       pause_restart_point = ast_tellstream(chan->stream);
+               if ((suspend && strchr(suspend, res)) || res == AST_CONTROL_STREAM_SUSPEND) {
+                       pause_restart_point = ast_tellstream(ast_channel_stream(chan));
+                       ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n"
+                               "Control: %s\r\n",
+                               ast_channel_name(chan),
+                               "Pause");
                        for (;;) {
                                ast_stopstream(chan);
                                if (!(res = ast_waitfordigit(chan, 1000))) {
                                        continue;
-                               } else if (res == -1 || strchr(suspend, res) || (stop && strchr(stop, res))) {
+                               } else if (res == -1 || (suspend && strchr(suspend, res)) || (stop && strchr(stop, res))
+                                               || res == AST_CONTROL_STREAM_SUSPEND || res == AST_CONTROL_STREAM_STOP) {
                                        break;
                                }
                        }
-                       if (res == *suspend) {
+                       if ((suspend && (res == *suspend)) || res == AST_CONTROL_STREAM_SUSPEND) {
                                res = 0;
+                               ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n"
+                                       "Control: %s\r\n",
+                                       ast_channel_name(chan),
+                                       "Unpause");
                                continue;
                        }
                }
@@ -622,7 +1179,11 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
                }
 
                /* if we get one of our stop chars, return it to the calling function */
-               if (stop && strchr(stop, res)) {
+               if ((stop && strchr(stop, res)) || res == AST_CONTROL_STREAM_STOP) {
+                       ast_test_suite_event_notify("PLAYBACK","Channel: %s\r\n"
+                               "Control: %s\r\n",
+                               ast_channel_name(chan),
+                               "Stop");
                        break;
                }
        }
@@ -630,8 +1191,8 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
        if (pause_restart_point) {
                offset = pause_restart_point;
        } else {
-               if (chan->stream) {
-                       offset = ast_tellstream(chan->stream);
+               if (ast_channel_stream(chan)) {
+                       offset = ast_tellstream(ast_channel_stream(chan));
                } else {
                        offset = -8;  /* indicate end of file */
                }
@@ -641,12 +1202,172 @@ int ast_control_streamfile(struct ast_channel *chan, const char *file,
                *offsetms = offset / 8; /* samples --> ms ... XXX Assumes 8 kHz */
        }
 
-       /* If we are returning a digit cast it as char */
-       if (res > 0 || chan->stream) {
-               res = (char)res;
+       ast_stopstream(chan);
+
+       return res;
+}
+
+int ast_control_streamfile_w_cb(struct ast_channel *chan,
+       const char *file,
+       const char *fwd,
+       const char *rev,
+       const char *stop,
+       const char *suspend,
+       const char *restart,
+       int skipms,
+       long *offsetms,
+       ast_waitstream_fr_cb cb)
+{
+       return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, NULL, cb);
+}
+
+int ast_control_streamfile(struct ast_channel *chan, const char *file,
+                          const char *fwd, const char *rev,
+                          const char *stop, const char *suspend,
+                          const char *restart, int skipms, long *offsetms)
+{
+       return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, NULL, NULL);
+}
+
+int ast_control_streamfile_lang(struct ast_channel *chan, const char *file,
+       const char *fwd, const char *rev, const char *stop, const char *suspend,
+       const char *restart, int skipms, const char *lang, long *offsetms)
+{
+       return control_streamfile(chan, file, fwd, rev, stop, suspend, restart, skipms, offsetms, lang, NULL);
+}
+
+enum control_tone_frame_response_result {
+       CONTROL_TONE_RESPONSE_FAILED = -1,
+       CONTROL_TONE_RESPONSE_NORMAL = 0,
+       CONTROL_TONE_RESPONSE_FINISHED = 1,
+};
+
+static enum control_tone_frame_response_result control_tone_frame_response(struct ast_channel *chan, struct ast_frame *fr, struct ast_tone_zone_sound *ts, const char *tone, int *paused)
+{
+       switch (fr->subclass.integer) {
+       case AST_CONTROL_STREAM_STOP:
+               ast_playtones_stop(chan);
+               return CONTROL_TONE_RESPONSE_FINISHED;
+       case AST_CONTROL_STREAM_SUSPEND:
+               if (*paused) {
+                       *paused = 0;
+                       if (ast_playtones_start(chan, 0, ts ? ts->data : tone, 0)) {
+                               return CONTROL_TONE_RESPONSE_FAILED;
+                       }
+               } else {
+                       *paused = 1;
+                       ast_playtones_stop(chan);
+               }
+               return CONTROL_TONE_RESPONSE_NORMAL;
+       case AST_CONTROL_STREAM_RESTART:
+               ast_playtones_stop(chan);
+               if (ast_playtones_start(chan, 0, ts ? ts->data : tone, 0)) {
+                       return CONTROL_TONE_RESPONSE_FAILED;
+               }
+               return CONTROL_TONE_RESPONSE_NORMAL;
+       case AST_CONTROL_STREAM_REVERSE:
+               ast_log(LOG_NOTICE, "Media control operation 'reverse' not supported for media type 'tone'\n");
+               return CONTROL_TONE_RESPONSE_NORMAL;
+       case AST_CONTROL_STREAM_FORWARD:
+               ast_log(LOG_NOTICE, "Media control operation 'forward' not supported for media type 'tone'\n");
+               return CONTROL_TONE_RESPONSE_NORMAL;
+       case AST_CONTROL_HANGUP:
+       case AST_CONTROL_BUSY:
+       case AST_CONTROL_CONGESTION:
+               return CONTROL_TONE_RESPONSE_FINISHED;
+       }
+
+       return CONTROL_TONE_RESPONSE_NORMAL;
+}
+
+static int parse_tone_uri(char *tone_parser,
+       const char **tone_indication,
+       const char **tone_zone)
+{
+       *tone_indication = strsep(&tone_parser, ";");
+
+       if (ast_strlen_zero(tone_parser)) {
+               /* Only the indication is included */
+               return 0;
+       }
+
+       if (!(strncmp(tone_parser, "tonezone=", 9))) {
+               *tone_zone = tone_parser + 9;
+       } else {
+               ast_log(LOG_ERROR, "Unexpected Tone URI component: %s\n", tone_parser);
+               return -1;
+       }
+
+       return 0;
+}
+
+int ast_control_tone(struct ast_channel *chan, const char *tone)
+{
+       struct ast_tone_zone *zone = NULL;
+       struct ast_tone_zone_sound *ts;
+       int paused = 0;
+       int res = 0;
+
+       const char *tone_indication = NULL;
+       const char *tone_zone = NULL;
+       char *tone_uri_parser;
+
+       if (ast_strlen_zero(tone)) {
+               return -1;
        }
 
-       ast_stopstream(chan);
+       tone_uri_parser = ast_strdupa(tone);
+
+       if (parse_tone_uri(tone_uri_parser, &tone_indication, &tone_zone)) {
+               return -1;
+       }
+
+       if (tone_zone) {
+               zone = ast_get_indication_zone(tone_zone);
+       }
+
+       ts = ast_get_indication_tone(zone ? zone : ast_channel_zone(chan), tone_indication);
+
+       if (ast_playtones_start(chan, 0, ts ? ts->data : tone_indication, 0)) {
+               return -1;
+       }
+
+       for (;;) {
+               struct ast_frame *fr;
+
+               if (ast_waitfor(chan, -1) < 0) {
+                       res = -1;
+                       break;
+               }
+
+               fr = ast_read_noaudio(chan);
+
+               if (!fr) {
+                       res = -1;
+                       break;
+               }
+
+               if (fr->frametype != AST_FRAME_CONTROL) {
+                       continue;
+               }
+
+               res = control_tone_frame_response(chan, fr, ts, tone_indication, &paused);
+               if (res == CONTROL_TONE_RESPONSE_FINISHED) {
+                       res = 0;
+                       break;
+               } else if (res == CONTROL_TONE_RESPONSE_FAILED) {
+                       res = -1;
+                       break;
+               }
+       }
+
+       if (ts) {
+               ast_tone_zone_sound_unref(ts);
+       }
+
+       if (zone) {
+               ast_tone_zone_unref(zone);
+       }
 
        return res;
 }
@@ -655,15 +1376,87 @@ int ast_play_and_wait(struct ast_channel *chan, const char *fn)
 {
        int d = 0;
 
-       if ((d = ast_streamfile(chan, fn, chan->language))) {
+       if ((d = ast_streamfile(chan, fn, ast_channel_language(chan)))) {
                return d;
        }
 
-       d = ast_waitstream(chan, AST_DIGIT_ANY);
+       d = ast_waitstream(chan, AST_DIGIT_ANY);
+
+       ast_stopstream(chan);
+
+       return d;
+}
+
+/*!
+ * \brief Construct a silence frame of the same duration as \a orig.
+ *
+ * The \a orig frame must be \ref AST_FORMAT_SLINEAR.
+ *
+ * \param orig Frame as basis for silence to generate.
+ * \return New frame of silence; free with ast_frfree().
+ * \return \c NULL on error.
+ */
+static struct ast_frame *make_silence(const struct ast_frame *orig)
+{
+       struct ast_frame *silence;
+       size_t size;
+       size_t datalen;
+       size_t samples = 0;
+       struct ast_frame *next;
+
+       if (!orig) {
+               return NULL;
+       }
+
+       if (ast_format_cmp(orig->subclass.format, ast_format_slin) == AST_FORMAT_CMP_NOT_EQUAL) {
+               ast_log(LOG_WARNING, "Attempting to silence non-slin frame\n");
+               return NULL;
+       }
+
+       for (next = AST_LIST_NEXT(orig, frame_list);
+                orig;
+                orig = next, next = orig ? AST_LIST_NEXT(orig, frame_list) : NULL) {
+               samples += orig->samples;
+       }
+
+       ast_verb(4, "Silencing %zu samples\n", samples);
+
+
+       datalen = sizeof(short) * samples;
+       size = sizeof(*silence) + datalen;
+       silence = ast_calloc(1, size);
+       if (!silence) {
+               return NULL;
+       }
+
+       silence->mallocd = AST_MALLOCD_HDR;
+       silence->frametype = AST_FRAME_VOICE;
+       silence->data.ptr = (void *)(silence + 1);
+       silence->samples = samples;
+       silence->datalen = datalen;
+
+       silence->subclass.format = ast_format_slin;
 
-       ast_stopstream(chan);
+       return silence;
+}
 
-       return d;
+/*!
+ * \brief Sets a channel's read format to \ref AST_FORMAT_SLINEAR, recording
+ * its original format.
+ *
+ * \param chan Channel to modify.
+ * \param[out] orig_format Output variable to store channel's original read
+ *                         format.
+ * \return 0 on success.
+ * \return -1 on error.
+ */
+static int set_read_to_slin(struct ast_channel *chan, struct ast_format **orig_format)
+{
+       if (!chan || !orig_format) {
+               return -1;
+       }
+       *orig_format = ao2_bump(ast_channel_readformat(chan));
+       return ast_set_read_format(chan, ast_format_slin);
 }
 
 static int global_silence_threshold = 128;
@@ -673,19 +1466,25 @@ static int global_maxsilence = 0;
  * \param chan Channel to playback to/record from.
  * \param playfile Filename of sound to play before recording begins.
  * \param recordfile Filename to record to.
- * \param maxtime Maximum length of recording (in milliseconds).
+ * \param maxtime Maximum length of recording (in seconds).
  * \param fmt Format(s) to record message in. Multiple formats may be specified by separating them with a '|'.
  * \param duration Where to store actual length of the recorded message (in milliseconds).
+ * \param sound_duration Where to store the length of the recorded message (in milliseconds), minus any silence
  * \param beep Whether to play a beep before starting to record.
  * \param silencethreshold
  * \param maxsilence Length of silence that will end a recording (in milliseconds).
  * \param path Optional filesystem path to unlock.
- * \param prepend If true, prepend the recorded audio to an existing file.
+ * \param prepend If true, prepend the recorded audio to an existing file and follow prepend mode recording rules
  * \param acceptdtmf DTMF digits that will end the recording.
  * \param canceldtmf DTMF digits that will cancel the recording.
+ * \param skip_confirmation_sound If true, don't play auth-thankyou at end. Nice for custom recording prompts in apps.
+ *
+ * \retval -1 failure or hangup
+ * \retval 'S' Recording ended from silence timeout
+ * \retval 't' Recording ended from the message exceeding the maximum duration, or via DTMF in prepend mode
+ * \retval dtmfchar Recording ended via the return value's DTMF character for either cancel or accept.
  */
-
-static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int beep, int silencethreshold, int maxsilence, const char *path, int prepend, const char *acceptdtmf, const char *canceldtmf)
+static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence, const char *path, int prepend, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists)
 {
        int d = 0;
        char *fmts;
@@ -699,9 +1498,24 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
        int totalsilence = 0;
        int dspsilence = 0;
        int olddspsilence = 0;
-       int rfmt = 0;
+       struct ast_format *rfmt = NULL;
        struct ast_silence_generator *silgen = NULL;
-       char prependfile[80];
+       char prependfile[PATH_MAX];
+       int ioflags;    /* IO flags for writing output file */
+
+       ioflags = O_CREAT|O_WRONLY;
+
+       switch (if_exists) {
+       case AST_RECORD_IF_EXISTS_FAIL:
+               ioflags |= O_EXCL;
+               break;
+       case AST_RECORD_IF_EXISTS_OVERWRITE:
+               ioflags |= O_TRUNC;
+               break;
+       case AST_RECORD_IF_EXISTS_APPEND:
+               ioflags |= O_APPEND;
+               break;
+       }
 
        if (silencethreshold < 0) {
                silencethreshold = global_silence_threshold;
@@ -718,7 +1532,7 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
        }
 
        ast_debug(1, "play_and_record: %s, %s, '%s'\n", playfile ? playfile : "<None>", recordfile, fmt);
-       snprintf(comment, sizeof(comment), "Playing %s, Recording to: %s on %s\n", playfile ? playfile : "<None>", recordfile, chan->name);
+       snprintf(comment, sizeof(comment), "Playing %s, Recording to: %s on %s\n", playfile ? playfile : "<None>", recordfile, ast_channel_name(chan));
 
        if (playfile || beep) {
                if (!beep) {
@@ -754,7 +1568,7 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
 
        end = start = time(NULL);  /* pre-initialize end to be same as start in case we never get into loop */
        for (x = 0; x < fmtcnt; x++) {
-               others[x] = ast_writefile(prepend ? prependfile : recordfile, sfmt[x], comment, O_TRUNC, 0, AST_FILE_MODE);
+               others[x] = ast_writefile(prepend ? prependfile : recordfile, sfmt[x], comment, ioflags, 0, AST_FILE_MODE);
                ast_verb(3, "x=%d, open writing:  %s format: %s, %p\n", x, prepend ? prependfile : recordfile, sfmt[x], others[x]);
 
                if (!others[x]) {
@@ -773,11 +1587,11 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                        return -1;
                }
                ast_dsp_set_threshold(sildet, silencethreshold);
-               rfmt = chan->readformat;
-               res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
+               res = set_read_to_slin(chan, &rfmt);
                if (res < 0) {
                        ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
                        ast_dsp_free(sildet);
+                       ao2_cleanup(rfmt);
                        return -1;
                }
        }
@@ -792,15 +1606,21 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
        }
 
        if (x == fmtcnt) {
-               /* Loop forever, writing the packets we read to the writer(s), until
-                  we read a digit or get a hangup */
+               /* Loop, writing the packets we read to the writer(s), until
+                * we have reason to stop. */
                struct ast_frame *f;
+               int paused = 0;
+               int muted = 0;
+               time_t pause_start = 0;
+               int paused_secs = 0;
+               int pausedsilence = 0;
+
                for (;;) {
                        if (!(res = ast_waitfor(chan, 2000))) {
                                ast_debug(1, "One waitfor failed, trying another\n");
                                /* Try one more time in case of masq */
                                if (!(res = ast_waitfor(chan, 2000))) {
-                                       ast_log(LOG_WARNING, "No audio available on %s??\n", chan->name);
+                                       ast_log(LOG_WARNING, "No audio available on %s??\n", ast_channel_name(chan));
                                        res = -1;
                                }
                        }
@@ -814,11 +1634,29 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                        }
                        if (f->frametype == AST_FRAME_VOICE) {
                                /* write each format */
-                               for (x = 0; x < fmtcnt; x++) {
-                                       if (prepend && !others[x]) {
-                                               break;
+                               if (paused) {
+                                       /* It's all good */
+                                       res = 0;
+                               } else {
+                                       RAII_VAR(struct ast_frame *, silence, NULL, ast_frame_dtor);
+                                       struct ast_frame *orig = f;
+
+                                       if (muted) {
+                                               silence = make_silence(orig);
+                                               if (!silence) {
+                                                       ast_log(LOG_WARNING,
+                                                               "Error creating silence\n");
+                                                       break;
+                                               }
+                                               f = silence;
+                                       }
+                                       for (x = 0; x < fmtcnt; x++) {
+                                               if (prepend && !others[x]) {
+                                                       break;
+                                               }
+                                               res = ast_writestream(others[x], f);
                                        }
-                                       res = ast_writestream(others[x], f);
+                                       f = orig;
                                }
 
                                /* Silence Detection */
@@ -830,6 +1668,17 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                                        }
                                        olddspsilence = dspsilence;
 
+                                       if (paused) {
+                                               /* record how much silence there was while we are paused */
+                                               pausedsilence = dspsilence;
+                                       } else if (dspsilence > pausedsilence) {
+                                               /* ignore the paused silence */
+                                               dspsilence -= pausedsilence;
+                                       } else {
+                                               /* dspsilence has reset, reset pausedsilence */
+                                               pausedsilence = 0;
+                                       }
+
                                        if (dspsilence > maxsilence) {
                                                /* Ended happily with silence */
                                                ast_verb(3, "Recording automatically stopped after a silence of %d seconds\n", dspsilence/1000);
@@ -861,15 +1710,51 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                                        break;
                                }
                                if (strchr(canceldtmf, f->subclass.integer)) {
-                                       ast_verb(3, "User cancelled message by pressing %c\n", f->subclass.integer);
+                                       ast_verb(3, "User canceled message by pressing %c\n", f->subclass.integer);
                                        res = f->subclass.integer;
                                        outmsg = 0;
                                        break;
                                }
+                       } else if (f->frametype == AST_FRAME_CONTROL) {
+                               if (f->subclass.integer == AST_CONTROL_RECORD_CANCEL) {
+                                       ast_verb(3, "Message canceled by control\n");
+                                       outmsg = 0; /* cancels the recording */
+                                       res = 0;
+                                       break;
+                               } else if (f->subclass.integer == AST_CONTROL_RECORD_STOP) {
+                                       ast_verb(3, "Message ended by control\n");
+                                       res = 0;
+                                       break;
+                               } else if (f->subclass.integer == AST_CONTROL_RECORD_SUSPEND) {
+                                       paused = !paused;
+                                       ast_verb(3, "Message %spaused by control\n",
+                                               paused ? "" : "un");
+                                       if (paused) {
+                                               pause_start = time(NULL);
+                                       } else {
+                                               paused_secs += time(NULL) - pause_start;
+                                       }
+                               } else if (f->subclass.integer == AST_CONTROL_RECORD_MUTE) {
+                                       muted = !muted;
+                                       ast_verb(3, "Message %smuted by control\n",
+                                               muted ? "" : "un");
+                                       /* We can only silence slin frames, so
+                                        * set the mode, if we haven't already
+                                        * for sildet
+                                        */
+                                       if (muted && !rfmt) {
+                                               ast_verb(3, "Setting read format to linear mode\n");
+                                               res = set_read_to_slin(chan, &rfmt);
+                                               if (res < 0) {
+                                                       ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
+                                                       break;
+                                               }
+                                       }
+                               }
                        }
-                       if (maxtime) {
+                       if (maxtime && !paused) {
                                end = time(NULL);
-                               if (maxtime < (end - start)) {
+                               if (maxtime < (end - start - paused_secs)) {
                                        ast_verb(3, "Took too long, cutting it short...\n");
                                        res = 't';
                                        outmsg = 2;
@@ -905,6 +1790,9 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
         * closed (which would create a resource leak).
         */
        *duration = others[0] ? ast_tellstream(others[0]) / 8000 : 0;
+       if (sound_duration) {
+               *sound_duration = *duration;
+       }
 
        if (!prepend) {
                /* Reduce duration by a total silence amount */
@@ -912,11 +1800,23 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                        totalsilence += dspsilence;
                }
 
-               if (totalsilence > 0)
-                       *duration -= (totalsilence - 200) / 1000;
+               if (sound_duration) {
+                       if (totalsilence > 0) {
+                               *sound_duration -= (totalsilence - 200) / 1000;
+                       }
+                       if (*sound_duration < 0) {
+                               *sound_duration = 0;
+                       }
+               }
+
+               if (dspsilence > 0) {
+                       *duration -= (dspsilence - 200) / 1000;
+               }
+
                if (*duration < 0) {
                        *duration = 0;
                }
+
                for (x = 0; x < fmtcnt; x++) {
                        if (!others[x]) {
                                break;
@@ -927,24 +1827,26 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                         * to trim ANY part of the recording.
                         */
                        if (res > 0 && dspsilence) {
-                                /* rewind only the trailing silence */
+                               /* rewind only the trailing silence */
                                ast_stream_rewind(others[x], dspsilence - 200);
                        }
                        ast_truncstream(others[x]);
                        ast_closestream(others[x]);
                }
-       }
-
-       if (prepend && outmsg) {
+       } else if (prepend && outmsg) {
                struct ast_filestream *realfiles[AST_MAX_FORMATS];
                struct ast_frame *fr;
 
                for (x = 0; x < fmtcnt; x++) {
                        snprintf(comment, sizeof(comment), "Opening the real file %s.%s\n", recordfile, sfmt[x]);
                        realfiles[x] = ast_readfile(recordfile, sfmt[x], comment, O_RDONLY, 0, 0);
-                       if (!others[x] || !realfiles[x]) {
+                       if (!others[x]) {
                                break;
                        }
+                       if (!realfiles[x]) {
+                               ast_closestream(others[x]);
+                               continue;
+                       }
                        /*!\note Same logic as above. */
                        if (dspsilence) {
                                ast_stream_rewind(others[x], dspsilence - 200);
@@ -961,11 +1863,20 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                        ast_verb(4, "Recording Format: sfmts=%s, prependfile %s, recordfile %s\n", sfmt[x], prependfile, recordfile);
                        ast_filedelete(prependfile, sfmt[x]);
                }
+       } else {
+               for (x = 0; x < fmtcnt; x++) {
+                       if (!others[x]) {
+                               break;
+                       }
+                       ast_closestream(others[x]);
+               }
        }
+
        if (rfmt && ast_set_read_format(chan, rfmt)) {
-               ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_getformatname(rfmt), chan->name);
+               ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_format_get_name(rfmt), ast_channel_name(chan));
        }
-       if (outmsg == 2) {
+       ao2_cleanup(rfmt);
+       if ((outmsg == 2) && (!skip_confirmation_sound)) {
                ast_stream_and_wait(chan, "auth-thankyou", "");
        }
        if (sildet) {
@@ -977,19 +1888,19 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
 static const char default_acceptdtmf[] = "#";
 static const char default_canceldtmf[] = "";
 
-int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf)
+int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists)
 {
-       return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, 0, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf));
+       return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, beep, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, ""), S_OR(canceldtmf, default_canceldtmf), skip_confirmation_sound, if_exists);
 }
 
-int ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int silencethreshold, int maxsilence, const char *path)
+int ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path)
 {
-       return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, 0, silencethreshold, maxsilence, path, 0, default_acceptdtmf, default_canceldtmf);
+       return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, default_acceptdtmf, default_canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
 }
 
-int ast_play_and_prepend(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int *duration, int beep, int silencethreshold, int maxsilence)
+int ast_play_and_prepend(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence)
 {
-       return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, beep, silencethreshold, maxsilence, NULL, 1, default_acceptdtmf, default_canceldtmf);
+       return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, beep, silencethreshold, maxsilence, NULL, 1, default_acceptdtmf, default_canceldtmf, 1, AST_RECORD_IF_EXISTS_OVERWRITE);
 }
 
 /* Channel group core functions */
@@ -1042,7 +1953,7 @@ int ast_app_group_set_channel(struct ast_channel *chan, const char *data)
        AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
                if ((gi->chan == chan) && ((ast_strlen_zero(category) && ast_strlen_zero(gi->category)) || (!ast_strlen_zero(gi->category) && !strcasecmp(gi->category, category)))) {
                        AST_RWLIST_REMOVE_CURRENT(group_list);
-                       free(gi);
+                       ast_free(gi);
                        break;
                }
        }
@@ -1050,7 +1961,7 @@ int ast_app_group_set_channel(struct ast_channel *chan, const char *data)
 
        if (ast_strlen_zero(group)) {
                /* Enable unsetting the group */
-       } else if ((gi = calloc(1, len))) {
+       } else if ((gi = ast_calloc(1, len))) {
                gi->chan = chan;
                gi->group = (char *) gi + sizeof(*gi);
                strcpy(gi->group, group);
@@ -1108,6 +2019,7 @@ int ast_app_group_match_get_count(const char *groupmatch, const char *category)
 
        if (!ast_strlen_zero(category) && regcomp(&regexbuf_category, category, REG_EXTENDED | REG_NOSUB)) {
                ast_log(LOG_ERROR, "Regex compile failed on: %s\n", category);
+               regfree(&regexbuf_group);
                return 0;
        }
 
@@ -1190,14 +2102,18 @@ unsigned int __ast_app_separate_args(char *buf, char delim, int remove_chars, ch
 {
        int argc;
        char *scan, *wasdelim = NULL;
-       int paren = 0, quote = 0;
+       int paren = 0, quote = 0, bracket = 0;
 
-       if (!buf || !array || !arraylen) {
+       if (!array || !arraylen) {
                return 0;
        }
 
        memset(array, 0, arraylen * sizeof(*array));
 
+       if (!buf) {
+               return 0;
+       }
+
        scan = buf;
 
        for (argc = 0; *scan && (argc < arraylen - 1); argc++) {
@@ -1209,6 +2125,12 @@ unsigned int __ast_app_separate_args(char *buf, char delim, int remove_chars, ch
                                if (paren) {
                                        paren--;
                                }
+                       } else if (*scan == '[') {
+                               bracket++;
+                       } else if (*scan == ']') {
+                               if (bracket) {
+                                       bracket--;
+                               }
                        } else if (*scan == '"' && delim != '"') {
                                quote = quote ? 0 : 1;
                                if (remove_chars) {
@@ -1223,7 +2145,7 @@ unsigned int __ast_app_separate_args(char *buf, char delim, int remove_chars, ch
                                } else {
                                        scan++;
                                }
-                       } else if ((*scan == delim) && !paren && !quote) {
+                       } else if ((*scan == delim) && !paren && !quote && !bracket) {
                                wasdelim = scan;
                                *scan++ = '\0';
                                break;
@@ -1255,10 +2177,10 @@ static enum AST_LOCK_RESULT ast_lock_path_lockfile(const char *path)
        int lp = strlen(path);
        time_t start;
 
-       s = alloca(lp + 10);
-       fs = alloca(lp + 20);
+       s = ast_alloca(lp + 10);
+       fs = ast_alloca(lp + 20);
 
-       snprintf(fs, strlen(path) + 19, "%s/.lock-%08lx", path, ast_random());
+       snprintf(fs, strlen(path) + 19, "%s/.lock-%08lx", path, (unsigned long)ast_random());
        fd = open(fs, O_WRONLY | O_CREAT | O_EXCL, AST_FILE_MODE);
        if (fd < 0) {
                ast_log(LOG_ERROR, "Unable to create lock file '%s': %s\n", path, strerror(errno));
@@ -1288,7 +2210,7 @@ static int ast_unlock_path_lockfile(const char *path)
        char *s;
        int res;
 
-       s = alloca(strlen(path) + 10);
+       s = ast_alloca(strlen(path) + 10);
 
        snprintf(s, strlen(path) + 9, "%s/%s", path, ".lock");
 
@@ -1315,9 +2237,9 @@ static void path_lock_destroy(struct path_lock *obj)
                close(obj->fd);
        }
        if (obj->path) {
-               free(obj->path);
+               ast_free(obj->path);
        }
-       free(obj);
+       ast_free(obj);
 }
 
 static enum AST_LOCK_RESULT ast_lock_path_flock(const char *path)
@@ -1329,7 +2251,7 @@ static enum AST_LOCK_RESULT ast_lock_path_flock(const char *path)
        struct path_lock *pl;
        struct stat st, ost;
 
-       fs = alloca(strlen(path) + 20);
+       fs = ast_alloca(strlen(path) + 20);
 
        snprintf(fs, strlen(path) + 19, "%s/lock", path);
        if (lstat(fs, &st) == 0) {
@@ -1361,7 +2283,7 @@ static enum AST_LOCK_RESULT ast_lock_path_flock(const char *path)
                return AST_LOCK_FAILURE;
        }
        pl->fd = fd;
-       pl->path = strdup(path);
+       pl->path = ast_strdup(path);
 
        time(&start);
        while (
@@ -1410,7 +2332,7 @@ static int ast_unlock_path_flock(const char *path)
        char *s;
        struct path_lock *p;
 
-       s = alloca(strlen(path) + 20);
+       s = ast_alloca(strlen(path) + 20);
 
        AST_LIST_LOCK(&path_lock_list);
        AST_LIST_TRAVERSE_SAFE_BEGIN(&path_lock_list, p, le) {
@@ -1426,9 +2348,9 @@ static int ast_unlock_path_flock(const char *path)
                snprintf(s, strlen(path) + 19, "%s/lock", path);
                unlink(s);
                path_lock_destroy(p);
-               ast_log(LOG_DEBUG, "Unlocked path '%s'\n", path);
+               ast_debug(1, "Unlocked path '%s'\n", path);
        } else {
-               ast_log(LOG_DEBUG, "Failed to unlock path '%s': "
+               ast_debug(1, "Failed to unlock path '%s': "
                                "lock not found\n", path);
        }
 
@@ -1472,7 +2394,7 @@ int ast_unlock_path(const char *path)
        return r;
 }
 
-int ast_record_review(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, const char *path) 
+int ast_record_review(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, const char *path)
 {
        int silencethreshold;
        int maxsilence = 0;
@@ -1516,7 +2438,7 @@ int ast_record_review(struct ast_channel *chan, const char *playfile, const char
                        /* Record */
                        ast_verb(3, "R%secording\n", recorded == 1 ? "e-r" : "");
                        recorded = 1;
-                       if ((cmd = ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, silencethreshold, maxsilence, path)) == -1) {
+                       if ((cmd = ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, NULL, silencethreshold, maxsilence, path)) == -1) {
                                /* User has hung up, no options to give */
                                return cmd;
                        }
@@ -1612,7 +2534,7 @@ static int ivr_dispatch(struct ast_channel *chan, struct ast_ivr_option *option,
                }
                return res;
        case AST_ACTION_WAITOPTION:
-               if (!(res = ast_waitfordigit(chan, chan->pbx ? chan->pbx->rtimeoutms : 10000))) {
+               if (!(res = ast_waitfordigit(chan, ast_channel_pbx(chan) ? ast_channel_pbx(chan)->rtimeoutms : 10000))) {
                        return 't';
                }
                return res;
@@ -1636,7 +2558,7 @@ static int ivr_dispatch(struct ast_channel *chan, struct ast_ivr_option *option,
                ast_stopstream(chan);
                return res;
        default:
-               ast_log(LOG_NOTICE, "Unknown dispatch function %d, ignoring!\n", option->action);
+               ast_log(LOG_NOTICE, "Unknown dispatch function %u, ignoring!\n", option->action);
                return 0;
        }
        return -1;
@@ -1670,7 +2592,7 @@ static int read_newoption(struct ast_channel *chan, struct ast_ivr_menu *menu, c
        int res = 0;
        int ms;
        while (option_matchmore(menu, exten)) {
-               ms = chan->pbx ? chan->pbx->dtimeoutms : 5000;
+               ms = ast_channel_pbx(chan) ? ast_channel_pbx(chan)->dtimeoutms : 5000;
                if (strlen(exten) >= maxexten - 1) {
                        break;
                }
@@ -1809,13 +2731,19 @@ char *ast_read_textfile(const char *filename)
        return output;
 }
 
-int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
+static int parse_options(const struct ast_app_option *options, void *_flags, char **args, char *optstr, int flaglen)
 {
        char *s, *arg;
        int curarg, res = 0;
        unsigned int argloc;
+       struct ast_flags *flags = _flags;
+       struct ast_flags64 *flags64 = _flags;
 
-       ast_clear_flag(flags, AST_FLAGS_ALL);
+       if (flaglen == 32) {
+               ast_clear_flag(flags, AST_FLAGS_ALL);
+       } else {
+               flags64->flags = 0;
+       }
 
        if (!optstr) {
                return 0;
@@ -1826,8 +2754,40 @@ int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags
                curarg = *s++ & 0x7f;   /* the array (in app.h) has 128 entries */
                argloc = options[curarg].arg_index;
                if (*s == '(') {
+                       int paren = 1, quote = 0;
+                       int parsequotes = (s[1] == '"') ? 1 : 0;
+
                        /* Has argument */
                        arg = ++s;
+                       for (; *s; s++) {
+                               if (*s == '(' && !quote) {
+                                       paren++;
+                               } else if (*s == ')' && !quote) {
+                                       /* Count parentheses, unless they're within quotes (or backslashed, below) */
+                                       paren--;
+                               } else if (*s == '"' && parsequotes) {
+                                       /* Leave embedded quotes alone, unless they are the first character */
+                                       quote = quote ? 0 : 1;
+                                       ast_copy_string(s, s + 1, INT_MAX);
+                                       s--;
+                               } else if (*s == '\\') {
+                                       if (!quote) {
+                                               /* If a backslash is found outside of quotes, remove it */
+                                               ast_copy_string(s, s + 1, INT_MAX);
+                                       } else if (quote && s[1] == '"') {
+                                               /* Backslash for a quote character within quotes, remove the backslash */
+                                               ast_copy_string(s, s + 1, INT_MAX);
+                                       } else {
+                                               /* Backslash within quotes, keep both characters */
+                                               s++;
+                                       }
+                               }
+
+                               if (paren == 0) {
+                                       break;
+                               }
+                       }
+                       /* This will find the closing paren we found above, or none, if the string ended before we found one. */
                        if ((s = strchr(s, ')'))) {
                                if (argloc) {
                                        args[argloc - 1] = arg;
@@ -1841,48 +2801,24 @@ int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags
                } else if (argloc) {
                        args[argloc - 1] = "";
                }
-               ast_set_flag(flags, options[curarg].flag);
+               if (flaglen == 32) {
+                       ast_set_flag(flags, options[curarg].flag);
+               } else {
+                       ast_set_flag64(flags64, options[curarg].flag);
+               }
        }
 
        return res;
 }
 
-int ast_app_parse_options64(const struct ast_app_option *options, struct ast_flags64 *flags, char **args, char *optstr)
+int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
 {
-       char *s, *arg;
-       int curarg, res = 0;
-       unsigned int argloc;
-
-       flags->flags = 0;
-
-       if (!optstr) {
-               return 0;
-       }
-
-       s = optstr;
-       while (*s) {
-               curarg = *s++ & 0x7f;   /* the array (in app.h) has 128 entries */
-               ast_set_flag64(flags, options[curarg].flag);
-               argloc = options[curarg].arg_index;
-               if (*s == '(') {
-                       /* Has argument */
-                       arg = ++s;
-                       if ((s = strchr(s, ')'))) {
-                               if (argloc) {
-                                       args[argloc - 1] = arg;
-                               }
-                               *s++ = '\0';
-                       } else {
-                               ast_log(LOG_WARNING, "Missing closing parenthesis for argument '%c' in string '%s'\n", curarg, arg);
-                               res = -1;
-                               break;
-                       }
-               } else if (argloc) {
-                       args[argloc - 1] = NULL;
-               }
-       }
+       return parse_options(options, flags, args, optstr, 32);
+}
 
-       return res;
+int ast_app_parse_options64(const struct ast_app_option *options, struct ast_flags64 *flags, char **args, char *optstr)
+{
+       return parse_options(options, flags, args, optstr, 64);
 }
 
 void ast_app_options2str64(const struct ast_app_option *options, struct ast_flags64 *flags, char *buf, size_t len)
@@ -2026,7 +2962,9 @@ int ast_safe_fork(int stop_reaper)
                ast_replace_sigchld();
        }
 
-       sigfillset(&signal_set);
+       /* GCC 4.9 gives a bogus "right-hand operand of comma expression has
+        * no effect" warning */
+       (void) sigfillset(&signal_set);
        pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
 
        pid = fork();
@@ -2034,6 +2972,21 @@ int ast_safe_fork(int stop_reaper)
        if (pid != 0) {
                /* Fork failed or parent */
                pthread_sigmask(SIG_SETMASK, &old_set, NULL);
+               if (!stop_reaper && pid > 0) {
+                       struct zombie *cur = ast_calloc(1, sizeof(*cur));
+                       if (cur) {
+                               cur->pid = pid;
+                               AST_LIST_LOCK(&zombies);
+                               AST_LIST_INSERT_TAIL(&zombies, cur, list);
+                               AST_LIST_UNLOCK(&zombies);
+                               if (shaun_of_the_dead_thread == AST_PTHREADT_NULL) {
+                                       if (ast_pthread_create_background(&shaun_of_the_dead_thread, NULL, shaun_of_the_dead, NULL)) {
+                                               ast_log(LOG_ERROR, "Shaun of the Dead wants to kill zombies, but can't?!!\n");
+                                               shaun_of_the_dead_thread = AST_PTHREADT_NULL;
+                                       }
+                               }
+                       }
+               }
                return pid;
        } else {
                /* Child */
@@ -2085,7 +3038,9 @@ int ast_app_parse_timelen(const char *timestr, int *result, enum ast_timelen uni
                return -1;
        }
 
-       if ((res = sscanf(timestr, FMT, &amount, u)) == 0) {
+       res = sscanf(timestr, FMT, &amount, u);
+
+       if (res == 0 || res == EOF) {
 #undef FMT
                return -1;
        } else if (res == 2) {
@@ -2126,3 +3081,318 @@ int ast_app_parse_timelen(const char *timestr, int *result, enum ast_timelen uni
        return 0;
 }
 
+
+
+static void mwi_state_dtor(void *obj)
+{
+       struct ast_mwi_state *mwi_state = obj;
+       ast_string_field_free_memory(mwi_state);
+       ao2_cleanup(mwi_state->snapshot);
+       mwi_state->snapshot = NULL;
+}
+
+struct stasis_topic *ast_mwi_topic_all(void)
+{
+       return mwi_topic_all;
+}
+
+struct stasis_cache *ast_mwi_state_cache(void)
+{
+       return mwi_state_cache;
+}
+
+struct stasis_topic *ast_mwi_topic_cached(void)
+{
+       return stasis_caching_get_topic(mwi_topic_cached);
+}
+
+struct stasis_topic *ast_mwi_topic(const char *uniqueid)
+{
+       return stasis_topic_pool_get_topic(mwi_topic_pool, uniqueid);
+}
+
+struct ast_mwi_state *ast_mwi_create(const char *mailbox, const char *context)
+{
+       RAII_VAR(struct ast_mwi_state *, mwi_state, NULL, ao2_cleanup);
+
+       ast_assert(!ast_strlen_zero(mailbox));
+
+       mwi_state = ao2_alloc(sizeof(*mwi_state), mwi_state_dtor);
+       if (!mwi_state) {
+               return NULL;
+       }
+
+       if (ast_string_field_init(mwi_state, 256)) {
+               return NULL;
+       }
+       if (!ast_strlen_zero(context)) {
+               ast_string_field_build(mwi_state, uniqueid, "%s@%s", mailbox, context);
+       } else {
+               ast_string_field_set(mwi_state, uniqueid, mailbox);
+       }
+
+       ao2_ref(mwi_state, +1);
+       return mwi_state;
+}
+
+/*!
+ * \internal
+ * \brief Create a MWI state snapshot message.
+ * \since 12.2.0
+ *
+ * \param[in] mailbox The mailbox identifier string.
+ * \param[in] context The context this mailbox resides in (NULL or "" if only using mailbox)
+ * \param[in] new_msgs The number of new messages in this mailbox
+ * \param[in] old_msgs The number of old messages in this mailbox
+ * \param[in] channel_id A unique identifier for a channel associated with this
+ * change in mailbox state
+ * \param[in] eid The EID of the server that originally published the message
+ *
+ * \retval message on success.  Use ao2_cleanup() when done with it.
+ * \retval NULL on error.
+ */
+static struct stasis_message *mwi_state_create_message(
+       const char *mailbox,
+       const char *context,
+       int new_msgs,
+       int old_msgs,
+       const char *channel_id,
+       struct ast_eid *eid)
+{
+       struct ast_mwi_state *mwi_state;
+       struct stasis_message *message;
+
+       if (!ast_mwi_state_type()) {
+               return NULL;
+       }
+
+       mwi_state = ast_mwi_create(mailbox, context);
+       if (!mwi_state) {
+               return NULL;
+       }
+
+       mwi_state->new_msgs = new_msgs;
+       mwi_state->old_msgs = old_msgs;
+
+       if (!ast_strlen_zero(channel_id)) {
+               struct stasis_message *chan_message;
+
+               chan_message = stasis_cache_get(ast_channel_cache(), ast_channel_snapshot_type(),
+                       channel_id);
+               if (chan_message) {
+                       mwi_state->snapshot = stasis_message_data(chan_message);
+                       ao2_ref(mwi_state->snapshot, +1);
+               }
+               ao2_cleanup(chan_message);
+       }
+
+       if (eid) {
+               mwi_state->eid = *eid;
+       } else {
+               mwi_state->eid = ast_eid_default;
+       }
+
+       /*
+        * XXX As far as stasis is concerned, all MWI events are local.
+        *
+        * We may in the future want to make MWI aggregate local/remote
+        * message counts similar to how device state aggregates state.
+        */
+       message = stasis_message_create_full(ast_mwi_state_type(), mwi_state, &ast_eid_default);
+       ao2_cleanup(mwi_state);
+       return message;
+}
+
+int ast_publish_mwi_state_full(
+       const char *mailbox,
+       const char *context,
+       int new_msgs,
+       int old_msgs,
+       const char *channel_id,
+       struct ast_eid *eid)
+{
+       struct ast_mwi_state *mwi_state;
+       RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
+       struct stasis_topic *mailbox_specific_topic;
+
+       message = mwi_state_create_message(mailbox, context, new_msgs, old_msgs, channel_id, eid);
+       if (!message) {
+               return -1;
+       }
+
+       mwi_state = stasis_message_data(message);
+       mailbox_specific_topic = ast_mwi_topic(mwi_state->uniqueid);
+       if (!mailbox_specific_topic) {
+               return -1;
+       }
+
+       stasis_publish(mailbox_specific_topic, message);
+
+       return 0;
+}
+
+int ast_delete_mwi_state_full(const char *mailbox, const char *context, struct ast_eid *eid)
+{
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+       struct stasis_message *cached_msg;
+       struct stasis_message *clear_msg;
+       struct ast_mwi_state *mwi_state;
+       struct stasis_topic *mailbox_specific_topic;
+
+       msg = mwi_state_create_message(mailbox, context, 0, 0, NULL, eid);
+       if (!msg) {
+               return -1;
+       }
+
+       mwi_state = stasis_message_data(msg);
+
+       /*
+        * XXX As far as stasis is concerned, all MWI events are local.
+        *
+        * For now, it is assumed that there is only one entity
+        * maintaining the state of a particular mailbox.
+        *
+        * If we ever have multiple MWI event entities maintaining
+        * the same mailbox that wish to delete their cached entry
+        * we will need to do something about the race condition
+        * potential between checking the cache and removing the
+        * cache entry.
+        */
+       cached_msg = stasis_cache_get_by_eid(ast_mwi_state_cache(),
+               ast_mwi_state_type(), mwi_state->uniqueid, &ast_eid_default);
+       if (!cached_msg) {
+               /* Nothing to clear */
+               return -1;
+       }
+       ao2_cleanup(cached_msg);
+
+       mailbox_specific_topic = ast_mwi_topic(mwi_state->uniqueid);
+       if (!mailbox_specific_topic) {
+               return -1;
+       }
+
+       clear_msg = stasis_cache_clear_create(msg);
+       if (clear_msg) {
+               stasis_publish(mailbox_specific_topic, clear_msg);
+       }
+       ao2_cleanup(clear_msg);
+       return 0;
+}
+
+static const char *mwi_state_get_id(struct stasis_message *message)
+{
+       if (ast_mwi_state_type() == stasis_message_type(message)) {
+               struct ast_mwi_state *mwi_state = stasis_message_data(message);
+               return mwi_state->uniqueid;
+       } else if (stasis_subscription_change_type() == stasis_message_type(message)) {
+               struct stasis_subscription_change *change = stasis_message_data(message);
+               return change->uniqueid;
+       }
+
+       return NULL;
+}
+
+static void mwi_blob_dtor(void *obj)
+{
+       struct ast_mwi_blob *mwi_blob = obj;
+
+       ao2_cleanup(mwi_blob->mwi_state);
+       ast_json_unref(mwi_blob->blob);
+}
+
+struct stasis_message *ast_mwi_blob_create(struct ast_mwi_state *mwi_state,
+                                              struct stasis_message_type *message_type,
+                                              struct ast_json *blob)
+{
+       RAII_VAR(struct ast_mwi_blob *, obj, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+       ast_assert(blob != NULL);
+
+       if (!message_type) {
+               return NULL;
+       }
+
+       obj = ao2_alloc(sizeof(*obj), mwi_blob_dtor);
+       if (!obj) {
+               return NULL;
+       }
+
+       obj->mwi_state = mwi_state;
+       ao2_ref(obj->mwi_state, +1);
+       obj->blob = ast_json_ref(blob);
+
+       /* This is not a normal MWI event.  Only used by the MinivmNotify app. */
+       msg = stasis_message_create(message_type, obj);
+       if (!msg) {
+               return NULL;
+       }
+
+       ao2_ref(msg, +1);
+       return msg;
+}
+
+struct stasis_topic *ast_queue_topic_all(void)
+{
+       return queue_topic_all;
+}
+
+struct stasis_topic *ast_queue_topic(const char *queuename)
+{
+       return stasis_topic_pool_get_topic(queue_topic_pool, queuename);
+}
+
+static void app_cleanup(void)
+{
+       ao2_cleanup(queue_topic_pool);
+       queue_topic_pool = NULL;
+       ao2_cleanup(queue_topic_all);
+       queue_topic_all = NULL;
+       ao2_cleanup(mwi_topic_pool);
+       mwi_topic_pool = NULL;
+       ao2_cleanup(mwi_topic_all);
+       mwi_topic_all = NULL;
+       ao2_cleanup(mwi_state_cache);
+       mwi_state_cache = NULL;
+       mwi_topic_cached = stasis_caching_unsubscribe_and_join(mwi_topic_cached);
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_mwi_state_type);
+       STASIS_MESSAGE_TYPE_CLEANUP(ast_mwi_vm_app_type);
+}
+
+int app_init(void)
+{
+       ast_register_cleanup(app_cleanup);
+
+       if (STASIS_MESSAGE_TYPE_INIT(ast_mwi_state_type) != 0) {
+               return -1;
+       }
+       if (STASIS_MESSAGE_TYPE_INIT(ast_mwi_vm_app_type) != 0) {
+               return -1;
+       }
+       mwi_topic_all = stasis_topic_create("stasis_mwi_topic");
+       if (!mwi_topic_all) {
+               return -1;
+       }
+       mwi_state_cache = stasis_cache_create(mwi_state_get_id);
+       if (!mwi_state_cache) {
+               return -1;
+       }
+       mwi_topic_cached = stasis_caching_topic_create(mwi_topic_all, mwi_state_cache);
+       if (!mwi_topic_cached) {
+               return -1;
+       }
+       mwi_topic_pool = stasis_topic_pool_create(mwi_topic_all);
+       if (!mwi_topic_pool) {
+               return -1;
+       }
+       queue_topic_all = stasis_topic_create("stasis_queue_topic");
+       if (!queue_topic_all) {
+               return -1;
+       }
+       queue_topic_pool = stasis_topic_pool_create(queue_topic_all);
+       if (!queue_topic_pool) {
+               return -1;
+       }
+       return 0;
+}
+