[svn-commits] dhubbard: branch dhubbard/pinecowbell r166281 - in /team/dhubbard/pinecowbell...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Mon Dec 22 11:03:46 CST 2008


Author: dhubbard
Date: Mon Dec 22 11:03:45 2008
New Revision: 166281

URL: http://svn.digium.com/view/asterisk?view=rev&rev=166281
Log:
Merged revisions 166258,166263,166267-166268,166273 via svnmerge from 
https://origsvn.digium.com/svn/asterisk/trunk

................
r166258 | russell | 2008-12-22 08:16:54 -0600 (Mon, 22 Dec 2008) | 26 lines

Remove AST_PBX_KEEPALIVE usage from res_agi.

This patch removes the usage of AST_PBX_KEEPALIVE from res_agi.  The only usage
was for the AGI command, "asyncagi break".  This patch removes this feature.
Normally, a feature would not be removed like this.  However, this code is
broken and usage of it will result in a memory leak.

Usage of this feature will make the AGI code return a result of 
AST_PBX_KEEPALIVE.  The PBX handler assumes that another thread has assumed
ownership of the channel.  The channel thread will exit without destroying the
channel.  Unfortunately, _no_ thread has ownership of the channel at this
point.  There are a couple of serious problems here:

1) The only way to recover the caller is to issue a channel redirect.  This
   will work, but this will be done with a masquerade, and the old ast_channel
   structure will be lost.

2) Until the channel redirect happens, there is no code servicing the channel.
   That means nothing is reading audio or handling events coming from the
   channel.  This is very bad.

The recommended way to get this same "break" functionality is to issue the
redirect while the channel is still being handled by the AGI code.  That way,
there will be no memory leak, and there will be no period of time that the
channel is not being serviced.

................
r166263 | russell | 2008-12-22 08:52:35 -0600 (Mon, 22 Dec 2008) | 14 lines

Blocked revisions 166262 via svnmerge

........
r166262 | russell | 2008-12-22 08:45:27 -0600 (Mon, 22 Dec 2008) | 7 lines

Re-work ref count handling of MoH classes using astobj2 to resolve crashes.

