Replace most uses of ast_register_atexit with ast_register_cleanup.
[asterisk/asterisk.git] / main / app.c
index 9e78417..f3fc298 100644 (file)
@@ -70,6 +70,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_channels.h"
 #include "asterisk/json.h"
+#include "asterisk/format_cache.h"
 
 #define MWI_TOPIC_BUCKETS 57
 
@@ -96,10 +97,43 @@ 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);
+STASIS_MESSAGE_TYPE_DEFN(ast_mwi_state_type,
+       .to_event = mwi_to_event, );
 STASIS_MESSAGE_TYPE_DEFN(ast_mwi_vm_app_type);
 /* @} */
 
@@ -223,7 +257,6 @@ enum ast_getdata_result ast_app_getdata(struct ast_channel *c, const char *promp
 
        filename = ast_strdupa(prompt);
        while ((front = strsep(&filename, "&"))) {
-               ast_test_suite_event_notify("PLAYBACK", "Message: %s\r\nChannel: %s", front, ast_channel_name(c));
                if (!ast_strlen_zero(front)) {
                        res = ast_streamfile(c, front, ast_channel_language(c));
                        if (res)
@@ -426,64 +459,175 @@ int ast_app_run_sub(struct ast_channel *autoservice_chan, struct ast_channel *su
        return res;
 }
 
-static ast_has_voicemail_fn *ast_has_voicemail_func = NULL;
-static ast_inboxcount_fn *ast_inboxcount_func = NULL;
-static ast_inboxcount2_fn *ast_inboxcount2_func = NULL;
-static ast_sayname_fn *ast_sayname_func = NULL;
-static ast_messagecount_fn *ast_messagecount_func = NULL;
-static ast_copy_recording_to_vm_fn *ast_copy_recording_to_vm_func = NULL;
-static ast_vm_index_to_foldername_fn *ast_vm_index_to_foldername_func = NULL;
-static ast_vm_mailbox_snapshot_create_fn *ast_vm_mailbox_snapshot_create_func = NULL;
-static ast_vm_mailbox_snapshot_destroy_fn *ast_vm_mailbox_snapshot_destroy_func = NULL;
-static ast_vm_msg_move_fn *ast_vm_msg_move_func = NULL;
-static ast_vm_msg_remove_fn *ast_vm_msg_remove_func = NULL;
-static ast_vm_msg_forward_fn *ast_vm_msg_forward_func = NULL;
-static ast_vm_msg_play_fn *ast_vm_msg_play_func = NULL;
-
-void ast_install_vm_functions(ast_has_voicemail_fn *has_voicemail_func,
-       ast_inboxcount_fn *inboxcount_func,
-       ast_inboxcount2_fn *inboxcount2_func,
-       ast_messagecount_fn *messagecount_func,
-       ast_sayname_fn *sayname_func,
-       ast_copy_recording_to_vm_fn *copy_recording_to_vm_func,
-       ast_vm_index_to_foldername_fn *vm_index_to_foldername_func,
-       ast_vm_mailbox_snapshot_create_fn *vm_mailbox_snapshot_create_func,
-       ast_vm_mailbox_snapshot_destroy_fn *vm_mailbox_snapshot_destroy_func,
-       ast_vm_msg_move_fn *vm_msg_move_func,
-       ast_vm_msg_remove_fn *vm_msg_remove_func,
-       ast_vm_msg_forward_fn *vm_msg_forward_func,
-       ast_vm_msg_play_fn *vm_msg_play_func)
-{
-       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;
-       ast_copy_recording_to_vm_func = copy_recording_to_vm_func;
-       ast_vm_index_to_foldername_func = vm_index_to_foldername_func;
-       ast_vm_mailbox_snapshot_create_func = vm_mailbox_snapshot_create_func;
-       ast_vm_mailbox_snapshot_destroy_func = vm_mailbox_snapshot_destroy_func;
-       ast_vm_msg_move_func = vm_msg_move_func;
-       ast_vm_msg_remove_func = vm_msg_remove_func;
-       ast_vm_msg_forward_func = vm_msg_forward_func;
-       ast_vm_msg_play_func = vm_msg_play_func;
-}
-
-void ast_uninstall_vm_functions(void)
-{
-       ast_has_voicemail_func = NULL;
-       ast_inboxcount_func = NULL;
-       ast_inboxcount2_func = NULL;
-       ast_messagecount_func = NULL;
-       ast_sayname_func = NULL;
-       ast_copy_recording_to_vm_func = NULL;
-       ast_vm_index_to_foldername_func = NULL;
-       ast_vm_mailbox_snapshot_create_func = NULL;
-       ast_vm_mailbox_snapshot_destroy_func = NULL;
-       ast_vm_msg_move_func = NULL;
-       ast_vm_msg_remove_func = NULL;
-       ast_vm_msg_forward_func = NULL;
-       ast_vm_msg_play_func = NULL;
+/*! \brief The container for the voicemail provider */
+static AO2_GLOBAL_OBJ_STATIC(vm_provider);
+
+/*! Voicemail not registered warning */
+static int vm_warnings;
+
+int ast_vm_is_registered(void)
+{
+       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;
+}
+
+int __ast_vm_register(const struct ast_vm_functions *vm_table, struct ast_module *module)
+{
+       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;
+}
+
+void ast_vm_test_swap_table_out(void)
+{
+       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;
+       }
+
+       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;
+
+       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
@@ -504,17 +648,54 @@ void ast_uninstall_vm_test_functions(void)
 }
 #endif
 
-int ast_app_has_voicemail(const char *mailbox, const char *folder)
+static void vm_warn_no_provider(void)
 {
-       static int warned = 0;
-       if (ast_has_voicemail_func) {
-               return ast_has_voicemail_func(mailbox, folder);
+       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)
 
-       if (warned++ % 10 == 0) {
-               ast_verb(3, "Message check requested for mailbox %s/folder %s but voicemail not loaded.\n", mailbox, folder ? folder : "INBOX");
+static void vm_greeter_warn_no_provider(void)
+{
+       if (vm_greeter_warnings++ % 10 == 0) {
+               ast_verb(3, "No voicemail greeter provider registered.\n");
        }
-       return 0;
+}
+
+#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;
 }
 
 /*!
@@ -525,44 +706,31 @@ int ast_app_has_voicemail(const char *mailbox, const char *folder)
  */
 int ast_app_copy_recording_to_vm(struct ast_vm_recording_data *vm_rec_data)
 {
-       static int warned = 0;
-
-       if (ast_copy_recording_to_vm_func) {
-               return ast_copy_recording_to_vm_func(vm_rec_data);
-       }
-
-       if (warned++ % 10 == 0) {
-               ast_verb(3, "copy recording to voicemail called to copy %s.%s to %s@%s, but voicemail not loaded.\n",
-                       vm_rec_data->recording_file, vm_rec_data->recording_ext,
-                       vm_rec_data->mailbox, vm_rec_data->context);
-       }
+       int res = -1;
 
-       return -1;
+       VM_API_CALL(res, copy_recording_to_vm, (vm_rec_data));
+       return res;
 }
 
-int ast_app_inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
+int ast_app_inboxcount(const char *mailboxes, int *newmsgs, int *oldmsgs)
 {
-       static int warned = 0;
+       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;
        }
@@ -572,46 +740,33 @@ int ast_app_inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int
        if (urgentmsgs) {
                *urgentmsgs = 0;
        }
-       if (ast_inboxcount2_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);
-       }
 
-       return 0;
+       VM_API_CALL(res, inboxcount2, (mailboxes, urgentmsgs, newmsgs, oldmsgs));
+       return res;
 }
 
-int ast_app_sayname(struct ast_channel *chan, const char *mailbox, const char *context)
+int ast_app_sayname(struct ast_channel *chan, const char *mailbox_id)
 {
-       if (ast_sayname_func) {
-               return ast_sayname_func(chan, mailbox, context);
-       }
-       return -1;
+       int res = -1;
+
+       VM_GREETER_API_CALL(res, sayname, (chan, mailbox_id));
+       return res;
 }
 
-int ast_app_messagecount(const char *context, const char *mailbox, const char *folder)
+int ast_app_messagecount(const char *mailbox_id, const char *folder)
 {
-       static int warned = 0;
-       if (ast_messagecount_func) {
-               return ast_messagecount_func(context, mailbox, folder);
-       }
-
-       if (!warned) {
-               warned++;
-               ast_verb(3, "Message count requested for mailbox %s@%s/%s but voicemail not loaded.\n", mailbox, context, folder);
-       }
+       int res = 0;
 
-       return 0;
+       VM_API_CALL(res, messagecount, (mailbox_id, folder));
+       return res;
 }
 
 const char *ast_vm_index_to_foldername(int id)
 {
-       if (ast_vm_index_to_foldername_func) {
-               return ast_vm_index_to_foldername_func(id);
-       }
-       return NULL;
+       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,
@@ -621,18 +776,19 @@ struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_create(const char *mailb
        enum ast_vm_snapshot_sort_val sort_val,
        int combine_INBOX_and_OLD)
 {
-       if (ast_vm_mailbox_snapshot_create_func) {
-               return ast_vm_mailbox_snapshot_create_func(mailbox, context, folder, descending, sort_val, combine_INBOX_and_OLD);
-       }
-       return NULL;
+       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)
 {
-       if (ast_vm_mailbox_snapshot_destroy_func) {
-               return ast_vm_mailbox_snapshot_destroy_func(mailbox_snapshot);
-       }
-       return NULL;
+       struct ast_vm_mailbox_snapshot *res = NULL;
+
+       VM_API_CALL(res, mailbox_snapshot_destroy, (mailbox_snapshot));
+       return res;
 }
 
 int ast_vm_msg_move(const char *mailbox,
@@ -642,10 +798,11 @@ int ast_vm_msg_move(const char *mailbox,
        const char *old_msg_ids[],
        const char *newfolder)
 {
-       if (ast_vm_msg_move_func) {
-               return ast_vm_msg_move_func(mailbox, context, num_msgs, oldfolder, old_msg_ids, newfolder);
-       }
-       return 0;
+       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,
@@ -654,10 +811,10 @@ int ast_vm_msg_remove(const char *mailbox,
        const char *folder,
        const char *msgs[])
 {
-       if (ast_vm_msg_remove_func) {
-               return ast_vm_msg_remove_func(mailbox, context, num_msgs, folder, msgs);
-       }
-       return 0;
+       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,
@@ -670,10 +827,11 @@ int ast_vm_msg_forward(const char *from_mailbox,
        const char *msg_ids[],
        int delete_old)
 {
-       if (ast_vm_msg_forward_func) {
-               return ast_vm_msg_forward_func(from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder, num_msgs, msg_ids, delete_old);
-       }
-       return 0;
+       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,
@@ -683,10 +841,10 @@ int ast_vm_msg_play(struct ast_channel *chan,
        const char *msg_num,
        ast_vm_msg_play_cb *cb)
 {
-       if (ast_vm_msg_play_func) {
-               return ast_vm_msg_play_func(chan, mailbox, context, folder, msg_num, cb);
-       }
-       return 0;
+       int res = 0;
+
+       VM_API_CALL(res, msg_play, (chan, mailbox, context, folder, msg_num, cb));
+       return res;
 }
 
 #ifdef TEST_FRAMEWORK
@@ -773,16 +931,18 @@ struct linear_state {
        int fd;
        int autoclose;
        int allowoverride;
-       struct ast_format origwfmt;
+       struct ast_format *origwfmt;
 };
 
 static void linear_release(struct ast_channel *chan, void *params)
 {
        struct linear_state *ls = params;
 
-       if (ls->origwfmt.id && ast_set_write_format(chan, &ls->origwfmt)) {
-               ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", ast_channel_name(chan), ls->origwfmt.id);
+       if (ls->origwfmt && ast_set_write_format(chan, 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);
@@ -802,7 +962,7 @@ static int linear_generator(struct ast_channel *chan, void *data, int len, int s
        };
        int res;
 
-       ast_format_set(&f.subclass.format, AST_FORMAT_SLINEAR, 0);
+       f.subclass.format = ast_format_slin;
 
        len = samples * 2;
        if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
@@ -836,10 +996,11 @@ static void *linear_alloc(struct ast_channel *chan, void *params)
                ast_clear_flag(ast_channel_flags(chan), AST_FLAG_WRITE_INT);
        }
 
-       ast_format_copy(&ls->origwfmt, ast_channel_writeformat(chan));
+       ls->origwfmt = ao2_bump(ast_channel_writeformat(chan));
 
-       if (ast_set_write_format_by_id(chan, AST_FORMAT_SLINEAR)) {
+       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;
        }
@@ -936,9 +1097,6 @@ static int control_streamfile(struct ast_channel *chan,
                        strcat(breaks, restart);
                }
        }
-       if (ast_channel_state(chan) != AST_STATE_UP) {
-               res = ast_answer(chan);
-       }
 
        if ((end = strchr(file, ':'))) {
                if (!strcasecmp(end, ":end")) {
@@ -1078,11 +1236,146 @@ int ast_control_streamfile_lang(struct ast_channel *chan, const char *file,
        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;
+       }
+
+       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;
+}
+
 int ast_play_and_wait(struct ast_channel *chan, const char *fn)
 {
        int d = 0;
 
-       ast_test_suite_event_notify("PLAYBACK", "Message: %s\r\nChannel: %s", fn, ast_channel_name(chan));
        if ((d = ast_streamfile(chan, fn, ast_channel_language(chan)))) {
                return d;
        }
@@ -1115,7 +1408,7 @@ static struct ast_frame *make_silence(const struct ast_frame *orig)
                return NULL;
        }
 
-       if (orig->subclass.format.id != AST_FORMAT_SLINEAR) {
+       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;
        }
@@ -1126,7 +1419,7 @@ static struct ast_frame *make_silence(const struct ast_frame *orig)
                samples += orig->samples;
        }
 
-       ast_verb(4, "Silencing %zd samples\n", samples);
+       ast_verb(4, "Silencing %zu samples\n", samples);
 
 
        datalen = sizeof(short) * samples;
@@ -1142,7 +1435,7 @@ static struct ast_frame *make_silence(const struct ast_frame *orig)
        silence->samples = samples;
        silence->datalen = datalen;
 
-       ast_format_set(&silence->subclass.format, AST_FORMAT_SLINEAR, 0);
+       silence->subclass.format = ast_format_slin;
 
        return silence;
 }
@@ -1157,13 +1450,13 @@ static struct ast_frame *make_silence(const struct ast_frame *orig)
  * \return 0 on success.
  * \return -1 on error.
  */
-static int set_read_to_slin(struct ast_channel *chan, struct ast_format *orig_format)
+static int set_read_to_slin(struct ast_channel *chan, struct ast_format **orig_format)
 {
        if (!chan || !orig_format) {
                return -1;
        }
-       ast_format_copy(orig_format, ast_channel_readformat(chan));
-       return ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR);
+       *orig_format = ao2_bump(ast_channel_readformat(chan));
+       return ast_set_read_format(chan, ast_format_slin);
 }
 
 static int global_silence_threshold = 128;
@@ -1205,7 +1498,7 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
        int totalsilence = 0;
        int dspsilence = 0;
        int olddspsilence = 0;
-       struct ast_format rfmt;
+       struct ast_format *rfmt = NULL;
        struct ast_silence_generator *silgen = NULL;
        char prependfile[PATH_MAX];
        int ioflags;    /* IO flags for writing output file */
@@ -1224,7 +1517,6 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                break;
        }
 
-       ast_format_clear(&rfmt);
        if (silencethreshold < 0) {
                silencethreshold = global_silence_threshold;
        }
@@ -1299,6 +1591,7 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                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;
                }
        }
@@ -1449,7 +1742,7 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                                         * set the mode, if we haven't already
                                         * for sildet
                                         */
-                                       if (muted && !rfmt.id) {
+                                       if (muted && !rfmt) {
                                                ast_verb(3, "Setting read format to linear mode\n");
                                                res = set_read_to_slin(chan, &rfmt);
                                                if (res < 0) {
@@ -1540,18 +1833,20 @@ static int __ast_play_and_record(struct ast_channel *chan, const char *playfile,
                        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);
@@ -1568,10 +1863,19 @@ 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.id && ast_set_read_format(chan, &rfmt)) {
-               ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_getformatname(&rfmt), ast_channel_name(chan));
+
+       if (rfmt && ast_set_read_format(chan, rfmt)) {
+               ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_format_get_name(rfmt), ast_channel_name(chan));
        }
+       ao2_cleanup(rfmt);
        if ((outmsg == 2) && (!skip_confirmation_sound)) {
                ast_stream_and_wait(chan, "auth-thankyou", "");
        }
@@ -1586,7 +1890,7 @@ 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 *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, sound_duration, beep, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf), skip_confirmation_sound, if_exists);
+       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 *sound_duration, int silencethreshold, int maxsilence, const char *path)
@@ -1649,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;
                }
        }
