2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Routines implementing music on hold
23 * \arg See also \ref Config_moh
25 * \author Mark Spencer <markster@digium.com>
29 <conflict>win32</conflict>
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
40 #include <sys/signal.h>
41 #include <netinet/in.h>
44 #include <sys/ioctl.h>
49 #include "asterisk/zapata.h"
51 #include "asterisk/lock.h"
52 #include "asterisk/file.h"
53 #include "asterisk/channel.h"
54 #include "asterisk/pbx.h"
55 #include "asterisk/app.h"
56 #include "asterisk/module.h"
57 #include "asterisk/translate.h"
58 #include "asterisk/say.h"
59 #include "asterisk/musiconhold.h"
60 #include "asterisk/config.h"
61 #include "asterisk/utils.h"
62 #include "asterisk/cli.h"
63 #include "asterisk/stringfields.h"
64 #include "asterisk/linkedlists.h"
66 #define INITIAL_NUM_FILES 8
68 static char *play_moh = "MusicOnHold";
69 static char *wait_moh = "WaitMusicOnHold";
70 static char *set_moh = "SetMusicOnHold";
71 static char *start_moh = "StartMusicOnHold";
72 static char *stop_moh = "StopMusicOnHold";
74 static char *play_moh_syn = "Play Music On Hold indefinitely";
75 static char *wait_moh_syn = "Wait, playing Music On Hold";
76 static char *set_moh_syn = "Set default Music On Hold class";
77 static char *start_moh_syn = "Play Music On Hold";
78 static char *stop_moh_syn = "Stop Playing Music On Hold";
80 static char *play_moh_desc = " MusicOnHold(class[,duration]):\n"
81 "Plays hold music specified by class. If omitted, the default\n"
82 "music source for the channel will be used. Change the default \n"
83 "class with Set(CHANNEL(musicclass)=...).\n"
84 "If duration is given, hold music will be played specified number\n"
85 "of seconds. If duration is ommited, music plays indefinitely.\n"
86 "Returns 0 when done, -1 on hangup.\n";
88 static char *wait_moh_desc = " WaitMusicOnHold(delay):\n"
90 " !!! DEPRECATED. Use MusicOnHold instead !!!\n"
92 "Plays hold music specified number of seconds. Returns 0 when\n"
93 "done, or -1 on hangup. If no hold music is available, the delay will\n"
94 "still occur with no sound.\n"
96 " !!! DEPRECATED. Use MusicOnHold instead !!!\n";
98 static char *set_moh_desc = " SetMusicOnHold(class):\n"
100 " !!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!\n"
102 "Sets the default class for music on hold for a given channel. When\n"
103 "music on hold is activated, this class will be used to select which\n"
106 " !!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!\n";
108 static char *start_moh_desc = " StartMusicOnHold(class):\n"
109 "Starts playing music on hold, uses default music class for channel.\n"
110 "Starts playing music specified by class. If omitted, the default\n"
111 "music source for the channel will be used. Always returns 0.\n";
113 static char *stop_moh_desc = " StopMusicOnHold(): "
114 "Stops playing music on hold.\n";
116 static int respawn_time = 20;
118 struct moh_files_state {
119 struct mohclass *class;
125 char *save_pos_filename;
128 #define MOH_QUIET (1 << 0)
129 #define MOH_SINGLE (1 << 1)
130 #define MOH_CUSTOM (1 << 2)
131 #define MOH_RANDOMIZE (1 << 3)
132 #define MOH_SORTALPHA (1 << 4)
134 #define MOH_CACHERTCLASSES (1 << 5) /*!< Should we use a separate instance of MOH for each user or not */
136 static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */
139 char name[MAX_MUSICCLASS];
144 /*! A dynamically sized array to hold the list of filenames in "files" mode */
146 /*! The current size of the filearray */
148 /*! The current number of files loaded into the filearray */
151 /*! The format from the MOH source, not applicable to "files" mode */
153 /*! The pid of the external application delivering MOH */
157 /*! Source of audio */
159 /*! FD for timing source */
161 /*! Number of users */
163 /*! Created on the fly, from RT engine */
165 unsigned int delete:1;
166 AST_LIST_HEAD_NOLOCK(, mohdata) members;
167 AST_LIST_ENTRY(mohclass) list;
173 struct mohclass *parent;
175 AST_LIST_ENTRY(mohdata) list;
178 AST_RWLIST_HEAD_STATIC(mohclasses, mohclass);
180 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
181 #define MPG_123 "/usr/bin/mpg123"
184 static int ast_moh_destroy_one(struct mohclass *moh);
185 static int reload(void);
187 static void ast_moh_free_class(struct mohclass **mohclass)
189 struct mohdata *member;
190 struct mohclass *class = *mohclass;
193 while ((member = AST_LIST_REMOVE_HEAD(&class->members, list)))
197 pthread_cancel(class->thread);
201 if (class->filearray) {
202 for (i = 0; i < class->total_files; i++)
203 ast_free(class->filearray[i]);
204 ast_free(class->filearray);
212 static void moh_files_release(struct ast_channel *chan, void *data)
214 struct moh_files_state *state;
217 if ((state = chan->music_state)) {
219 ast_closestream(chan->stream);
222 ast_verb(3, "Stopped music on hold on %s\n", chan->name);
224 if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
225 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
227 state->save_pos = state->pos;
229 if (ast_atomic_dec_and_test(&state->class->inuse) && state->class->delete)
230 ast_moh_destroy_one(state->class);
236 static int ast_moh_files_next(struct ast_channel *chan)
238 struct moh_files_state *state = chan->music_state;
241 /* Discontinue a stream if it is running already */
243 ast_closestream(chan->stream);
247 if (!state->class->total_files) {
248 ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
252 /* If a specific file has been saved confirm it still exists and that it is still valid */
253 if (state->save_pos >= 0 && state->save_pos < state->class->total_files && state->class->filearray[state->save_pos] == state->save_pos_filename) {
254 state->pos = state->save_pos;
255 state->save_pos = -1;
256 } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
257 /* Get a random file and ensure we can open it */
258 for (tries = 0; tries < 20; tries++) {
259 state->pos = rand() % state->class->total_files;
260 if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
263 state->save_pos = -1;
266 /* This is easy, just increment our position and make sure we don't exceed the total file count */
268 state->pos %= state->class->total_files;
269 state->save_pos = -1;
273 if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
274 ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
276 state->pos %= state->class->total_files;
280 /* Record the pointer to the filename for position resuming later */
281 state->save_pos_filename = state->class->filearray[state->pos];
283 ast_debug(1, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
286 ast_seekstream(chan->stream, state->samples, SEEK_SET);
292 static struct ast_frame *moh_files_readframe(struct ast_channel *chan)
294 struct ast_frame *f = NULL;
296 if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
297 if (!ast_moh_files_next(chan))
298 f = ast_readframe(chan->stream);
304 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
306 struct moh_files_state *state = chan->music_state;
307 struct ast_frame *f = NULL;
310 state->sample_queue += samples;
312 while (state->sample_queue > 0) {
313 if ((f = moh_files_readframe(chan))) {
314 state->samples += f->samples;
315 state->sample_queue -= f->samples;
316 res = ast_write(chan, f);
319 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
329 static void *moh_files_alloc(struct ast_channel *chan, void *params)
331 struct moh_files_state *state;
332 struct mohclass *class = params;
334 if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
335 chan->music_state = state;
336 state->class = class;
337 state->save_pos = -1;
339 state = chan->music_state;
342 if (state->class != class) {
344 memset(state, 0, sizeof(*state));
345 state->class = class;
346 if (ast_test_flag(state->class, MOH_RANDOMIZE) && class->total_files)
347 state->pos = ast_random() % class->total_files;
350 state->origwfmt = chan->writeformat;
352 ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name);
355 return chan->music_state;
358 /*! \note This function should be called with the mohclasses list locked */
359 static struct mohclass *get_mohbydigit(char digit)
361 struct mohclass *moh = NULL;
363 AST_RWLIST_TRAVERSE(&mohclasses, moh, list) {
364 if (digit == moh->digit)
371 static void moh_handle_digit(struct ast_channel *chan, char digit)
373 struct mohclass *moh;
374 const char *classname = NULL;
376 AST_RWLIST_RDLOCK(&mohclasses);
377 if ((moh = get_mohbydigit(digit)))
378 classname = ast_strdupa(moh->name);
379 AST_RWLIST_UNLOCK(&mohclasses);
385 ast_moh_start(chan, classname, NULL);
388 static struct ast_generator moh_file_stream =
390 alloc: moh_files_alloc,
391 release: moh_files_release,
392 generate: moh_files_generator,
393 digit: moh_handle_digit,
396 static int spawn_mp3(struct mohclass *class)
400 char fns[MAX_MP3S][80];
401 char *argv[MAX_MP3S + 50];
409 if (!strcasecmp(class->dir, "nodir")) {
412 dir = opendir(class->dir);
413 if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) {
414 ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
419 if (!ast_test_flag(class, MOH_CUSTOM)) {
420 argv[argc++] = "mpg123";
423 argv[argc++] = "--mono";
425 argv[argc++] = "8000";
427 if (!ast_test_flag(class, MOH_SINGLE)) {
429 argv[argc++] = "2048";
434 if (ast_test_flag(class, MOH_QUIET))
435 argv[argc++] = "4096";
437 argv[argc++] = "8192";
439 /* Look for extra arguments and add them to the list */
440 ast_copy_string(xargs, class->args, sizeof(xargs));
442 while (!ast_strlen_zero(argptr)) {
443 argv[argc++] = argptr;
444 strsep(&argptr, ",");
447 /* Format arguments for argv vector */
448 ast_copy_string(xargs, class->args, sizeof(xargs));
450 while (!ast_strlen_zero(argptr)) {
451 argv[argc++] = argptr;
452 strsep(&argptr, " ");
457 if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) {
458 ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
459 argv[argc++] = fns[files];
462 while ((de = readdir(dir)) && (files < MAX_MP3S)) {
463 if ((strlen(de->d_name) > 3) &&
464 ((ast_test_flag(class, MOH_CUSTOM) &&
465 (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") ||
466 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
467 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
468 ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
469 argv[argc++] = fns[files];
479 ast_log(LOG_WARNING, "Pipe failed\n");
483 ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
488 if (time(NULL) - class->start < respawn_time) {
489 sleep(respawn_time - (time(NULL) - class->start));
493 class->pid = ast_safe_fork(0);
494 if (class->pid < 0) {
497 ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
501 if (ast_opt_high_priority)
505 /* Stdout goes to pipe */
506 dup2(fds[1], STDOUT_FILENO);
508 /* Close everything else */
509 ast_close_fds_above_n(STDERR_FILENO);
513 if (ast_test_flag(class, MOH_CUSTOM)) {
514 execv(argv[0], argv);
516 /* Default install is /usr/local/bin */
517 execv(LOCAL_MPG_123, argv);
518 /* Many places have it in /usr/bin */
519 execv(MPG_123, argv);
520 /* Check PATH as a last-ditch effort */
521 execvp("mpg123", argv);
523 /* Can't use logger, since log FDs are closed */
524 fprintf(stderr, "MOH: exec failed: %s\n", strerror(errno));
534 static void *monmp3thread(void *data)
536 #define MOH_MS_INTERVAL 100
538 struct mohclass *class = data;
544 struct timeval tv, tv_tmp;
549 pthread_testcancel();
550 /* Spawn mp3 player if it's not there */
551 if (class->srcfd < 0) {
552 if ((class->srcfd = spawn_mp3(class)) < 0) {
553 ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
554 /* Try again later */
556 pthread_testcancel();
559 if (class->pseudofd > -1) {
563 /* Pause some amount of time */
564 res = read(class->pseudofd, buf, sizeof(buf));
565 pthread_testcancel();
569 tv_tmp = ast_tvnow();
572 delta = ast_tvdiff_ms(tv_tmp, tv);
573 if (delta < MOH_MS_INTERVAL) { /* too early */
574 tv = ast_tvadd(tv, ast_samp2tv(MOH_MS_INTERVAL, 1000)); /* next deadline */
575 usleep(1000 * (MOH_MS_INTERVAL - delta));
576 pthread_testcancel();
578 ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
581 res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */
583 if (AST_LIST_EMPTY(&class->members))
586 len = ast_codec_get_len(class->format, res);
588 if ((res2 = read(class->srcfd, sbuf, len)) != len) {
592 pthread_testcancel();
593 if (class->pid > 1) {
594 kill(class->pid, SIGHUP);
596 kill(class->pid, SIGTERM);
598 kill(class->pid, SIGKILL);
602 ast_debug(1, "Read %d bytes of audio while expecting %d\n", res2, len);
606 pthread_testcancel();
607 AST_RWLIST_RDLOCK(&mohclasses);
608 AST_RWLIST_TRAVERSE(&class->members, moh, list) {
610 if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
611 ast_debug(1, "Only wrote %d of %d bytes to pipe\n", res, res2);
614 AST_RWLIST_UNLOCK(&mohclasses);
619 static int play_moh_exec(struct ast_channel *chan, void *data)
625 AST_DECLARE_APP_ARGS(args,
627 AST_APP_ARG(duration);
630 parse = ast_strdupa(data);
632 AST_STANDARD_APP_ARGS(args, parse);
634 if (!ast_strlen_zero(args.duration)) {
635 if (sscanf(args.duration, "%d", &timeout) == 1) {
638 ast_log(LOG_WARNING, "Invalid MusicOnHold duration '%s'. Will wait indefinitely.\n", args.duration);
642 class = S_OR(args.class, NULL);
643 if (ast_moh_start(chan, class, NULL)) {
644 ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
649 res = ast_safe_sleep(chan, timeout);
651 while (!(res = ast_safe_sleep(chan, 10000)));
659 static int wait_moh_exec(struct ast_channel *chan, void *data)
661 static int deprecation_warning = 0;
664 if (!deprecation_warning) {
665 deprecation_warning = 1;
666 ast_log(LOG_WARNING, "WaitMusicOnHold application is deprecated and will be removed. Use MusicOnHold with duration parameter instead\n");
669 if (!data || !atoi(data)) {
670 ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
673 if (ast_moh_start(chan, NULL, NULL)) {
674 ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
677 res = ast_safe_sleep(chan, atoi(data) * 1000);
682 static int set_moh_exec(struct ast_channel *chan, void *data)
684 static int deprecation_warning = 0;
686 if (!deprecation_warning) {
687 deprecation_warning = 1;
688 ast_log(LOG_WARNING, "SetMusicOnHold application is deprecated and will be removed. Use Set(CHANNEL(musicclass)=...) instead\n");
691 if (ast_strlen_zero(data)) {
692 ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
695 ast_string_field_set(chan, musicclass, data);
699 static int start_moh_exec(struct ast_channel *chan, void *data)
703 AST_DECLARE_APP_ARGS(args,
707 parse = ast_strdupa(data);
709 AST_STANDARD_APP_ARGS(args, parse);
711 class = S_OR(args.class, NULL);
712 if (ast_moh_start(chan, class, NULL))
713 ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
718 static int stop_moh_exec(struct ast_channel *chan, void *data)
725 /*! \note This function should be called with the mohclasses list locked */
726 static struct mohclass *get_mohbyname(const char *name, int warn)
728 struct mohclass *moh = NULL;
730 AST_RWLIST_TRAVERSE(&mohclasses, moh, list) {
731 if (!strcasecmp(name, moh->name))
736 ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name);
741 static struct mohdata *mohalloc(struct mohclass *cl)
746 if (!(moh = ast_calloc(1, sizeof(*moh))))
749 if (pipe(moh->pipe)) {
750 ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
755 /* Make entirely non-blocking */
756 flags = fcntl(moh->pipe[0], F_GETFL);
757 fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
758 flags = fcntl(moh->pipe[1], F_GETFL);
759 fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
761 moh->f.frametype = AST_FRAME_VOICE;
762 moh->f.subclass = cl->format;
763 moh->f.offset = AST_FRIENDLY_OFFSET;
767 AST_RWLIST_WRLOCK(&mohclasses);
768 AST_LIST_INSERT_HEAD(&cl->members, moh, list);
769 AST_RWLIST_UNLOCK(&mohclasses);
774 static void moh_release(struct ast_channel *chan, void *data)
776 struct mohdata *moh = data;
778 struct moh_files_state *state;
780 AST_RWLIST_WRLOCK(&mohclasses);
781 AST_RWLIST_REMOVE(&moh->parent->members, moh, list);
782 AST_RWLIST_UNLOCK(&mohclasses);
786 oldwfmt = moh->origwfmt;
787 state = chan->music_state;
788 if (moh->parent->delete && ast_atomic_dec_and_test(&moh->parent->inuse))
789 ast_moh_destroy_one(moh->parent);
790 if (ast_atomic_dec_and_test(&state->class->inuse) && state->class->delete)
791 ast_moh_destroy_one(state->class);
795 if (oldwfmt && ast_set_write_format(chan, oldwfmt))
796 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
797 ast_verb(3, "Stopped music on hold on %s\n", chan->name);
801 static void *moh_alloc(struct ast_channel *chan, void *params)
804 struct mohclass *class = params;
805 struct moh_files_state *state;
807 /* Initiating music_state for current channel. Channel should know name of moh class */
808 if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
809 chan->music_state = state;
810 state->class = class;
812 state = chan->music_state;
813 if (state && state->class != class) {
814 memset(state, 0, sizeof(*state));
815 state->class = class;
818 if ((res = mohalloc(class))) {
819 res->origwfmt = chan->writeformat;
820 if (ast_set_write_format(chan, class->format)) {
821 ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
822 moh_release(NULL, res);
825 ast_verb(3, "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
830 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
832 struct mohdata *moh = data;
833 short buf[1280 + AST_FRIENDLY_OFFSET / 2];
836 if (!moh->parent->pid)
839 len = ast_codec_get_len(moh->parent->format, samples);
841 if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
842 ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
843 len = sizeof(buf) - AST_FRIENDLY_OFFSET;
845 res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
849 moh->f.datalen = res;
850 moh->f.data = buf + AST_FRIENDLY_OFFSET / 2;
851 moh->f.samples = ast_codec_get_samples(&moh->f);
853 if (ast_write(chan, &moh->f) < 0) {
854 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
861 static struct ast_generator mohgen =
864 release: moh_release,
865 generate: moh_generate,
866 digit: moh_handle_digit
869 static int moh_add_file(struct mohclass *class, const char *filepath)
871 if (!class->allowed_files) {
872 if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
874 class->allowed_files = INITIAL_NUM_FILES;
875 } else if (class->total_files == class->allowed_files) {
876 if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
877 class->allowed_files = 0;
878 class->total_files = 0;
881 class->allowed_files *= 2;
884 if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
887 class->total_files++;
892 static int moh_sort_compare(const void *i1, const void *i2)
896 s1 = ((char **)i1)[0];
897 s2 = ((char **)i2)[0];
899 return strcasecmp(s1, s2);
902 static int moh_scan_files(struct mohclass *class) {
905 struct dirent *files_dirent;
907 char filepath[PATH_MAX];
913 files_DIR = opendir(class->dir);
915 ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir);
919 for (i = 0; i < class->total_files; i++)
920 ast_free(class->filearray[i]);
922 class->total_files = 0;
923 dirnamelen = strlen(class->dir) + 2;
924 getcwd(path, sizeof(path));
926 while ((files_dirent = readdir(files_DIR))) {
927 /* The file name must be at least long enough to have the file type extension */
928 if ((strlen(files_dirent->d_name) < 4))
931 /* Skip files that starts with a dot */
932 if (files_dirent->d_name[0] == '.')
935 /* Skip files without extensions... they are not audio */
936 if (!strchr(files_dirent->d_name, '.'))
939 snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name);
941 if (stat(filepath, &statbuf))
944 if (!S_ISREG(statbuf.st_mode))
947 if ((ext = strrchr(filepath, '.')))
950 /* if the file is present in multiple formats, ensure we only put it into the list once */
951 for (i = 0; i < class->total_files; i++)
952 if (!strcmp(filepath, class->filearray[i]))
955 if (i == class->total_files) {
956 if (moh_add_file(class, filepath))
963 if (ast_test_flag(class, MOH_SORTALPHA))
964 qsort(&class->filearray[0], class->total_files, sizeof(char *), moh_sort_compare);
965 return class->total_files;
968 static int moh_diff(struct mohclass *old, struct mohclass *new)
973 if (strcmp(old->dir, new->dir))
975 else if (strcmp(old->mode, new->mode))
977 else if (strcmp(old->args, new->args))
979 else if (old->flags != new->flags)
985 static int moh_register(struct mohclass *moh, int reload)
990 struct mohclass *mohclass = NULL;
993 AST_RWLIST_WRLOCK(&mohclasses);
994 if ((mohclass = get_mohbyname(moh->name, 0)) && !moh_diff(mohclass, moh)) {
995 if (!mohclass->delete) {
996 ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
998 AST_RWLIST_UNLOCK(&mohclasses);
1002 AST_RWLIST_UNLOCK(&mohclasses);
1005 moh->start -= respawn_time;
1007 if (!strcasecmp(moh->mode, "files")) {
1008 res = moh_scan_files(moh);
1011 if (option_verbose > 2)
1012 ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n", moh->dir, moh->name);
1014 ast_moh_free_class(&moh);
1017 if (strchr(moh->args, 'r'))
1018 ast_set_flag(moh, MOH_RANDOMIZE);
1019 } 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")) {
1021 if (!strcasecmp(moh->mode, "custom"))
1022 ast_set_flag(moh, MOH_CUSTOM);
1023 else if (!strcasecmp(moh->mode, "mp3nb"))
1024 ast_set_flag(moh, MOH_SINGLE);
1025 else if (!strcasecmp(moh->mode, "quietmp3nb"))
1026 ast_set_flag(moh, MOH_SINGLE | MOH_QUIET);
1027 else if (!strcasecmp(moh->mode, "quietmp3"))
1028 ast_set_flag(moh, MOH_QUIET);
1032 /* Open /dev/zap/pseudo for timing... Is
1033 there a better, yet reliable way to do this? */
1034 moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
1035 if (moh->pseudofd < 0) {
1036 ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
1039 ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x);
1044 if (ast_pthread_create_background(&moh->thread, NULL, monmp3thread, moh)) {
1045 ast_log(LOG_WARNING, "Unable to create moh...\n");
1046 if (moh->pseudofd > -1)
1047 close(moh->pseudofd);
1048 ast_moh_free_class(&moh);
1052 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
1053 ast_moh_free_class(&moh);
1057 AST_RWLIST_WRLOCK(&mohclasses);
1058 AST_RWLIST_INSERT_HEAD(&mohclasses, moh, list);
1059 AST_RWLIST_UNLOCK(&mohclasses);
1064 static void local_ast_moh_cleanup(struct ast_channel *chan)
1066 struct moh_files_state *state = chan->music_state;
1069 if (state->class->realtime) {
1070 if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
1071 /* We are cleaning out cached RT class, we should remove it from list, if no one else using it */
1072 if (!(state->class->inuse)) {
1073 /* Remove this class from list */
1074 AST_RWLIST_WRLOCK(&mohclasses);
1075 AST_RWLIST_REMOVE(&mohclasses, state->class, list);
1076 AST_RWLIST_UNLOCK(&mohclasses);
1078 /* Free some memory */
1079 ast_moh_destroy_one(state->class);
1082 ast_moh_destroy_one(state->class);
1085 ast_free(chan->music_state);
1086 chan->music_state = NULL;
1090 static struct mohclass *moh_class_malloc(void)
1092 struct mohclass *class;
1094 if ((class = ast_calloc(1, sizeof(*class)))) {
1095 class->format = AST_FORMAT_SLINEAR;
1096 class->realtime = 0;
1102 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
1104 struct mohclass *mohclass = NULL;
1105 struct ast_variable *var = NULL;
1106 struct ast_variable *tmp = NULL;
1107 struct moh_files_state *state = chan->music_state;
1112 /* The following is the order of preference for which class to use:
1113 * 1) The channels explicitly set musicclass, which should *only* be
1114 * set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
1115 * 2) The mclass argument. If a channel is calling ast_moh_start() as the
1116 * result of receiving a HOLD control frame, this should be the
1117 * payload that came with the frame.
1118 * 3) The interpclass argument. This would be from the mohinterpret
1119 * option from channel drivers. This is the same as the old musicclass
1121 * 4) The default class.
1124 /* First, let's check in memory for static and cached RT classes */
1125 AST_RWLIST_RDLOCK(&mohclasses);
1126 if (!ast_strlen_zero(chan->musicclass))
1127 mohclass = get_mohbyname(chan->musicclass, 1);
1128 if (!mohclass && !ast_strlen_zero(mclass))
1129 mohclass = get_mohbyname(mclass, 1);
1130 if (!mohclass && !ast_strlen_zero(interpclass))
1131 mohclass = get_mohbyname(interpclass, 1);
1132 AST_RWLIST_UNLOCK(&mohclasses);
1134 /* If no moh class found in memory, then check RT */
1135 if (!mohclass && ast_check_realtime("musiconhold")) {
1136 if (!ast_strlen_zero(chan->musicclass)) {
1137 var = ast_load_realtime("musiconhold", "name", chan->musicclass, NULL);
1139 if (!var && !ast_strlen_zero(mclass))
1140 var = ast_load_realtime("musiconhold", "name", mclass, NULL);
1141 if (!var && !ast_strlen_zero(interpclass))
1142 var = ast_load_realtime("musiconhold", "name", interpclass, NULL);
1144 var = ast_load_realtime("musiconhold", "name", "default", NULL);
1145 if (var && (mohclass = moh_class_malloc())) {
1146 mohclass->realtime = 1;
1147 for (tmp = var; tmp; tmp = tmp->next) {
1148 if (!strcasecmp(tmp->name, "name"))
1149 ast_copy_string(mohclass->name, tmp->value, sizeof(mohclass->name));
1150 else if (!strcasecmp(tmp->name, "mode"))
1151 ast_copy_string(mohclass->mode, tmp->value, sizeof(mohclass->mode));
1152 else if (!strcasecmp(tmp->name, "directory"))
1153 ast_copy_string(mohclass->dir, tmp->value, sizeof(mohclass->dir));
1154 else if (!strcasecmp(tmp->name, "application"))
1155 ast_copy_string(mohclass->args, tmp->value, sizeof(mohclass->args));
1156 else if (!strcasecmp(tmp->name, "digit") && (isdigit(*tmp->value) || strchr("*#", *tmp->value)))
1157 mohclass->digit = *tmp->value;
1158 else if (!strcasecmp(tmp->name, "random"))
1159 ast_set2_flag(mohclass, ast_true(tmp->value), MOH_RANDOMIZE);
1160 else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "random"))
1161 ast_set_flag(mohclass, MOH_RANDOMIZE);
1162 else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "alpha"))
1163 ast_set_flag(mohclass, MOH_SORTALPHA);
1164 else if (!strcasecmp(tmp->name, "format")) {
1165 mohclass->format = ast_getformatbyname(tmp->value);
1166 if (!mohclass->format) {
1167 ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", tmp->value);
1168 mohclass->format = AST_FORMAT_SLINEAR;
1172 ast_variables_destroy(var);
1173 if (ast_strlen_zero(mohclass->dir)) {
1174 if (!strcasecmp(mohclass->mode, "custom")) {
1175 strcpy(mohclass->dir, "nodir");
1177 ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name);
1182 if (ast_strlen_zero(mohclass->mode)) {
1183 ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", mohclass->name);
1187 if (ast_strlen_zero(mohclass->args) && !strcasecmp(mohclass->mode, "custom")) {
1188 ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", mohclass->name);
1193 if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
1194 /* CACHERTCLASSES enabled, let's add this class to default tree */
1195 if (state && state->class) {
1196 /* Class already exist for this channel */
1197 ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
1198 if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
1199 /* we found RT class with the same name, seems like we should continue playing existing one */
1200 ast_moh_free_class(&mohclass);
1201 mohclass = state->class;
1204 moh_register(mohclass, 0);
1207 /* We don't register RT moh class, so let's init it manualy */
1209 time(&mohclass->start);
1210 mohclass->start -= respawn_time;
1212 if (!strcasecmp(mohclass->mode, "files")) {
1213 if (!moh_scan_files(mohclass)) {
1214 ast_moh_free_class(&mohclass);
1217 if (strchr(mohclass->args, 'r'))
1218 ast_set_flag(mohclass, MOH_RANDOMIZE);
1219 } else if (!strcasecmp(mohclass->mode, "mp3") || !strcasecmp(mohclass->mode, "mp3nb") || !strcasecmp(mohclass->mode, "quietmp3") || !strcasecmp(mohclass->mode, "quietmp3nb") || !strcasecmp(mohclass->mode, "httpmp3") || !strcasecmp(mohclass->mode, "custom")) {
1221 if (!strcasecmp(mohclass->mode, "custom"))
1222 ast_set_flag(mohclass, MOH_CUSTOM);
1223 else if (!strcasecmp(mohclass->mode, "mp3nb"))
1224 ast_set_flag(mohclass, MOH_SINGLE);
1225 else if (!strcasecmp(mohclass->mode, "quietmp3nb"))
1226 ast_set_flag(mohclass, MOH_SINGLE | MOH_QUIET);
1227 else if (!strcasecmp(mohclass->mode, "quietmp3"))
1228 ast_set_flag(mohclass, MOH_QUIET);
1230 mohclass->srcfd = -1;
1232 /* Open /dev/zap/pseudo for timing... Is
1233 there a better, yet reliable way to do this? */
1234 mohclass->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
1235 if (mohclass->pseudofd < 0) {
1236 ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
1239 ioctl(mohclass->pseudofd, ZT_SET_BLOCKSIZE, &x);
1242 mohclass->pseudofd = -1;
1244 /* Let's check if this channel already had a moh class before */
1245 if (state && state->class) {
1246 /* Class already exist for this channel */
1247 ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
1248 if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
1249 /* we found RT class with the same name, seems like we should continue playing existing one */
1250 ast_moh_free_class(&mohclass);
1251 mohclass = state->class;
1255 if (ast_pthread_create_background(&mohclass->thread, NULL, monmp3thread, mohclass)) {
1256 ast_log(LOG_WARNING, "Unable to create moh...\n");
1257 if (mohclass->pseudofd > -1)
1258 close(mohclass->pseudofd);
1259 ast_moh_free_class(&mohclass);
1264 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mohclass->mode);
1265 ast_moh_free_class(&mohclass);
1272 ast_variables_destroy(var);
1277 /* Requested MOH class not found, check for 'default' class in musiconhold.conf */
1279 AST_RWLIST_RDLOCK(&mohclasses);
1280 mohclass = get_mohbyname("default", 1);
1282 ast_atomic_fetchadd_int(&mohclass->inuse, +1);
1283 AST_RWLIST_UNLOCK(&mohclasses);
1285 AST_RWLIST_RDLOCK(&mohclasses);
1286 ast_atomic_fetchadd_int(&mohclass->inuse, +1);
1287 AST_RWLIST_UNLOCK(&mohclasses);
1293 ast_set_flag(chan, AST_FLAG_MOH);
1294 if (mohclass->total_files) {
1295 return ast_activate_generator(chan, &moh_file_stream, mohclass);
1297 return ast_activate_generator(chan, &mohgen, mohclass);
1300 static void local_ast_moh_stop(struct ast_channel *chan)
1302 struct moh_files_state *state = chan->music_state;
1303 ast_clear_flag(chan, AST_FLAG_MOH);
1304 ast_deactivate_generator(chan);
1308 ast_closestream(chan->stream);
1309 chan->stream = NULL;
1314 static int load_moh_classes(int reload)
1316 struct ast_config *cfg;
1317 struct ast_variable *var;
1318 struct mohclass *class;
1321 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1323 cfg = ast_config_load("musiconhold.conf", config_flags);
1325 if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED)
1329 AST_RWLIST_WRLOCK(&mohclasses);
1330 AST_RWLIST_TRAVERSE(&mohclasses, class, list) {
1333 AST_RWLIST_UNLOCK(&mohclasses);
1336 ast_clear_flag(global_flags, AST_FLAGS_ALL);
1338 cat = ast_category_browse(cfg, NULL);
1339 for (; cat; cat = ast_category_browse(cfg, cat)) {
1340 /* Setup common options from [general] section */
1341 if (!strcasecmp(cat, "general")) {
1342 var = ast_variable_browse(cfg, cat);
1344 if (!strcasecmp(var->name, "cachertclasses")) {
1345 ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES);
1347 ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name);
1352 /* These names were deprecated in 1.4 and should not be used until after the next major release. */
1353 if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files") && strcasecmp(cat, "general")) {
1354 if (!(class = moh_class_malloc()))
1357 ast_copy_string(class->name, cat, sizeof(class->name));
1358 var = ast_variable_browse(cfg, cat);
1360 if (!strcasecmp(var->name, "mode"))
1361 ast_copy_string(class->mode, var->value, sizeof(class->mode));
1362 else if (!strcasecmp(var->name, "directory"))
1363 ast_copy_string(class->dir, var->value, sizeof(class->dir));
1364 else if (!strcasecmp(var->name, "application"))
1365 ast_copy_string(class->args, var->value, sizeof(class->args));
1366 else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value)))
1367 class->digit = *var->value;
1368 else if (!strcasecmp(var->name, "random"))
1369 ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
1370 else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random"))
1371 ast_set_flag(class, MOH_RANDOMIZE);
1372 else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha"))
1373 ast_set_flag(class, MOH_SORTALPHA);
1374 else if (!strcasecmp(var->name, "format")) {
1375 class->format = ast_getformatbyname(var->value);
1376 if (!class->format) {
1377 ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
1378 class->format = AST_FORMAT_SLINEAR;
1384 if (ast_strlen_zero(class->dir)) {
1385 if (!strcasecmp(class->mode, "custom")) {
1386 strcpy(class->dir, "nodir");
1388 ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
1393 if (ast_strlen_zero(class->mode)) {
1394 ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
1398 if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
1399 ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
1404 /* Don't leak a class when it's already registered */
1405 moh_register(class, reload);
1411 ast_config_destroy(cfg);
1416 static int ast_moh_destroy_one(struct mohclass *moh)
1419 int bytes, tbytes = 0, stime = 0, pid = 0;
1423 ast_debug(1, "killing %d!\n", moh->pid);
1424 stime = time(NULL) + 2;
1427 /* Back when this was just mpg123, SIGKILL was fine. Now we need
1428 * to give the process a reason and time enough to kill off its
1435 while ((ast_wait_for_input(moh->srcfd, 100) > 0) && (bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime)
1436 tbytes = tbytes + bytes;
1437 ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
1440 ast_moh_free_class(&moh);
1446 static void ast_moh_destroy(void)
1448 struct mohclass *moh;
1450 ast_verb(2, "Destroying musiconhold processes\n");
1452 AST_RWLIST_WRLOCK(&mohclasses);
1453 while ((moh = AST_RWLIST_REMOVE_HEAD(&mohclasses, list))) {
1454 ast_moh_destroy_one(moh);
1456 AST_RWLIST_UNLOCK(&mohclasses);
1459 static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1463 e->command = "moh reload";
1465 "Usage: moh reload\n"
1466 " Reloads the MusicOnHold module.\n"
1467 " Alias for 'module reload res_musiconhold.so'\n";
1473 if (a->argc != e->args)
1474 return CLI_SHOWUSAGE;
1481 static char *handle_cli_moh_show_files(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1484 struct mohclass *class;
1488 e->command = "moh show files";
1490 "Usage: moh show files\n"
1491 " Lists all loaded file-based MusicOnHold classes and their\n"
1498 if (a->argc != e->args)
1499 return CLI_SHOWUSAGE;
1501 AST_RWLIST_RDLOCK(&mohclasses);
1502 AST_RWLIST_TRAVERSE(&mohclasses, class, list) {
1503 if (!class->total_files)
1506 ast_cli(a->fd, "Class: %s\n", class->name);
1507 for (i = 0; i < class->total_files; i++)
1508 ast_cli(a->fd, "\tFile: %s\n", class->filearray[i]);
1510 AST_RWLIST_UNLOCK(&mohclasses);
1515 static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1517 struct mohclass *class;
1521 e->command = "moh show classes";
1523 "Usage: moh show classes\n"
1524 " Lists all MusicOnHold classes.\n";
1530 if (a->argc != e->args)
1531 return CLI_SHOWUSAGE;
1533 AST_RWLIST_RDLOCK(&mohclasses);
1534 AST_RWLIST_TRAVERSE(&mohclasses, class, list) {
1535 ast_cli(a->fd, "Class: %s\n", class->name);
1536 ast_cli(a->fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
1537 ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
1538 ast_cli(a->fd, "\tUse Count: %d\n", class->inuse);
1540 ast_cli(a->fd, "\tDigit: %c\n", class->digit);
1541 if (ast_test_flag(class, MOH_CUSTOM))
1542 ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
1543 if (strcasecmp(class->mode, "files"))
1544 ast_cli(a->fd, "\tFormat: %s\n", ast_getformatname(class->format));
1546 AST_RWLIST_UNLOCK(&mohclasses);
1551 static struct ast_cli_entry cli_moh[] = {
1552 AST_CLI_DEFINE(handle_cli_moh_reload, "Reload MusicOnHold"),
1553 AST_CLI_DEFINE(handle_cli_moh_show_classes, "List MusicOnHold classes"),
1554 AST_CLI_DEFINE(handle_cli_moh_show_files, "List MusicOnHold file-based classes")
1557 static int init_classes(int reload)
1559 struct mohclass *moh;
1561 if (!load_moh_classes(reload)) /* Load classes from config */
1562 return 0; /* Return if nothing is found */
1564 AST_RWLIST_WRLOCK(&mohclasses);
1565 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&mohclasses, moh, list) {
1566 if (reload && moh->delete) {
1567 AST_RWLIST_REMOVE_CURRENT(list);
1569 ast_moh_destroy_one(moh);
1572 AST_RWLIST_TRAVERSE_SAFE_END
1573 AST_RWLIST_UNLOCK(&mohclasses);
1578 static int load_module(void)
1582 res = ast_register_application(play_moh, play_moh_exec, play_moh_syn, play_moh_desc);
1583 ast_register_atexit(ast_moh_destroy);
1584 ast_cli_register_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
1586 res = ast_register_application(wait_moh, wait_moh_exec, wait_moh_syn, wait_moh_desc);
1588 res = ast_register_application(set_moh, set_moh_exec, set_moh_syn, set_moh_desc);
1590 res = ast_register_application(start_moh, start_moh_exec, start_moh_syn, start_moh_desc);
1592 res = ast_register_application(stop_moh, stop_moh_exec, stop_moh_syn, stop_moh_desc);
1594 if (!init_classes(0)) { /* No music classes configured, so skip it */
1595 ast_log(LOG_WARNING, "No music on hold classes configured, disabling music on hold.\n");
1597 ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
1600 return AST_MODULE_LOAD_SUCCESS;
1603 static int reload(void)
1605 if (init_classes(1))
1606 ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
1611 static int unload_module(void)
1614 struct mohclass *class = NULL;
1616 AST_RWLIST_WRLOCK(&mohclasses);
1617 AST_LIST_TRAVERSE(&mohclasses, class, list) {
1618 if (class->inuse > 0) {
1623 AST_RWLIST_UNLOCK(&mohclasses);
1625 ast_log(LOG_WARNING, "Unable to unload res_musiconhold due to active MOH channels\n");
1629 ast_uninstall_music_functions();
1631 res = ast_unregister_application(play_moh);
1632 res |= ast_unregister_application(wait_moh);
1633 res |= ast_unregister_application(set_moh);
1634 res |= ast_unregister_application(start_moh);
1635 res |= ast_unregister_application(stop_moh);
1636 ast_cli_unregister_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
1640 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Music On Hold Resource",
1641 .load = load_module,
1642 .unload = unload_module,