(closes issue #13566)
Reported by: igorcarneiro
Tested by: russell
Review: http://reviewboard.digium.com/r/106/

........

................
r166267 | mmichelson | 2008-12-22 10:07:59 -0600 (Mon, 22 Dec 2008) | 17 lines

Fix a file playback crash and explicitly initialize values in func_timeout.c

A crash was brought up on the bugtracker. The first run through valgrind
was full of legitimate complaints of uninitialized values in func_timeout when
setting a response timeout. These were fixed but the crash persisted.

A second run through showed the real problem. The reference counting used
for filestreams was incorrect because there were some missing increments
when a frame was read from a format module.

(closes issue #14118)
Reported by: blitzrage
Patches:
      14118v2.patch uploaded by putnopvut (license 60)
Tested by: blitzrage


................
r166268 | file | 2008-12-22 10:08:13 -0600 (Mon, 22 Dec 2008) | 7 lines

Record the previous port in the temporary address structure so that the comparison does not treat the host as having changed even if it did not. This would have been uninitialized before and would have led to a baddddd port.
(closes issue #13628)
Reported by: pananix
Patches:
      bug13628.patch uploaded by jpeeler (license 325)
Tested by: file, blitzrage

................
r166273 | russell | 2008-12-22 10:10:40 -0600 (Mon, 22 Dec 2008) | 7 lines

Re-work ref count handling of MoH classes using astobj2 to resolve crashes.

(closes issue #13566)
Reported by: igorcarneiro
Tested by: russell
Review: http://reviewboard.digium.com/r/106/

................

Modified:
    team/dhubbard/pinecowbell/   (props changed)
    team/dhubbard/pinecowbell/funcs/func_timeout.c
    team/dhubbard/pinecowbell/main/dnsmgr.c
    team/dhubbard/pinecowbell/main/file.c
    team/dhubbard/pinecowbell/res/res_agi.c
    team/dhubbard/pinecowbell/res/res_musiconhold.c

Propchange: team/dhubbard/pinecowbell/
------------------------------------------------------------------------------
Binary property 'branch-1.4-blocked' - no diff available.

Propchange: team/dhubbard/pinecowbell/
------------------------------------------------------------------------------
--- svnmerge-integrated (original)
+++ svnmerge-integrated Mon Dec 22 11:03:45 2008
@@ -1,1 +1,1 @@
-/trunk:1-166251
+/trunk:1-166280

Modified: team/dhubbard/pinecowbell/funcs/func_timeout.c
URL: http://svn.digium.com/view/asterisk/team/dhubbard/pinecowbell/funcs/func_timeout.c?view=diff&rev=166281&r1=166280&r2=166281
==============================================================================
--- team/dhubbard/pinecowbell/funcs/func_timeout.c (original)
+++ team/dhubbard/pinecowbell/funcs/func_timeout.c Mon Dec 22 11:03:45 2008
@@ -119,11 +119,12 @@
 static int timeout_write(struct ast_channel *chan, const char *cmd, char *data,
 			 const char *value)
 {
-	double x;
-	long sec;
+	double x = 0.0;
+	long sec = 0L;
 	char timestr[64];
 	struct ast_tm myt;
-	struct timeval when;
+	struct timeval when = {0,};
+	int res;
 
 	if (!chan)
 		return -1;
@@ -136,9 +137,13 @@
 	if (!value)
 		return -1;
 
-	if ((sscanf(value, "%ld%lf", &sec, &x) == 0) || sec < 0)
+	res = sscanf(value, "%ld%lf", &sec, &x);
+	if (res == 0 || sec < 0) {
 		when.tv_sec = 0;
-	else {
+		when.tv_usec = 0;
+	} else if (res == 1) {
+		when.tv_sec = sec;
+	} else if (res == 2) {
 		when.tv_sec = sec;
 		when.tv_usec = x * 1000000;
 	}

Modified: team/dhubbard/pinecowbell/main/dnsmgr.c
URL: http://svn.digium.com/view/asterisk/team/dhubbard/pinecowbell/main/dnsmgr.c?view=diff&rev=166281&r1=166280&r2=166281
==============================================================================
--- team/dhubbard/pinecowbell/main/dnsmgr.c (original)
+++ team/dhubbard/pinecowbell/main/dnsmgr.c Mon Dec 22 11:03:45 2008
@@ -164,6 +164,8 @@
 	if (verbose)
 		ast_verb(3, "refreshing '%s'\n", entry->name);
 
+	tmp.sin_port = entry->last.sin_port;
+	
 	if (!ast_get_ip_or_srv(&tmp, entry->name, entry->service) && inaddrcmp(&tmp, &entry->last)) {
 		ast_copy_string(iabuf, ast_inet_ntoa(entry->last.sin_addr), sizeof(iabuf));
 		ast_copy_string(iabuf2, ast_inet_ntoa(tmp.sin_addr), sizeof(iabuf2));

Modified: team/dhubbard/pinecowbell/main/file.c
URL: http://svn.digium.com/view/asterisk/team/dhubbard/pinecowbell/main/file.c?view=diff&rev=166281&r1=166280&r2=166281
==============================================================================
--- team/dhubbard/pinecowbell/main/file.c (original)
+++ team/dhubbard/pinecowbell/main/file.c Mon Dec 22 11:03:45 2008
@@ -714,6 +714,10 @@
 			goto return_failure;
 		
 		fr = s->fmt->read(s, &whennext);
+		if (fr) {
+			ast_set_flag(fr, AST_FRFLAG_FROM_FILESTREAM);
+			ao2_ref(s, +1);
+		}
 		if (!fr /* stream complete */ || ast_write(s->owner, fr) /* error writing */) {
 			if (fr)
 				ast_log(LOG_WARNING, "Failed to write frame\n");
@@ -764,6 +768,10 @@
 
 	while (!whennext) {
 		struct ast_frame *fr = s->fmt->read(s, &whennext);
+		if (fr) {
+			ast_set_flag(fr, AST_FRFLAG_FROM_FILESTREAM);
+			ao2_ref(s, +1);
+		}
 		if (!fr || ast_write(s->owner, fr)) { /* no stream or error, as above */
 			if (fr)
 				ast_log(LOG_WARNING, "Failed to write frame\n");

Modified: team/dhubbard/pinecowbell/res/res_agi.c
URL: http://svn.digium.com/view/asterisk/team/dhubbard/pinecowbell/res/res_agi.c?view=diff&rev=166281&r1=166280&r2=166281
==============================================================================
--- team/dhubbard/pinecowbell/res/res_agi.c (original)
+++ team/dhubbard/pinecowbell/res/res_agi.c Mon Dec 22 11:03:45 2008
@@ -687,7 +687,7 @@
 			/* OK, we have a command, let's call the
 			   command handler. */
 			res = agi_handle_command(chan, &async_agi, cmd->cmd_buffer, 0);
-			if ((res < 0) || (res == AST_PBX_KEEPALIVE)) {
+			if (res < 0) {
 				free_agi_cmd(cmd);
 				break;
 			}
@@ -2229,12 +2229,6 @@
 	return RESULT_SUCCESS;
 }
 
-static int handle_asyncagi_break(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
-{
-	ast_agi_send(agi->fd, chan, "200 result=0\n");
-	return AST_PBX_KEEPALIVE;
-}
-
 static char usage_verbose[] =
 " Usage: VERBOSE <message> <level>\n"
 "	Sends <message> to the console via verbose message system.\n"
@@ -2394,10 +2388,6 @@
 "	Cause the channel to automatically hangup at <time> seconds in the\n"
 " future.  Of course it can be hungup before then as well. Setting to 0 will\n"
 " cause the autohangup feature to be disabled on this channel.\n";
-
-static char usage_break_aagi[] =
-" Usage: ASYNCAGI BREAK\n"
-"	Break the Async AGI loop.\n";
 
 static char usage_speechcreate[] =
 " Usage: SPEECH CREATE <engine>\n"
@@ -2480,7 +2470,6 @@
 	{ { "speech", "activate", "grammar", NULL }, handle_speechactivategrammar, "Activates a grammar", usage_speechactivategrammar, 0 },
 	{ { "speech", "deactivate", "grammar", NULL }, handle_speechdeactivategrammar, "Deactivates a grammar", usage_speechdeactivategrammar, 0 },
 	{ { "speech", "recognize", NULL }, handle_speechrecognize, "Recognizes speech", usage_speechrecognize, 0 },
-	{ { "asyncagi", "break", NULL }, handle_asyncagi_break, "Break AsyncAGI loop", usage_break_aagi, 0 },
 };
 
 static AST_RWLIST_HEAD_STATIC(agi_commands, agi_command);
@@ -2751,7 +2740,6 @@
 			ast_module_unref(c->mod);
 		switch (res) {
 		case RESULT_SHOWUSAGE: ami_res = "Usage"; resultcode = 520; break;
-		case AST_PBX_KEEPALIVE: ami_res = "KeepAlive"; resultcode = 210; break;
 		case RESULT_FAILURE: ami_res = "Failure"; resultcode = -1; break;
 		case RESULT_SUCCESS: ami_res = "Success"; resultcode = 200; break;
 		}
@@ -2767,10 +2755,6 @@
 			ast_agi_send(agi->fd, chan, "520-Invalid command syntax.  Proper usage follows:\n");
 			ast_agi_send(agi->fd, chan, "%s", c->usage);
 			ast_agi_send(agi->fd, chan, "520 End of proper usage.\n");
-			break;
-		case AST_PBX_KEEPALIVE:
-			/* We've been asked to keep alive, so do so */
-			return AST_PBX_KEEPALIVE;
 			break;
 		case RESULT_FAILURE:
 			/* They've already given the failure.  We've been hung up on so handle this
@@ -2876,8 +2860,9 @@
 
 			if (!buf[0]) {
 				/* Program terminated */
-				if (returnstatus && returnstatus != AST_PBX_KEEPALIVE)
+				if (returnstatus) {
 					returnstatus = -1;
+				}
 				ast_verb(3, "<%s>AGI Script %s completed, returning %d\n", chan->name, request, returnstatus);
 				if (pid > 0)
 					waitpid(pid, status, 0);
@@ -2899,7 +2884,7 @@
 				ast_verbose("<%s>AGI Rx << %s\n", chan->name, buf);
 			returnstatus |= agi_handle_command(chan, agi, buf, dead);
 			/* If the handle_command returns -1, we need to stop */
-			if ((returnstatus < 0) || (returnstatus == AST_PBX_KEEPALIVE)) {
+			if (returnstatus < 0) {
 				needhup = 1;
 				continue;
 			}

Modified: team/dhubbard/pinecowbell/res/res_musiconhold.c
URL: http://svn.digium.com/view/asterisk/team/dhubbard/pinecowbell/res/res_musiconhold.c?view=diff&rev=166281&r1=166280&r2=166281
==============================================================================
--- team/dhubbard/pinecowbell/res/res_musiconhold.c (original)
+++ team/dhubbard/pinecowbell/res/res_musiconhold.c Mon Dec 22 11:03:45 2008
@@ -46,7 +46,7 @@
 #include <thread.h>
 #endif
 
-#if defined(HAVE_DAHDI)
+#ifdef HAVE_DAHDI
 #include <dahdi/user.h>
 #endif
 
@@ -66,6 +66,7 @@
 #include "asterisk/linkedlists.h"
 #include "asterisk/manager.h"
 #include "asterisk/paths.h"
+#include "asterisk/astobj2.h"
 
 #define INITIAL_NUM_FILES   8
 
@@ -162,8 +163,6 @@
 	int srcfd;
 	/*! FD for timing source */
 	int pseudofd;
-	/*! Number of users */
-	int inuse;
 	/*! Created on the fly, from RT engine */
 	int realtime;
 	unsigned int delete:1;
@@ -179,63 +178,44 @@
 	AST_LIST_ENTRY(mohdata) list;
 };
 
-AST_RWLIST_HEAD_STATIC(mohclasses, mohclass);
+static struct ao2_container *mohclasses;
 
 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
 #define MPG_123 "/usr/bin/mpg123"
 #define MAX_MP3S 256
 
-static int ast_moh_destroy_one(struct mohclass *moh);
 static int reload(void);
 
-static void ast_moh_free_class(struct mohclass **mohclass) 
-{
-	struct mohdata *member;
-	struct mohclass *class = *mohclass;
-	int i;
+#define mohclass_ref(class)   (ao2_ref((class), +1), class)
+#define mohclass_unref(class) (ao2_ref((class), -1), NULL)
+
+static void moh_files_release(struct ast_channel *chan, void *data)
+{
+	struct moh_files_state *state;
+
+	if (!chan || !chan->music_state) {
+		return;
+	}
+
+	state = chan->music_state;
+
+	if (chan->stream) {
+		ast_closestream(chan->stream);
+		chan->stream = NULL;
+	}
 	
-	while ((member = AST_LIST_REMOVE_HEAD(&class->members, list)))
-		ast_free(member);
-	
-	if (class->thread) {
-		pthread_cancel(class->thread);
-		class->thread = 0;
-	}
-
-	if (class->filearray) {
-		for (i = 0; i < class->total_files; i++)
-			ast_free(class->filearray[i]);
-		ast_free(class->filearray);
-	}
-
-	ast_free(class);
-	*mohclass = NULL;
-}
-
-
-static void moh_files_release(struct ast_channel *chan, void *data)
-{
-	struct moh_files_state *state;
-
-	if (chan) {
-		if ((state = chan->music_state)) {
-			if (chan->stream) {
-	                        ast_closestream(chan->stream);
-	                        chan->stream = NULL;
-	                }
-			ast_verb(3, "Stopped music on hold on %s\n", chan->name);
-	
-			if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
-				ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
-			}
-			state->save_pos = state->pos;
-
-			if (ast_atomic_dec_and_test(&state->class->inuse) && state->class->delete)
-				ast_moh_destroy_one(state->class);
-		}
-	}
-}
-
+	if (option_verbose > 2) {
+		ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
+	}
+
+	if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
+		ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
+	}
+
+	state->save_pos = state->pos;
+
+	state->class = mohclass_unref(state->class);
+}
 
 static int ast_moh_files_next(struct ast_channel *chan) 
 {
@@ -291,7 +271,6 @@
 
 	return 0;
 }
-
 
 static struct ast_frame *moh_files_readframe(struct ast_channel *chan) 
 {
@@ -329,7 +308,6 @@
 	return res;
 }
 
-
 static void *moh_files_alloc(struct ast_channel *chan, void *params)
 {
 	struct moh_files_state *state;
@@ -337,53 +315,62 @@
 
 	if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
 		chan->music_state = state;
-		state->class = class;
+		state->class = mohclass_ref(class);
 		state->save_pos = -1;
-	} else 
+	} else {
 		state = chan->music_state;
-
-	if (state) {
-		if (state->class != class) {
-			/* initialize */
-			memset(state, 0, sizeof(*state));
-			state->class = class;
-			if (ast_test_flag(state->class, MOH_RANDOMIZE) && class->total_files)
-				state->pos = ast_random() % class->total_files;
-		}
-
-		state->origwfmt = chan->writeformat;
-
-		ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name);
-	}
+	}
+
+	if (!state) {
+		return NULL;
+	}
+
+	if (state->class != class) {
+		/* (re-)initialize */
+		if (state->class) {
+			state->class = mohclass_unref(state->class);
+		}
+		memset(state, 0, sizeof(*state));
+		state->class = mohclass_ref(class);
+		if (ast_test_flag(state->class, MOH_RANDOMIZE) && class->total_files) {
+			state->pos = ast_random() % class->total_files;
+		}
+	}
+
+	state->origwfmt = chan->writeformat;
+
+	ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name);
 	
 	return chan->music_state;
 }
 
+static int moh_digit_match(void *obj, void *arg, int flags)
+{
+	char *digit = arg;
+	struct mohclass *class = obj;
+
+	return (*digit == class->digit) ? CMP_MATCH | CMP_STOP : 0;
+}
+
 /*! \note This function should be called with the mohclasses list locked */
 static struct mohclass *get_mohbydigit(char digit)
 {
-	struct mohclass *moh = NULL;
-
-	AST_RWLIST_TRAVERSE(&mohclasses, moh, list) {
-		if (digit == moh->digit)
-			break;
-	}
-
-	return moh;
+	return ao2_callback(mohclasses, 0, moh_digit_match, &digit);
 }
 
 static void moh_handle_digit(struct ast_channel *chan, char digit)
 {
-	struct mohclass *moh;
+	struct mohclass *class;
 	const char *classname = NULL;
 
-	AST_RWLIST_RDLOCK(&mohclasses);
-	if ((moh = get_mohbydigit(digit)))
-		classname = ast_strdupa(moh->name);
-	AST_RWLIST_UNLOCK(&mohclasses);
-
-	if (!moh)
+	if ((class = get_mohbydigit(digit))) {
+		classname = ast_strdupa(class->name);
+		class = mohclass_unref(class);
+	}
+
+	if (!class) {
 		return;
+	}
 
 	ast_moh_stop(chan);
 	ast_moh_start(chan, classname, NULL);
@@ -391,10 +378,10 @@
 
 static struct ast_generator moh_file_stream = 
 {
-	alloc: moh_files_alloc,
-	release: moh_files_release,
-	generate: moh_files_generator,
-	digit: moh_handle_digit,
+	.alloc    = moh_files_alloc,
+	.release  = moh_files_release,
+	.generate = moh_files_generator,
+	.digit    = moh_handle_digit,
 };
 
 static int spawn_mp3(struct mohclass *class)
@@ -610,15 +597,17 @@
 			}
 			continue;
 		}
+
 		pthread_testcancel();
-		AST_RWLIST_RDLOCK(&mohclasses);
-		AST_RWLIST_TRAVERSE(&class->members, moh, list) {
+
+		ao2_lock(class);
+		AST_LIST_TRAVERSE(&class->members, moh, list) {
 			/* Write data */
 			if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
 				ast_debug(1, "Only wrote %d of %d bytes to pipe\n", res, res2);
 			}
 		}
-		AST_RWLIST_UNLOCK(&mohclasses);
+		ao2_unlock(class);
 	}
 	return NULL;
 }
@@ -729,18 +718,20 @@
 	return 0;
 }
 
