Skip to content
Snippets Groups Projects
res_musiconhold.c 53.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Copyright (C) 1999 - 2006, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * This program is free software, distributed under the terms of
    
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    
    /*! \file
     *
     * \brief Routines implementing music on hold
    
     * \arg See also \ref Config_moh
    
     * \author Mark Spencer <markster@digium.com>
    
    /*** MODULEINFO
    	<conflict>win32</conflict>
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    #include <signal.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <sys/time.h>
    #include <sys/signal.h>
    #include <netinet/in.h>
    #include <sys/stat.h>
    #include <dirent.h>
    #include <sys/ioctl.h>
    
    #include <dahdi/user.h>
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    #include "asterisk/lock.h"
    #include "asterisk/file.h"
    #include "asterisk/channel.h"
    #include "asterisk/pbx.h"
    
    #include "asterisk/app.h"
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    #include "asterisk/module.h"
    #include "asterisk/translate.h"
    #include "asterisk/say.h"
    #include "asterisk/musiconhold.h"
    #include "asterisk/config.h"
    #include "asterisk/utils.h"
    #include "asterisk/cli.h"
    
    #include "asterisk/stringfields.h"
    
    #include "asterisk/linkedlists.h"
    
    #include "asterisk/manager.h"
    
    /*** DOCUMENTATION
    	<application name="MusicOnHold" language="en_US">
    		<synopsis>
    			Play Music On Hold indefinitely.
    		</synopsis>
    		<syntax>
    			<parameter name="class" required="true" />
    			<parameter name="duration" />
    		</syntax>
    		<description>
    			<para>Plays hold music specified by class. If omitted, the default music
    			source for the channel will be used. Change the default class with
    			Set(CHANNEL(musicclass)=...). If duration is given, hold music will be played
    			specified number of seconds. If duration is ommited, music plays indefinitely.
    			Returns <literal>0</literal> when done, <literal>-1</literal> on hangup.</para>
    		</description>
    	</application>
    	<application name="WaitMusicOnHold" language="en_US">
    		<synopsis>
    			Wait, playing Music On Hold.
    		</synopsis>
    		<syntax>
    			<parameter name="delay" required="true" />
    		</syntax>
    		<description>
    			<para> !!! DEPRECATED. Use MusicOnHold instead !!!</para>
    			<para>Plays hold music specified number of seconds. Returns <literal>0</literal> when done,
    			or <literal>-1</literal> on hangup. If no hold music is available, the delay will still occur
    			with no sound.</para>
    			<para> !!! DEPRECATED. Use MusicOnHold instead !!!</para>
    		</description>
    	</application>
    	<application name="SetMusicOnHold" language="en_US">
    		<synopsis>
    			Set default Music On Hold class.
    		</synopsis>
    		<syntax>
    			<parameter name="class" required="yes" />
    		</syntax>
    		<description>
    			<para>!!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!</para>
    			<para>Sets the default class for music on hold for a given channel.
    			When music on hold is activated, this class will be used to select which
    			music is played.</para>
    			<para>!!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!</para>
    		</description>
    	</application>
    	<application name="StartMusicOnHold" language="en_US">
    		<synopsis>
    			Play Music On Hold.
    		</synopsis>
    		<syntax>
    			<parameter name="class" required="true" />
    		</syntax>
    		<description>
    			<para>Starts playing music on hold, uses default music class for channel.
    			Starts playing music specified by class. If omitted, the default music
    			source for the channel will be used. Always returns <literal>0</literal>.</para>
    		</description>
    	</application>
    	<application name="StopMusicOnHold" language="en_US">
    		<synopsis>
    			Stop playing Music On Hold.
    		</synopsis>
    		<syntax />
    		<description>
    			<para>Stops playing music on hold.</para>
    		</description>
    	</application>
     ***/
    
    static const char play_moh[] = "MusicOnHold";
    static const char wait_moh[] = "WaitMusicOnHold";
    static const char set_moh[] = "SetMusicOnHold";
    static const char start_moh[] = "StartMusicOnHold";
    static const char stop_moh[] = "StopMusicOnHold";
    
    	int pos;
    	int save_pos;
    
    	char *save_pos_filename;
    
    #define MOH_QUIET		(1 << 0)
    #define MOH_SINGLE		(1 << 1)
    #define MOH_CUSTOM		(1 << 2)
    #define MOH_RANDOMIZE		(1 << 3)
    
    #define MOH_CACHERTCLASSES      (1 << 5)        /*!< Should we use a separate instance of MOH for each user or not */
    
    
    /* Custom astobj2 flag */
    #define MOH_NOTDELETED          (1 << 30)       /*!< Find only records that aren't deleted? */
    
    
    static struct ast_flags global_flags[1] = {{0}};        /*!< global MOH_ flags */
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    struct mohclass {
    
    	char name[MAX_MUSICCLASS];
    
    	char args[256];
    	char mode[80];
    
    	/*! A dynamically sized array to hold the list of filenames in "files" mode */
    	char **filearray;
    	/*! The current size of the filearray */
    	int allowed_files;
    	/*! The current number of files loaded into the filearray */
    
    	/*! The format from the MOH source, not applicable to "files" mode */
    
    	/*! The pid of the external application delivering MOH */
    	int pid;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	pthread_t thread;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int srcfd;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int pseudofd;
    
    	/*! Created on the fly, from RT engine */
    	int realtime;
    
    	unsigned int delete:1;
    
    	AST_LIST_HEAD_NOLOCK(, mohdata) members;
    	AST_LIST_ENTRY(mohclass) list;
    
    Mark Spencer's avatar
    Mark Spencer committed
    };
    
    struct mohdata {
    	int pipe[2];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct mohclass *parent;
    
    	struct ast_frame f;
    	AST_LIST_ENTRY(mohdata) list;
    
    static struct ao2_container *mohclasses;
    
    #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
    
    Mark Spencer's avatar
    Mark Spencer committed
    #define MPG_123 "/usr/bin/mpg123"
    #define MAX_MP3S 256
    
    
    static int reload(void);
    
    #define mohclass_ref(class,string)   (ao2_t_ref((class), +1, (string)), class)
    
    #define mohclass_unref(class,string) (ao2_t_ref((class), -1, (string)), (struct mohclass *) NULL)
    
    #else
    #define mohclass_unref(class,string) _mohclass_unref(class, string, __FILE__,__LINE__,__PRETTY_FUNCTION__)
    static struct mohclass *_mohclass_unref(struct mohclass *class, const char *tag, const char *file, int line, const char *funcname)
    {
    	struct mohclass *dup;
    	if ((dup = ao2_find(mohclasses, class, OBJ_POINTER))) {
    		if (_ao2_ref_debug(dup, -1, (char *) tag, (char *) file, line, funcname) == 2) {
    			FILE *ref = fopen("/tmp/refs", "a");
    			if (ref) {
    				fprintf(ref, "%p =1   %s:%d:%s (%s) BAD ATTEMPT!\n", class, file, line, funcname, tag);
    				fclose(ref);
    			}
    			ast_log(LOG_WARNING, "Attempt to unref mohclass %p (%s) when only 1 ref remained, and class is still in a container! (at %s:%d (%s))\n",
    				class, class->name, file, line, funcname);
    		} else {
    			ao2_ref(class, -1);
    		}
    	} else {
    		ao2_t_ref(class, -1, (char *) tag);
    	}
    	return NULL;
    }
    #endif
    
    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;
    	}
    
    	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 '%s'\n", chan->name, ast_getformatname(state->origwfmt));
    
    	state->class = mohclass_unref(state->class, "Unreffing channel's music class upon deactivation of generator");
    
    static int ast_moh_files_next(struct ast_channel *chan) 
    {
    
    	struct moh_files_state *state = chan->music_state;
    
    	/* Discontinue a stream if it is running already */
    
    	if (chan->stream) {
    		ast_closestream(chan->stream);
    		chan->stream = NULL;
    	}
    
    	if (!state->class->total_files) {
    		ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
    		return -1;
    	}
    
    
    	/* If a specific file has been saved confirm it still exists and that it is still valid */
    	if (state->save_pos >= 0 && state->save_pos < state->class->total_files && state->class->filearray[state->save_pos] == state->save_pos_filename) {
    
    		state->pos = state->save_pos;
    
    		state->save_pos = -1;
    
    	} else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
    		/* Get a random file and ensure we can open it */
    
    		for (tries = 0; tries < 20; tries++) {
    
    			state->pos = ast_random() % state->class->total_files;
    
    			if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
    
    		state->save_pos = -1;
    
    		state->samples = 0;
    	} else {
    		/* This is easy, just increment our position and make sure we don't exceed the total file count */
    		state->pos++;
    		state->pos %= state->class->total_files;
    
    		state->save_pos = -1;
    
    		state->samples = 0;
    
    	}
    
    	if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
    		ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
    		state->pos++;
    
    		state->pos %= state->class->total_files;
    
    	/* Record the pointer to the filename for position resuming later */
    	state->save_pos_filename = state->class->filearray[state->pos];
    
    
    	ast_debug(1, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
    
    
    	if (state->samples)
    		ast_seekstream(chan->stream, state->samples, SEEK_SET);
    
    
    	return 0;
    
    static struct ast_frame *moh_files_readframe(struct ast_channel *chan) 
    {
    
    	if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
    		if (!ast_moh_files_next(chan))
    
    			f = ast_readframe(chan->stream);
    	}
    
    	return f;
    }
    
    static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
    {
    	struct moh_files_state *state = chan->music_state;
    	struct ast_frame *f = NULL;
    	int res = 0;
    
    	while (state->sample_queue > 0) {
    
    		if ((f = moh_files_readframe(chan))) {
    			state->samples += f->samples;
    
    			state->sample_queue -= f->samples;
    
    			if (res < 0) {
    
    				ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
    				return -1;
    			}
    		} else
    			return -1;	
    	}
    	return res;
    }
    
    static void *moh_files_alloc(struct ast_channel *chan, void *params)
    {
    	struct moh_files_state *state;
    	struct mohclass *class = params;
    
    
    	if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
    
    		ast_module_ref(ast_module_info->self);
    
    	/* LOGIC: Comparing an unrefcounted pointer is a really bad idea, because
    	 * malloc may allocate a different class to the same memory block.  This
    	 * might only happen when two reloads are generated in a short period of
    	 * time, but it's still important to protect against.
    	 * PROG: Compare the quick operation first, to save CPU. */
    	if (state->save_total != class->total_files || strcmp(state->name, class->name) != 0) {
    
    		if (ast_test_flag(class, MOH_RANDOMIZE) && class->total_files) {
    
    			state->pos = ast_random() % class->total_files;
    		}
    	}
    
    	state->class = mohclass_ref(class, "Reffing music class for channel");
    
    	/* For comparison on restart of MOH (see above) */
    	ast_copy_string(state->name, class->name, sizeof(state->name));
    	state->save_total = class->total_files;
    
    	ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name);
    
    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)
    {
    
    	return ao2_t_callback(mohclasses, 0, moh_digit_match, &digit, "digit callback");
    
    }
    
    static void moh_handle_digit(struct ast_channel *chan, char digit)
    {
    
    	const char *classname = NULL;
    
    	if ((class = get_mohbydigit(digit))) {
    		classname = ast_strdupa(class->name);
    
    		class = mohclass_unref(class, "Unreffing ao2_find from finding by digit");
    
    Matthias Nick's avatar
     
    Matthias Nick committed
    		ast_string_field_set(chan,musicclass,classname);
    		ast_moh_stop(chan);
    		ast_moh_start(chan, classname, NULL);
    
    	.alloc    = moh_files_alloc,
    	.release  = moh_files_release,
    	.generate = moh_files_generator,
    	.digit    = moh_handle_digit,
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int spawn_mp3(struct mohclass *class)
    {
    	int fds[2];
    
    	int files = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char fns[MAX_MP3S][80];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *argv[MAX_MP3S + 50];
    	char xargs[256];
    	char *argptr;
    
    	int argc = 0;
    
    	DIR *dir = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct dirent *de;
    
    	if (!strcasecmp(class->dir, "nodir")) {
    		files = 1;
    	} else {
    		dir = opendir(class->dir);
    
    		if (!dir && strncasecmp(class->dir, "http://", 7)) {
    
    			ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
    			return -1;
    		}
    
    	if (!ast_test_flag(class, MOH_CUSTOM)) {
    
    		argv[argc++] = "mpg123";
    		argv[argc++] = "-q";
    		argv[argc++] = "-s";
    		argv[argc++] = "--mono";
    		argv[argc++] = "-r";
    		argv[argc++] = "8000";
    		
    
    		if (!ast_test_flag(class, MOH_SINGLE)) {
    
    		if (ast_test_flag(class, MOH_QUIET))
    
    		/* Look for extra arguments and add them to the list */
    
    		ast_copy_string(xargs, class->args, sizeof(xargs));
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		while (!ast_strlen_zero(argptr)) {
    
    			strsep(&argptr, ",");
    
    		ast_copy_string(xargs, class->args, sizeof(xargs));
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		while (!ast_strlen_zero(argptr)) {
    
    			strsep(&argptr, " ");
    
    	if (!strncasecmp(class->dir, "http://", 7)) {
    
    		ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
    
    		argv[argc++] = fns[files];
    		files++;
    
    	} else if (dir) {
    
    		while ((de = readdir(dir)) && (files < MAX_MP3S)) {
    
    			    ((ast_test_flag(class, MOH_CUSTOM) && 
    			      (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || 
    			       !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
    			     !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
    
    				ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
    
    				argv[argc++] = fns[files];
    				files++;
    			}
    
    		}
    	}
    	argv[argc] = NULL;
    
    	if (dir) {
    		closedir(dir);
    	}
    
    	if (pipe(fds)) {	
    		ast_log(LOG_WARNING, "Pipe failed\n");
    		return -1;
    	}
    	if (!files) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
    
    		close(fds[0]);
    		close(fds[1]);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return -1;
    	}
    
    	if (!strncasecmp(class->dir, "http://", 7) && time(NULL) - class->start < respawn_time) {
    
    		sleep(respawn_time - (time(NULL) - class->start));
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (class->pid < 0) {
    		close(fds[0]);
    		close(fds[1]);
    		ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
    		return -1;
    	}
    
    		if (ast_opt_high_priority)
    			ast_set_priority(0);
    
    
    		close(fds[0]);
    		/* Stdout goes to pipe */
    		dup2(fds[1], STDOUT_FILENO);
    
    
    		/* Close everything else */
    		ast_close_fds_above_n(STDERR_FILENO);
    
    
    		if (strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
    
    			ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
    			_exit(1);
    		}
    
    		setpgid(0, getpid());
    
    		if (ast_test_flag(class, MOH_CUSTOM)) {
    
    		} else {
    			/* Default install is /usr/local/bin */
    			execv(LOCAL_MPG_123, argv);
    			/* Many places have it in /usr/bin */
    			execv(MPG_123, argv);
    			/* Check PATH as a last-ditch effort */
    			execvp("mpg123", argv);
    		}
    
    		/* Can't use logger, since log FDs are closed */
    		fprintf(stderr, "MOH: exec failed: %s\n", strerror(errno));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    static void *monmp3thread(void *data)
    {
    
    #define	MOH_MS_INTERVAL		100
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct mohclass *class = data;
    	struct mohdata *moh;
    	char buf[8192];
    	short sbuf[8192];
    	int res, res2;
    
    	struct timeval deadline, tv_tmp;
    
    	deadline.tv_sec = 0;
    	deadline.tv_usec = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	for(;/* ever */;) {
    
    		pthread_testcancel();
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Spawn mp3 player if it's not there */
    
    		if (class->srcfd < 0) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if ((class->srcfd = spawn_mp3(class)) < 0) {
    
    				ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    				/* Try again later */
    				sleep(500);
    
    				pthread_testcancel();
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    		}
    		if (class->pseudofd > -1) {
    
    #ifdef SOLARIS
    			thr_yield();
    #endif
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Pause some amount of time */
    			res = read(class->pseudofd, buf, sizeof(buf));
    
    			pthread_testcancel();
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else {
    
    			/* Reliable sleep */
    
    			if (ast_tvzero(deadline))
    				deadline = tv_tmp;
    			delta = ast_tvdiff_ms(tv_tmp, deadline);
    
    			if (delta < MOH_MS_INTERVAL) {	/* too early */
    
    				deadline = ast_tvadd(deadline, ast_samp2tv(MOH_MS_INTERVAL, 1000));	/* next deadline */
    
    				pthread_testcancel();
    
    			} else {
    				ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
    
    			res = 8 * MOH_MS_INTERVAL;	/* 8 samples per millisecond */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    		if ((strncasecmp(class->dir, "http://", 7) && strcasecmp(class->dir, "nodir")) && AST_LIST_EMPTY(&class->members))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			continue;
    		/* Read mp3 audio */
    
    		len = ast_codec_get_len(class->format, res);
    
    		if ((res2 = read(class->srcfd, sbuf, len)) != len) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (!res2) {
    				close(class->srcfd);
    				class->srcfd = -1;
    
    				pthread_testcancel();
    
    							ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno));
    						}
    						usleep(100000);
    						if (killpg(class->pid, SIGTERM) < 0) {
    							if (errno == ESRCH) {
    								break;
    							}
    							ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno));
    						}
    						usleep(100000);
    						if (killpg(class->pid, SIGKILL) < 0) {
    							if (errno == ESRCH) {
    								break;
    							}
    							ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno));
    						}
    					} while (0);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					class->pid = 0;
    				}
    
    				ast_debug(1, "Read %d bytes of audio while expecting %d\n", res2, len);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			continue;
    		}
    
    		pthread_testcancel();
    
    
    		ao2_lock(class);
    		AST_LIST_TRAVERSE(&class->members, moh, list) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* 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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    	return NULL;
    }
    
    
    static int play_moh_exec(struct ast_channel *chan, const char *data)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	char *parse;
    	char *class;
    	int timeout = -1;
    	int res;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(class);
    		AST_APP_ARG(duration);
    	);
    
    	parse = ast_strdupa(data);
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (!ast_strlen_zero(args.duration)) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (sscanf(args.duration, "%30d", &timeout) == 1) {
    
    			timeout *= 1000;
    		} else {
    			ast_log(LOG_WARNING, "Invalid MusicOnHold duration '%s'. Will wait indefinitely.\n", args.duration);
    		}
    	}
    
    	class = S_OR(args.class, NULL);
    	if (ast_moh_start(chan, class, NULL)) {
    		ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    
    	if (timeout > 0)
    		res = ast_safe_sleep(chan, timeout);
    	else {
    		while (!(res = ast_safe_sleep(chan, 10000)));
    	}
    
    
    static int wait_moh_exec(struct ast_channel *chan, const char *data)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	static int deprecation_warning = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res;
    
    
    	if (!deprecation_warning) {
    		deprecation_warning = 1;
    		ast_log(LOG_WARNING, "WaitMusicOnHold application is deprecated and will be removed. Use MusicOnHold with duration parameter instead\n");
    	}
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!data || !atoi(data)) {
    		ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
    		return -1;
    	}
    
    		ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    	res = ast_safe_sleep(chan, atoi(data) * 1000);
    	ast_moh_stop(chan);
    	return res;
    }
    
    
    static int set_moh_exec(struct ast_channel *chan, const char *data)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	static int deprecation_warning = 0;
    
    	if (!deprecation_warning) {
    		deprecation_warning = 1;
    		ast_log(LOG_WARNING, "SetMusicOnHold application is deprecated and will be removed. Use Set(CHANNEL(musicclass)=...) instead\n");
    	}
    
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	if (ast_strlen_zero(data)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
    		return -1;
    	}
    
    	ast_string_field_set(chan, musicclass, data);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    static int start_moh_exec(struct ast_channel *chan, const char *data)
    
    	char *parse;
    	char *class;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(class);
    	);
    
    	parse = ast_strdupa(data);
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	class = S_OR(args.class, NULL);
    
    		ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
    
    static int stop_moh_exec(struct ast_channel *chan, const char *data)
    
    #define get_mohbyname(a,b,c)	_get_mohbyname(a,b,c,__FILE__,__LINE__,__PRETTY_FUNCTION__)
    
    static struct mohclass *_get_mohbyname(const char *name, int warn, int flags, const char *file, int lineno, const char *funcname)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct mohclass *moh = NULL;
    
    	ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
    
    #ifdef REF_DEBUG
    	moh = __ao2_find_debug(mohclasses, &tmp_class, flags, "get_mohbyname", (char *) file, lineno, funcname);
    #else
    	moh = __ao2_find(mohclasses, &tmp_class, flags);
    #endif
    
    		ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    static struct mohdata *mohalloc(struct mohclass *cl)
    {
    	struct mohdata *moh;
    
    	long flags;	
    
    	if (!(moh = ast_calloc(1, sizeof(*moh))))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (pipe(moh->pipe)) {
    		ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return NULL;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Make entirely non-blocking */
    	flags = fcntl(moh->pipe[0], F_GETFL);
    	fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
    	flags = fcntl(moh->pipe[1], F_GETFL);
    	fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
    
    
    	moh->f.frametype = AST_FRAME_VOICE;
    
    	moh->f.subclass.codec = cl->format;
    
    	moh->f.offset = AST_FRIENDLY_OFFSET;
    
    
    	moh->parent = mohclass_ref(cl, "Reffing music class for mohdata parent");
    
    	AST_LIST_INSERT_HEAD(&cl->members, moh, list);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return moh;
    }
    
    static void moh_release(struct ast_channel *chan, void *data)
    {
    
    	struct mohdata *moh = data;
    
    	struct mohclass *class = moh->parent;
    
    	ao2_lock(class);
    	AST_LIST_REMOVE(&moh->parent->members, moh, list);	
    	ao2_unlock(class);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	close(moh->pipe[0]);
    	close(moh->pipe[1]);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	oldwfmt = moh->origwfmt;
    
    	moh->parent = class = mohclass_unref(class, "unreffing moh->parent upon deactivation of generator");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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));
    		}
    
    
    		ast_verb(3, "Stopped music on hold on %s\n", chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    }
    
    static void *moh_alloc(struct ast_channel *chan, void *params)
    {
    	struct mohdata *res;
    
    	struct mohclass *class = params;
    
    	struct moh_files_state *state;
    
    	/* Initiating music_state for current channel. Channel should know name of moh class */
    	if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
    		chan->music_state = state;
    
    		state->class = mohclass_ref(class, "Copying reference into state container");
    		ast_module_ref(ast_module_info->self);
    
    	} else
    		state = chan->music_state;
    	if (state && state->class != class) {
    		memset(state, 0, sizeof(*state));
    		state->class = class;
    	}
    
    	if ((res = mohalloc(class))) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		res->origwfmt = chan->writeformat;
    
    		if (ast_set_write_format(chan, class->format)) {
    			ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
    
    Mark Spencer's avatar
    Mark Spencer committed
    			moh_release(NULL, res);
    			res = NULL;
    		}
    
    		ast_verb(3, "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    	return res;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	struct mohdata *moh = data;
    
    	short buf[1280 + AST_FRIENDLY_OFFSET / 2];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res;
    
    	len = ast_codec_get_len(moh->parent->format, samples);
    
    
    	if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
    
    		ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
    
    		len = sizeof(buf) - AST_FRIENDLY_OFFSET;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    	res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
    
    	if (res <= 0)
    		return 0;
    
    
    	moh->f.datalen = res;
    
    	moh->f.data.ptr = buf + AST_FRIENDLY_OFFSET / 2;
    
    	moh->f.samples = ast_codec_get_samples(&moh->f);
    
    	if (ast_write(chan, &moh->f) < 0) {
    
    		ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
    		return -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    static struct ast_generator mohgen = {
    	.alloc    = moh_alloc,
    	.release  = moh_release,
    	.generate = moh_generate,
    
    	.digit    = moh_handle_digit,
    
    static int moh_add_file(struct mohclass *class, const char *filepath)
    {
    	if (!class->allowed_files) {
    		if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
    			return -1;
    		class->allowed_files = INITIAL_NUM_FILES;
    	} else if (class->total_files == class->allowed_files) {
    		if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
    			class->allowed_files = 0;
    			class->total_files = 0;
    			return -1;
    		}
    		class->allowed_files *= 2;
    	}
    
    	if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
    		return -1;
    
    	class->total_files++;
    
    	return 0;
    }
    
    
    static int moh_sort_compare(const void *i1, const void *i2)
    {
    	char *s1, *s2;
    
    	s1 = ((char **)i1)[0];
    	s2 = ((char **)i2)[0];
    
    	return strcasecmp(s1, s2);
    }
    
    
    static int moh_scan_files(struct mohclass *class) {
    
    	DIR *files_DIR;
    	struct dirent *files_dirent;
    
    	if (class->dir[0] != '/') {
    		ast_copy_string(dir_path, ast_config_AST_VAR_DIR, sizeof(dir_path));
    
    		strncat(dir_path, "/", sizeof(dir_path) - 1);
    		strncat(dir_path, class->dir, sizeof(dir_path) - 1);
    
    	} else {
    		ast_copy_string(dir_path, class->dir, sizeof(dir_path));
    	}
    	ast_debug(4, "Scanning '%s' for files for class '%s'\n", dir_path, class->name);
    	files_DIR = opendir(dir_path);