@@ -1657,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);
@@ -1876,7 +2180,7 @@ static enum AST_LOCK_RESULT ast_lock_path_lockfile(const char *path)
        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));
@@ -1933,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)
@@ -1979,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 (
@@ -2254,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;
@@ -2658,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();
@@ -2808,10 +3114,8 @@ struct stasis_topic *ast_mwi_topic(const char *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);
-       struct ast_str *uniqueid = ast_str_alloca(AST_MAX_MAILBOX_UNIQUEID);
 
        ast_assert(!ast_strlen_zero(mailbox));
-       ast_assert(!ast_strlen_zero(context));
 
        mwi_state = ao2_alloc(sizeof(*mwi_state), mwi_state_dtor);
        if (!mwi_state) {
@@ -2821,56 +3125,102 @@ struct ast_mwi_state *ast_mwi_create(const char *mailbox, const char *context)
        if (ast_string_field_init(mwi_state, 256)) {
                return NULL;
        }
-       ast_str_set(&uniqueid, 0, "%s@%s", mailbox, context);
-       ast_string_field_set(mwi_state, uniqueid, ast_str_buffer(uniqueid));
-       ast_string_field_set(mwi_state, mailbox, mailbox);
-       ast_string_field_set(mwi_state, context, context);
+       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;
 }
 
-
-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)
+/*!
+ * \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)
 {
-       RAII_VAR(struct ast_mwi_state *, mwi_state, NULL, ao2_cleanup);
-       RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
-       struct stasis_topic *mailbox_specific_topic;
+       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 -1;
+               return NULL;
        }
 
        mwi_state->new_msgs = new_msgs;
        mwi_state->old_msgs = old_msgs;
 
        if (!ast_strlen_zero(channel_id)) {
-               RAII_VAR(struct stasis_message *, chan_message,
-                       stasis_cache_get(ast_channel_cache(),
-                                       ast_channel_snapshot_type(),
-                                       channel_id),
-                       ao2_cleanup);
+               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 {
-               ast_set_default_eid(&mwi_state->eid);
+               mwi_state->eid = ast_eid_default;
        }
 
-       message = stasis_message_create(ast_mwi_state_type(), mwi_state);
+       /*
+        * 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;
@@ -2881,6 +3231,54 @@ int ast_publish_mwi_state_full(
        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)) {
@@ -2911,6 +3309,10 @@ struct stasis_message *ast_mwi_blob_create(struct ast_mwi_state *mwi_state,
 
        ast_assert(blob != NULL);
 
+       if (!message_type) {
+               return NULL;
+       }
+
        obj = ao2_alloc(sizeof(*obj), mwi_blob_dtor);
        if (!obj) {
                return NULL;
@@ -2920,6 +3322,7 @@ struct stasis_message *ast_mwi_blob_create(struct ast_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;