-/*! \note This function should be called with the mohclasses list locked */
 static struct mohclass *get_mohbyname(const char *name, int warn)
 {
 	struct mohclass *moh = NULL;
-
-	AST_RWLIST_TRAVERSE(&mohclasses, moh, list) {
-		if (!strcasecmp(name, moh->name))
-			break;
-	}
-
-	if (!moh && warn)
+	struct mohclass tmp_class = {
+		.flags = 0,
+	};
+
+	ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
+
+	moh = ao2_find(mohclasses, &tmp_class, 0);
+
+	if (!moh && warn) {
 		ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name);
+	}
 
 	return moh;
 }
@@ -769,11 +760,11 @@
 	moh->f.subclass = cl->format;
 	moh->f.offset = AST_FRIENDLY_OFFSET;
 
-	moh->parent = cl;
-
-	AST_RWLIST_WRLOCK(&mohclasses);
+	moh->parent = mohclass_ref(cl);
+
+	ao2_lock(cl);
 	AST_LIST_INSERT_HEAD(&cl->members, moh, list);
-	AST_RWLIST_UNLOCK(&mohclasses);
+	ao2_unlock(cl);
 	
 	return moh;
 }
@@ -781,26 +772,28 @@
 static void moh_release(struct ast_channel *chan, void *data)
 {
 	struct mohdata *moh = data;
+	struct mohclass *class = moh->parent;
 	int oldwfmt;
-	struct moh_files_state *state;
-
-	AST_RWLIST_WRLOCK(&mohclasses);
-	AST_RWLIST_REMOVE(&moh->parent->members, moh, list);	
-	AST_RWLIST_UNLOCK(&mohclasses);
+
+	ao2_lock(class);
+	AST_LIST_REMOVE(&moh->parent->members, moh, list);	
+	ao2_unlock(class);
 	
 	close(moh->pipe[0]);
 	close(moh->pipe[1]);
+
 	oldwfmt = moh->origwfmt;
-	state = chan->music_state;
-	if (moh->parent->delete && ast_atomic_dec_and_test(&moh->parent->inuse))
-		ast_moh_destroy_one(moh->parent);
-	if (ast_atomic_dec_and_test(&state->class->inuse) && state->class->delete)
-		ast_moh_destroy_one(state->class);
+
+	moh->parent = class = mohclass_unref(class);
 
 	ast_free(moh);
+
 	if (chan) {
-		if (oldwfmt && ast_set_write_format(chan, oldwfmt)) 
-			ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
+		if (oldwfmt && ast_set_write_format(chan, oldwfmt)) {
+			ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n",
+					chan->name, ast_getformatname(oldwfmt));
+		}
+
 		ast_verb(3, "Stopped music on hold on %s\n", chan->name);
 	}
 }
