Merged revisions 173559 via svnmerge from
authorMark Michelson <mmichelson@digium.com>
Thu, 5 Feb 2009 18:34:06 +0000 (18:34 +0000)
committerMark Michelson <mmichelson@digium.com>
Thu, 5 Feb 2009 18:34:06 +0000 (18:34 +0000)
https://origsvn.digium.com/svn/asterisk/branches/1.4

........
r173559 | mmichelson | 2009-02-05 11:34:33 -0600 (Thu, 05 Feb 2009) | 25 lines

Fix a problem where a channel pointer becomes invalid due to masquerading or hanging up.

app_mixmonitor runs its own thread to monitor the channel's activity and write the mixed
audio to a file. Since this thread runs independently of the channel, it is possible that
the mixmonitor thread's channel pointer will point to freed memory when the channel either
is masqueraded or hangs up (technically, both cases are hangups, but we need to handle the
cases slightly differently).

The solution for this is to employ a datastore, which has the nice benefit of allowing us
to hook into channel masquerades and hangups and update our pointer as necessary. If this
looks familiar, this same technique is employed in app_chanspy. app_chanspy is a bit more
involved since it does a lot more operations on the channel that is being spied upon.

app_mixmonitor does have an extra touch that app_chanspy doesn't have, though. Since there
is a thread race between the channel's thread and the mixmonitor thread on a hangup, we em-
ploy a condition-and-boolean combination to ensure that the channel thread finishes with
our structure before the mixmonitor thread attempts to free it. No crashes!

(closes issue #14374)
Reported by: aragon
Patches:
  14374.patch uploaded by putnopvut (license 60)
Tested by: aragon, putnopvut

........

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@173589 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_mixmonitor.c

index c5a5e10..7fdc4cb 100644 (file)
@@ -138,7 +138,7 @@ struct mixmonitor {
        char *post_process;
        char *name;
        unsigned int flags;
-       struct ast_channel *chan;
+       struct mixmonitor_ds *mixmonitor_ds;
 };
 
 enum {
@@ -164,6 +164,50 @@ AST_APP_OPTIONS(mixmonitor_opts, {
        AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
 });
 
+/* This structure is used as a means of making sure that our pointer to
+ * the channel we are monitoring remains valid. This is very similar to 
+ * what is used in app_chanspy.c.
+ */
+struct mixmonitor_ds {
+       struct ast_channel *chan;
+       /* These condition variables are used to be sure that the channel
+        * hangup code completes before the mixmonitor thread attempts to
+        * free this structure. The combination of a bookean flag and a
+        * ast_cond_t ensure that no matter what order the threads run in,
+        * we are guaranteed to never have the waiting thread block forever
+        * in the case that the signaling thread runs first.
+        */
+       unsigned int destruction_ok;
+       ast_cond_t destruction_condition;
+       ast_mutex_t lock;
+};
+
+static void mixmonitor_ds_destroy(void *data)
+{
+       struct mixmonitor_ds *mixmonitor_ds = data;
+
+       ast_mutex_lock(&mixmonitor_ds->lock);
+       mixmonitor_ds->chan = NULL;
+       mixmonitor_ds->destruction_ok = 1;
+       ast_cond_signal(&mixmonitor_ds->destruction_condition);
+       ast_mutex_unlock(&mixmonitor_ds->lock);
+}
+
+static void mixmonitor_ds_chan_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+       struct mixmonitor_ds *mixmonitor_ds = data;
+
+       ast_mutex_lock(&mixmonitor_ds->lock);
+       mixmonitor_ds->chan = new_chan;
+       ast_mutex_unlock(&mixmonitor_ds->lock);
+}
+
+static struct ast_datastore_info mixmonitor_ds_info = {
+       .type = "mixmonitor",
+       .destroy = mixmonitor_ds_destroy,
+       .chan_fixup = mixmonitor_ds_chan_fixup,
+};
+
 static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook) 
 {
        struct ast_channel *peer = NULL;
@@ -205,7 +249,9 @@ static void *mixmonitor_thread(void *obj)
                if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, AST_FORMAT_SLINEAR)))
                        continue;
 
-               if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || ast_bridged_channel(mixmonitor->chan)) {
+               ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
+               if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->mixmonitor_ds->chan && ast_bridged_channel(mixmonitor->mixmonitor_ds->chan))) {
+                       ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
                        /* Initialize the file if not already done so */
                        if (!fs && !errflag) {
                                oflags = O_CREAT | O_WRONLY;
@@ -225,6 +271,8 @@ static void *mixmonitor_thread(void *obj)
                        /* Write out frame */
                        if (fs)
                                ast_writestream(fs, fr);
+               } else {
+                       ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
                }
 
                /* All done! free it. */
@@ -246,12 +294,46 @@ static void *mixmonitor_thread(void *obj)
                ast_safe_system(mixmonitor->post_process);
        }
 
+       ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
+       if (!mixmonitor->mixmonitor_ds->destruction_ok) {
+               ast_cond_wait(&mixmonitor->mixmonitor_ds->destruction_condition, &mixmonitor->mixmonitor_ds->lock);
+       }
+       ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
+       ast_free(mixmonitor->mixmonitor_ds);
        ast_free(mixmonitor);
 
-
        return NULL;
 }
 
+static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel *chan)
+{
+       struct ast_datastore *datastore = NULL;
+       struct mixmonitor_ds *mixmonitor_ds;
+
+       if (!(mixmonitor_ds = ast_calloc(1, sizeof(*mixmonitor_ds)))) {
+               return -1;
+       }
+       
+       ast_mutex_init(&mixmonitor_ds->lock);
+       ast_cond_init(&mixmonitor_ds->destruction_condition, NULL);
+
+       if (!(datastore = ast_datastore_alloc(&mixmonitor_ds_info, NULL))) {
+               ast_free(mixmonitor_ds);
+               return -1;
+       }
+
+       /* No need to lock mixmonitor_ds since this is still operating in the channel's thread */
+       mixmonitor_ds->chan = chan;
+       datastore->data = mixmonitor_ds;
+
+       ast_channel_lock(chan);
+       ast_channel_datastore_add(chan, datastore);
+       ast_channel_unlock(chan);
+
+       mixmonitor->mixmonitor_ds = mixmonitor_ds;
+       return 0;
+}
+
 static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
                                  int readvol, int writevol, const char *post_process) 
 {
@@ -285,7 +367,9 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
 
        /* Copy over flags and channel name */
        mixmonitor->flags = flags;
-       mixmonitor->chan = chan;
+       if (setup_mixmonitor_ds(mixmonitor, chan)) {
+               return;
+       }
        mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
        strcpy(mixmonitor->name, chan->name);
        if (!ast_strlen_zero(postprocess2)) {
@@ -318,7 +402,6 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
        }
 
        ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
-
 }
 
 static int mixmonitor_exec(struct ast_channel *chan, void *data)