@@ -840,10 +833,6 @@
 	short buf[1280 + AST_FRIENDLY_OFFSET / 2];
 	int res;
 
-	if (!moh->parent->pid && moh->parent->inuse == 0) {
-		return -1;
-	}
-
 	len = ast_codec_get_len(moh->parent->format, samples);
 
 	if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
@@ -866,11 +855,10 @@
 	return 0;
 }
 
-static struct ast_generator mohgen = 
-{
-	alloc: moh_alloc,
-	release: moh_release,
-	generate: moh_generate,
+static struct ast_generator mohgen = {
+	.alloc    = moh_alloc,
+	.release  = moh_release,
+	.generate = moh_generate,
 	digit: moh_handle_digit
 };
 
@@ -991,98 +979,135 @@
 	return class->total_files;
 }
 
+static int init_files_class(struct mohclass *class)
+{
+	int res;
+
+	res = moh_scan_files(class);
+
+	if (res < 0) {
+		return -1;
+	}
+
+	if (!res) {
+		if (option_verbose > 2) {
+			ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n",
+					class->dir, class->name);
+		}
+		return -1;
+	}
+
+	if (strchr(class->args, 'r')) {
+		ast_set_flag(class, MOH_RANDOMIZE);
+	}
+
+	return 0;
+}
+
+
 static int moh_diff(struct mohclass *old, struct mohclass *new)
 {
-	if (!old || !new)
-		return -1;
-
-	if (strcmp(old->dir, new->dir))
-		return -1;
-	else if (strcmp(old->mode, new->mode))
-		return -1;
-	else if (strcmp(old->args, new->args))
-		return -1;
-	else if (old->flags != new->flags)
-		return -1;
+	if (!old || !new) {
+		return -1;
+	}
+
+	if (strcmp(old->dir, new->dir)) {
+		return -1;
+	} else if (strcmp(old->mode, new->mode)) {
+		return -1;
+	} else if (strcmp(old->args, new->args)) {
+		return -1;
+	} else if (old->flags != new->flags) {
+		return -1;
+	}
 
 	return 0;
 }
 
-static int moh_register(struct mohclass *moh, int is_reload)
+static int init_app_class(struct mohclass *class)
 {
 #ifdef HAVE_DAHDI
 	int x;
 #endif
+
+	if (!strcasecmp(class->mode, "custom")) {
+		ast_set_flag(class, MOH_CUSTOM);
+	} else if (!strcasecmp(class->mode, "mp3nb")) {
+		ast_set_flag(class, MOH_SINGLE);
+	} else if (!strcasecmp(class->mode, "quietmp3nb")) {
+		ast_set_flag(class, MOH_SINGLE | MOH_QUIET);
+	} else if (!strcasecmp(class->mode, "quietmp3")) {
+		ast_set_flag(class, MOH_QUIET);
+	}
+		
+	class->srcfd = -1;
+	class->pseudofd = -1;
+
+#ifdef HAVE_DAHDI
+	/* Open /dev/zap/pseudo for timing...  Is
+	   there a better, yet reliable way to do this? */
+	class->pseudofd = open("/dev/dahdi/psuedo", O_RDONLY);
+	if (class->pseudofd < 0) {
+		ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
+	} else {
+		x = 320;
+		ioctl(class->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
+	}
+#endif
+
+	if (ast_pthread_create_background(&class->thread, NULL, monmp3thread, class)) {
+		ast_log(LOG_WARNING, "Unable to create moh thread...\n");
+		if (class->pseudofd > -1) {
+			close(class->pseudofd);
+			class->pseudofd = -1;
+		}
+		return -1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \note This function owns the reference it gets to moh
+ */
+static int moh_register(struct mohclass *moh, int reload)
+{
 	struct mohclass *mohclass = NULL;
-	int res = 0;
-
-	AST_RWLIST_WRLOCK(&mohclasses);
+
 	if ((mohclass = get_mohbyname(moh->name, 0)) && !moh_diff(mohclass, moh)) {
 		if (!mohclass->delete) {
-			ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
-			ast_free(moh);
-			AST_RWLIST_UNLOCK(&mohclasses);
+ 			ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
+			mohclass = mohclass_unref(mohclass);
+			moh = mohclass_unref(moh);
 			return -1;
 		}
-	}
-	AST_RWLIST_UNLOCK(&mohclasses);
+		mohclass = mohclass_unref(mohclass);
+	}
 
 	time(&moh->start);
 	moh->start -= respawn_time;
 	
 	if (!strcasecmp(moh->mode, "files")) {
-		res = moh_scan_files(moh);
-		if (res <= 0) {
-			if (res == 0) {
-				if (option_verbose > 2)
-					ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n", moh->dir, moh->name);
-			}
-			ast_moh_free_class(&moh);
+		if (init_files_class(moh)) {
+			moh = mohclass_unref(moh);
 			return -1;
 		}
-		if (strchr(moh->args, 'r'))
-			ast_set_flag(moh, MOH_RANDOMIZE);
-	} else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
-
-		if (!strcasecmp(moh->mode, "custom"))
-			ast_set_flag(moh, MOH_CUSTOM);
-		else if (!strcasecmp(moh->mode, "mp3nb"))
-			ast_set_flag(moh, MOH_SINGLE);
-		else if (!strcasecmp(moh->mode, "quietmp3nb"))
-			ast_set_flag(moh, MOH_SINGLE | MOH_QUIET);
-		else if (!strcasecmp(moh->mode, "quietmp3"))
-			ast_set_flag(moh, MOH_QUIET);
-		
-		moh->srcfd = -1;
-#ifdef HAVE_DAHDI
-		/* Open /dev/dahdi/pseudo for timing...  Is
-		   there a better, yet reliable way to do this? */
-		moh->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
-		if (moh->pseudofd < 0) {
-			ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
-		} else {
-			x = 320;
-			ioctl(moh->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
-		}
-#else
-		moh->pseudofd = -1;
-#endif
-		if (ast_pthread_create_background(&moh->thread, NULL, monmp3thread, moh)) {
-			ast_log(LOG_WARNING, "Unable to create moh...\n");
-			if (moh->pseudofd > -1)
-				close(moh->pseudofd);
-			ast_moh_free_class(&moh);
+	} else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || 
+			!strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || 
+			!strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
+		if (init_app_class(moh)) {
+			moh = mohclass_unref(moh);
 			return -1;
 		}
 	} else {
 		ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
-		ast_moh_free_class(&moh);
-		return -1;
-	}
-
-	AST_RWLIST_WRLOCK(&mohclasses);
-	AST_RWLIST_INSERT_HEAD(&mohclasses, moh, list);
-	AST_RWLIST_UNLOCK(&mohclasses);
+		moh = mohclass_unref(moh);
+		return -1;
+	}
+
+	ao2_link(mohclasses, moh);
+
+	moh = mohclass_unref(moh);
 	
 	return 0;
 }
@@ -1092,34 +1117,19 @@
 	struct moh_files_state *state = chan->music_state;
 
 	if (state) {
-		if (state->class->realtime) {
-			if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
-				/* We are cleaning out cached RT class, we should remove it from list, if no one else using it */
-				if (!(state->class->inuse)) {
-					/* Remove this class from list */
-					AST_RWLIST_WRLOCK(&mohclasses);
-					AST_RWLIST_REMOVE(&mohclasses, state->class, list);
-					AST_RWLIST_UNLOCK(&mohclasses);
-	
-					/* Free some memory */
-					ast_moh_destroy_one(state->class);
-				}
-			} else {
-				ast_moh_destroy_one(state->class);
-			}
-		}
 		ast_free(chan->music_state);
 		chan->music_state = NULL;
 	}
 }
 
+static void moh_class_destructor(void *obj);
+
 static struct mohclass *moh_class_malloc(void)
 {
 	struct mohclass *class;
 
-	if ((class = ast_calloc(1, sizeof(*class)))) {
+	if ((class = ao2_alloc(sizeof(*class), moh_class_destructor))) {
 		class->format = AST_FORMAT_SLINEAR;
-		class->realtime = 0;
 	}
 
 	return class;
@@ -1128,12 +1138,8 @@
 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
 {
 	struct mohclass *mohclass = NULL;
-	struct ast_variable *var = NULL;
-	struct ast_variable *tmp = NULL;
 	struct moh_files_state *state = chan->music_state;
-#ifdef HAVE_DAHDI
-	int x;
-#endif
+	int res;
 
 	/* The following is the order of preference for which class to use:
 	 * 1) The channels explicitly set musicclass, which should *only* be
@@ -1146,19 +1152,20 @@
 	 *    option.
 	 * 4) The default class.
 	 */
-	
-	/* First, let's check in memory for static and cached RT classes */
-	AST_RWLIST_RDLOCK(&mohclasses);
-	if (!ast_strlen_zero(chan->musicclass))
+	if (!ast_strlen_zero(chan->musicclass)) {
 		mohclass = get_mohbyname(chan->musicclass, 1);
-	if (!mohclass && !ast_strlen_zero(mclass))
+	}
+	if (!mohclass && !ast_strlen_zero(mclass)) {
 		mohclass = get_mohbyname(mclass, 1);
-	if (!mohclass && !ast_strlen_zero(interpclass))
+	}
+	if (!mohclass && !ast_strlen_zero(interpclass)) {
 		mohclass = get_mohbyname(interpclass, 1);
-	AST_RWLIST_UNLOCK(&mohclasses);
+	}
 
 	/* If no moh class found in memory, then check RT */
 	if (!mohclass && ast_check_realtime("musiconhold")) {
+		struct ast_variable *var = NULL, *tmp = NULL;
+
 		if (!ast_strlen_zero(chan->musicclass)) {
 			var = ast_load_realtime("musiconhold", "name", chan->musicclass, SENTINEL);
 		}
@@ -1201,18 +1208,18 @@
 					strcpy(mohclass->dir, "nodir");
 				} else {
 					ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name);
-					ast_free(mohclass);
+					mohclass = mohclass_unref(mohclass);
 					return -1;
 				}
 			}
 			if (ast_strlen_zero(mohclass->mode)) {
 				ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", mohclass->name);
-				ast_free(mohclass);
+				mohclass = mohclass_unref(mohclass);
 				return -1;
 			}
 			if (ast_strlen_zero(mohclass->args) && !strcasecmp(mohclass->mode, "custom")) {
 				ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", mohclass->name);
-				ast_free(mohclass);
+				mohclass = mohclass_unref(mohclass);
 				return -1;
 			}
 
@@ -1223,13 +1230,12 @@
 					ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
 					if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
 						/* we found RT class with the same name, seems like we should continue playing existing one */
-						ast_moh_free_class(&mohclass);
+						mohclass = mohclass_unref(mohclass);
 						mohclass = state->class;
 					}
 				}
 				moh_register(mohclass, 0);
 			} else {
-
 				/* We don't register RT moh class, so let's init it manualy */
 
 				time(&mohclass->start);
@@ -1237,7 +1243,7 @@
 	
 				if (!strcasecmp(mohclass->mode, "files")) {
 					if (!moh_scan_files(mohclass)) {
-						ast_moh_free_class(&mohclass);
+						mohclass = mohclass_unref(mohclass);
 						return -1;
 					}
 					if (strchr(mohclass->args, 'r'))
@@ -1261,7 +1267,7 @@
 					if (mohclass->pseudofd < 0) {
 						ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
 					} else {
-						x = 320;
+						int x = 320;
 						ioctl(mohclass->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
 					}
 #else
@@ -1273,48 +1279,38 @@
 						ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
 						if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
 							/* we found RT class with the same name, seems like we should continue playing existing one */
-							ast_moh_free_class(&mohclass);
+							mohclass = mohclass_unref(mohclass);
 							mohclass = state->class;
-	
 						}
 					} else {
 						if (ast_pthread_create_background(&mohclass->thread, NULL, monmp3thread, mohclass)) {
 							ast_log(LOG_WARNING, "Unable to create moh...\n");
-							if (mohclass->pseudofd > -1)
+							if (mohclass->pseudofd > -1) {
 								close(mohclass->pseudofd);
-							ast_moh_free_class(&mohclass);
+								mohclass->pseudofd = -1;
+							}
+							mohclass = mohclass_unref(mohclass);
 							return -1;
 						}
 					}
 				} else {
 					ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mohclass->mode);
-					ast_moh_free_class(&mohclass);
+					mohclass = mohclass_unref(mohclass);
 					return -1;
 				}
-
 			}
-
-		} else if (var)
+		} else if (var) {
 			ast_variables_destroy(var);
-	}
-
-	
-
-	/* Requested MOH class not found, check for 'default' class in musiconhold.conf  */
+		}
+	}
+
 	if (!mohclass) {
-		AST_RWLIST_RDLOCK(&mohclasses);
 		mohclass = get_mohbyname("default", 1);
-		if (mohclass)
-			ast_atomic_fetchadd_int(&mohclass->inuse, +1);
-		AST_RWLIST_UNLOCK(&mohclasses);
-	} else {
-		AST_RWLIST_RDLOCK(&mohclasses);
-		ast_atomic_fetchadd_int(&mohclass->inuse, +1);
-		AST_RWLIST_UNLOCK(&mohclasses);
-	}
-
-	if (!mohclass)
-		return -1;
+	}
+
+	if (!mohclass) {
+		return -1;
+	}
 
 	manager_event(EVENT_FLAG_CALL, "MusicOnHold",
 		"State: Start\r\n"
@@ -1323,10 +1319,16 @@
 		chan->name, chan->uniqueid);
 
 	ast_set_flag(chan, AST_FLAG_MOH);
+
 	if (mohclass->total_files) {
-		return ast_activate_generator(chan, &moh_file_stream, mohclass);
-	} else
-		return ast_activate_generator(chan, &mohgen, mohclass);
+		res = ast_activate_generator(chan, &moh_file_stream, mohclass);
+	} else {
+		res = ast_activate_generator(chan, &mohgen, mohclass);
+	}
+
+	mohclass = mohclass_unref(mohclass);
+
+	return res;
 }
 
 static void local_ast_moh_stop(struct ast_channel *chan)
@@ -1349,14 +1351,85 @@
 		chan->name, chan->uniqueid);
 }
 
-static int load_moh_classes(int is_reload)
+static void moh_class_destructor(void *obj)
+{
+	struct mohclass *class = obj;
+	struct mohdata *member;
+
+	ast_debug(1, "Destroying MOH class '%s'\n", class->name);
+
+	if (class->pid > 1) {
+		char buff[8192];
+		int bytes, tbytes = 0, stime = 0, pid = 0;
+
+		ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
+
+		stime = time(NULL) + 2;
+		pid = class->pid;
+		class->pid = 0;
+
+		/* Back when this was just mpg123, SIGKILL was fine.  Now we need
+		 * to give the process a reason and time enough to kill off its
+		 * children. */
+		killpg(pid, SIGHUP);
+		usleep(100000);
+		killpg(pid, SIGTERM);
+		usleep(100000);
+		killpg(pid, SIGKILL);
+
+		while ((ast_wait_for_input(class->srcfd, 100) > 0) && 
+				(bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
+			tbytes = tbytes + bytes;
+		}
+
+		ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
+
+		close(class->srcfd);
+	}
+
+	while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
+		free(member);
+	}
+	
+	if (class->thread) {
+		pthread_cancel(class->thread);
+		class->thread = AST_PTHREADT_NULL;
+	}
+
+	if (class->filearray) {
+		int i;
+		for (i = 0; i < class->total_files; i++) {
+			free(class->filearray[i]);
+		}
+		free(class->filearray);
+		class->filearray = NULL;
+	}
+}
+
+static int moh_class_mark(void *obj, void *arg, int flags)
+{
+	struct mohclass *class = obj;
+
+	class->delete = 1;
+
+	return 0;
+}
+
+static int moh_classes_delete_marked(void *obj, void *arg, int flags)
+{
+	struct mohclass *class = obj;
+
+	return class->delete ? CMP_MATCH : 0;
+}
+
+static int load_moh_classes(int reload)
 {
 	struct ast_config *cfg;
 	struct ast_variable *var;
 	struct mohclass *class;	
 	char *cat;
 	int numclasses = 0;
-	struct ast_flags config_flags = { is_reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
 
 	cfg = ast_config_load("musiconhold.conf", config_flags);
 
@@ -1364,12 +1437,8 @@
 		return 0;
 	}
 
-	if (is_reload) {
-		AST_RWLIST_WRLOCK(&mohclasses);
-		AST_RWLIST_TRAVERSE(&mohclasses, class, list) {
-			class->delete = 1;
-		}
-		AST_RWLIST_UNLOCK(&mohclasses);
+	if (reload) {
+		ao2_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL);
 	}
 	
 	ast_clear_flag(global_flags, AST_FLAGS_ALL);
@@ -1378,121 +1447,87 @@
 	for (; cat; cat = ast_category_browse(cfg, cat)) {
 		/* Setup common options from [general] section */
 		if (!strcasecmp(cat, "general")) {
-			var = ast_variable_browse(cfg, cat);
-			while (var) {
+			for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
 				if (!strcasecmp(var->name, "cachertclasses")) {
 					ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES);
 				} else {
 					ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name);
 				}
-				var = var->next;
 			}
 		}
 		/* These names were deprecated in 1.4 and should not be used until after the next major release. */
-		if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files") && strcasecmp(cat, "general")) {
-			if (!(class = moh_class_malloc()))
-				break;
-
-			ast_copy_string(class->name, cat, sizeof(class->name));	
-			var = ast_variable_browse(cfg, cat);
-			while (var) {
-				if (!strcasecmp(var->name, "mode"))
-					ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
-				else if (!strcasecmp(var->name, "directory"))
-					ast_copy_string(class->dir, var->value, sizeof(class->dir));
-				else if (!strcasecmp(var->name, "application"))
-					ast_copy_string(class->args, var->value, sizeof(class->args));
-				else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value)))
-					class->digit = *var->value;
-				else if (!strcasecmp(var->name, "random"))
-					ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
-				else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random"))
-					ast_set_flag(class, MOH_RANDOMIZE);
-				else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) 
-					ast_set_flag(class, MOH_SORTALPHA);
-				else if (!strcasecmp(var->name, "format")) {
-					class->format = ast_getformatbyname(var->value);
-					if (!class->format) {
-						ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
-						class->format = AST_FORMAT_SLINEAR;
-					}
-				}
-				var = var->next;
-			}
-
-			if (ast_strlen_zero(class->dir)) {
-				if (!strcasecmp(class->mode, "custom")) {
-					strcpy(class->dir, "nodir");
-				} else {
-					ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
-					ast_free(class);
-					continue;
+		if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files") || 
+				!strcasecmp(cat, "general")) {
+			continue;
+		}
+
+		if (!(class = moh_class_malloc())) {
+			break;
+		}
+
+		ast_copy_string(class->name, cat, sizeof(class->name));	
+		for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+			if (!strcasecmp(var->name, "mode"))
+				ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
+			else if (!strcasecmp(var->name, "directory"))
+				ast_copy_string(class->dir, var->value, sizeof(class->dir));
+			else if (!strcasecmp(var->name, "application"))
+				ast_copy_string(class->args, var->value, sizeof(class->args));
+			else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value)))
+				class->digit = *var->value;
+			else if (!strcasecmp(var->name, "random"))
+				ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
+			else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random"))
+				ast_set_flag(class, MOH_RANDOMIZE);
+			else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) 
+				ast_set_flag(class, MOH_SORTALPHA);
+			else if (!strcasecmp(var->name, "format")) {
+				class->format = ast_getformatbyname(var->value);
+				if (!class->format) {
+					ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
+					class->format = AST_FORMAT_SLINEAR;
 				}
 			}
-			if (ast_strlen_zero(class->mode)) {
-				ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
-				ast_free(class);
+		}
+
+		if (ast_strlen_zero(class->dir)) {
+			if (!strcasecmp(class->mode, "custom")) {
+				strcpy(class->dir, "nodir");
+			} else {
+				ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
+				class = mohclass_unref(class);
 				continue;
 			}
-			if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
-				ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
-				ast_free(class);
-				continue;
-			}
-
-			/* Don't leak a class when it's already registered */
-			moh_register(class, is_reload);
-
-			numclasses++;
-		}
+		}
+		if (ast_strlen_zero(class->mode)) {
+			ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
+			class = mohclass_unref(class);
+			continue;
+		}
+		if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
+			ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
+			class = mohclass_unref(class);
+			continue;
+		}
+
+		/* Don't leak a class when it's already registered */
+		moh_register(class, reload);
+
+		numclasses++;
 	}
 
 	ast_config_destroy(cfg);
 
+	ao2_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, 

[... 262 lines stripped ...]



More information about the svn-commits mailing list