X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=audiod.c;h=ed5344c076636f77e23fdf6c756f62e6a369a995;hp=462ecc8baefe442c8b383f253a7a57bb086cf591;hb=b3fd38bd1965ca8fec37cdd94552dc0f62f41372;hpb=6df4ed2fc04c7eeb4474e7d152e4374c219c3aac diff --git a/audiod.c b/audiod.c index 462ecc8b..ed5344c0 100644 --- a/audiod.c +++ b/audiod.c @@ -18,226 +18,95 @@ /** \file audiod.c the paraslash's audio daemon */ -#include /* gettimeofday */ #include "para.h" #include "audiod.cmdline.h" #include "list.h" -#include "close_on_fork.h" +#include "sched.h" #include "recv.h" #include "filter.h" #include "grab_client.cmdline.h" #include "grab_client.h" - -#include "error.h" +#include "client.cmdline.h" +#include "client.h" #include "audiod.h" #include "net.h" #include "daemon.h" #include "string.h" #include "fd.h" +#include "write.h" +#include "write_common.h" +#include "error.h" /** define the array of error lists needed by para_audiod */ INIT_AUDIOD_ERRLISTS; /** define the array containing all supported audio formats */ DEFINE_AUDIO_FORMAT_ARRAY; -/** - * the possible modes of operation - * - * - off: disconnect from para_server - * - on: receive status information from para_server and play the audio stream - * - sb: only receive status information but not the audio stream -*/ -enum {AUDIOD_OFF, AUDIOD_ON, AUDIOD_STANDBY}; - /** defines how to handle one supported audio format */ struct audio_format_info { -/** pointer to the receiver for this audio format */ + /** pointer to the receiver for this audio format */ struct receiver *receiver; -/** the receiver configuration */ + /** the receiver configuration */ void *receiver_conf; -/** the number of filters that should be activated for this audio format */ + /** the number of filters that should be activated for this audio format */ unsigned int num_filters; -/** pointer to the array of filters to be activated */ + /** pointer to the array of filters to be activated */ struct filter **filters; -/** pointer to the array of filter configurations */ + /** pointer to the array of filter configurations */ void **filter_conf; -/** output of the last filter is written to stdin of this command */ - char *write_cmd; -/** do not start receiver/filters/writer before this time */ + /** the number of filters that should be activated for this audio format */ + unsigned int num_writers; + /** pointer to the array of writers to be activated */ + struct writer **writers; + /** pointer to the array of writer configurations */ + void **writer_conf; + /** do not start receiver/filters/writer before this time */ struct timeval restart_barrier; }; -/** - * describes one instance of a receiver-filter-writer chain - * - * \sa receier_node, receiver, filter, filter_node, filter_chain_info - */ -struct slot_info { -/** number of the audio format in this slot */ - int format; -/** the file descriptor of the writer */ - int write_fd; -/** the process id of the writer */ - pid_t wpid; -/** time of the last successful read from the receiver */ - struct timeval rtime; -/** time the last write to the write fd happend */ - struct timeval wtime; -/** writer start time */ - struct timeval wstime; -/** did we include \a write_fd in the fdset */ - int wcheck; -/** set to one if we have sent the TERM signal to \a wpid */ - int wkilled; -/** the receiver info associated with this slot */ - struct receiver_node *receiver_node; -/** the active filter chain */ - struct filter_chain_info *fci; -}; +struct slot_info slot[MAX_STREAM_SLOTS]; -static struct slot_info slot[MAX_STREAM_SLOTS]; - -/** defines one command of para_audiod */ -struct audiod_command { -/** the name of the command */ -const char *name; -/** pointer to the function that handles the command */ -int (*handler)(int, int, char**); -/** one-line description of the command */ -const char *description; -/** summary of the command line options */ -const char *synopsis; -/** the long help text */ -const char *help; -}; -extern const char *status_item_list[NUM_STAT_ITEMS]; - -static int com_grab(int, int, char **); -static int com_cycle(int, int, char **); -static int com_help(int, int, char **); -static int com_off(int, int, char **); -static int com_on(int, int, char **); -static int com_sb(int, int, char **); -static int com_stat(int, int, char **); -static int com_term(int, int, char **); -static int stat_pipe = -1, signal_pipe; - -static struct gengetopt_args_info conf; -static struct timeval server_stream_start, sa_time_diff; -static int playing, current_decoder = -1, - audiod_status = AUDIOD_ON, offset_seconds, length_seconds, - sa_time_diff_sign = 1, audiod_socket = -1; -static char *af_status, /* the audio format announced in server status */ - *socket_name, *hostname; -static char *stat_item_values[NUM_STAT_ITEMS]; -static FILE *logfile; -static const struct timeval restart_delay = {0, 300 * 1000}; +int audiod_status = AUDIOD_ON; +struct audiod_args_info conf; +static char *socket_name; +static FILE *logfile; static struct audio_format_info afi[NUM_AUDIO_FORMATS]; -static struct audiod_command cmds[] = { -{ -.name = "cycle", -.handler = com_cycle, -.description = "switch to next mode", -.synopsis = "cycle", -.help = - -"on -> standby -> off -> on\n" - -}, -{ -.name = "grab", -.handler = com_grab, -.description = "grab the audio stream", -.synopsis = "-- grab [grab_options]", -.help = - -"grab ('splice') the audio stream at any position in the filter \n" -"chain and send that data back to the client. Try\n" -"\t para_audioc -- grab -h\n" -"for the list of available options.\n" -}, - -{ -.name = "help", -.handler = com_help, -.description = "display command list or help for given command", -.synopsis = "help [command]", -.help = - -"When I was younger, so much younger than today, I never needed\n" -"anybody's help in any way. But now these days are gone, I'm not so\n" -"self assured. Now I find I've changed my mind and opened up the doors.\n" -"\n" -" -- Beatles: Help\n" - -}, -{ -.name = "off", -.handler = com_off, -.description = "deactivate para_audiod", -.synopsis = "off", -.help = - -"Close connection to para_server and stop all decoders.\n" - -}, -{ -.name = "on", -.handler = com_on, -.description = "activate para_audiod", -.synopsis = "on", -.help = - -"Establish connection to para_server, retrieve para_server's current\n" -"status. If playing, start corresponding decoder. Otherwise stop\n" -"all decoders.\n" - -}, -{ -.name = "sb", -.handler = com_sb, -.description = "enter standby mode", -.synopsis = "sb", -.help = - -"Stop all decoders but leave connection to para_server open.\n" +static struct signal_task signal_task_struct, *sig_task = &signal_task_struct; -}, -{ -.name = "stat", -.handler = com_stat, -.description = "print status information", -.synopsis = "stat [item1 ...]", -.help = - -"Dump given status items (all if none given) to stdout.\n" - -}, -{ -.name = "term", -.handler = com_term, -.description = "terminate audiod", -.synopsis = "term", -.help = +static struct status_task status_task_struct; +struct status_task *stat_task = &status_task_struct; +static struct timeval initial_delay_barrier; -"Stop all decoders, shut down connection to para_server and exit.\n" +/** + * the task for handling audiod commands + * + * \sa struct task, struct sched + */ +struct command_task { + /** the local listening socket */ + int fd; + /** the associated task structure */ + struct task task; +}; -}, -{ -.name = NULL, -} +/** + * task for signal handling + */ +struct signal_task { + /** the signal pipe */ + int fd; + /** the number of the most recent signal */ + int signum; + /** the associated task structure */ + struct task task; }; -/** iterate over all slots */ -#define FOR_EACH_SLOT(slot) for (slot = 0; slot < MAX_STREAM_SLOTS; slot++) /** iterate over all supported audio formats */ #define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++) -/** iterate over the array of all audiod commands */ -#define FOR_EACH_COMMAND(c) for (c = 0; cmds[c].name; c++) /** * get the audio format number @@ -265,9 +134,12 @@ void para_log(int ll, const char* fmt,...) struct tm *tm; time_t t1; char str[MAXLINE] = ""; + static char *hostname; if (ll < conf.loglevel_arg) return; + if (!hostname) + hostname = para_hostname(); outfd = logfile? logfile : stderr; time(&t1); tm = localtime(&t1); @@ -280,103 +152,6 @@ void para_log(int ll, const char* fmt,...) va_end(argp); } -static int client_write(int fd, const char *buf) -{ - size_t len = strlen(buf); - return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1; -} - -static char *get_time_string(struct timeval *newest_stime) -{ - struct timeval now, diff, adj_stream_start, tmp; - int total = 0, use_server_time = 1; - - if (!playing) { - if (length_seconds) - return NULL; - return make_message("%s:\n", status_item_list[SI_PLAY_TIME]); - } - if (audiod_status == AUDIOD_OFF) - goto out; - if (sa_time_diff_sign > 0) - tv_diff(&server_stream_start, &sa_time_diff, - &adj_stream_start); - else - tv_add(&server_stream_start, &sa_time_diff, - &adj_stream_start); - tmp = adj_stream_start; - if (newest_stime && audiod_status == AUDIOD_ON) { - tv_diff(newest_stime, &adj_stream_start, &diff); - if (tv2ms(&diff) < 5000) { - tmp = *newest_stime; - use_server_time = 0; - } - } - gettimeofday(&now, NULL); - tv_diff(&now, &tmp, &diff); - total = diff.tv_sec + offset_seconds; - if (total > length_seconds) - total = length_seconds; - if (total < 0) - total = 0; -out: - return make_message( - "%s:%s%d:%02d [%d:%02d] (%d%%/%d:%02d)\n", - status_item_list[SI_PLAY_TIME], - use_server_time? "~" : "", - total / 60, - total % 60, - (length_seconds - total) / 60, - (length_seconds - total) % 60, - length_seconds? (total * 100 + length_seconds / 2) / - length_seconds : 0, - length_seconds / 60, - length_seconds % 60 - ); -} - -__malloc static char *audiod_status_string(void) -{ - const char *status = (audiod_status == AUDIOD_ON)? - "on" : (audiod_status == AUDIOD_OFF)? "off": "sb"; - return make_message("%s:%s\n", status_item_list[SI_AUDIOD_STATUS], status); -} - -static struct timeval *wstime(void) -{ - int i; - struct timeval *max = NULL; - FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - if (s->wpid <= 0) - continue; - if (max && tv_diff(&s->wstime, max, NULL) <= 0) - continue; - max = &s->wstime; - } - return max; -} -__malloc static char *decoder_flags(void) -{ - int i; - char decoder_flags[MAX_STREAM_SLOTS + 1]; - - FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - char flag = '0'; - if (s->receiver_node) - flag += 1; - if (s->wpid > 0) - flag += 2; - if (flag != '0') - flag += s->format * 4; - decoder_flags[i] = flag; - } - decoder_flags[MAX_STREAM_SLOTS] = '\0'; - return make_message("%s:%s\n", status_item_list[SI_DECODER_FLAGS], - decoder_flags); -} - static char *configfile_exists(void) { static char *config_file; @@ -391,47 +166,14 @@ static char *configfile_exists(void) static void setup_signal_handling(void) { - signal_pipe = para_signal_init(); - PARA_INFO_LOG("signal pipe: fd %d\n", signal_pipe); + sig_task->fd = para_signal_init(); + PARA_INFO_LOG("signal pipe: fd %d\n", sig_task->fd); para_install_sighandler(SIGINT); para_install_sighandler(SIGTERM); - para_install_sighandler(SIGCHLD); para_install_sighandler(SIGHUP); signal(SIGPIPE, SIG_IGN); } -static void audiod_status_dump(void) -{ - static char *p_ts, *p_us, *p_as, *p_df; - struct timeval *t = wstime(); - char *us, *tmp = get_time_string(t); - - if (tmp && (!p_ts || strcmp(tmp, p_ts))) - stat_client_write(tmp, SI_PLAY_TIME); - free(p_ts); - p_ts = tmp; - - us = uptime_str(); - tmp = make_message("%s:%s\n", status_item_list[SI_AUDIOD_UPTIME], us); - free(us); - if (!p_us || strcmp(p_us, tmp)) - stat_client_write(tmp, SI_AUDIOD_UPTIME); - free(p_us); - p_us = tmp; - - tmp = audiod_status_string(); - if (!p_as || strcmp(p_as, tmp)) - stat_client_write(tmp, SI_AUDIOD_STATUS); - free(p_as); - p_as = tmp; - - tmp = decoder_flags(); - if (!p_df || strcmp(p_df, tmp)) - stat_client_write(tmp, SI_DECODER_FLAGS); - free(p_df); - p_df = tmp; -} - static void clear_slot(int slot_num) { struct slot_info *s = &slot[slot_num]; @@ -441,30 +183,6 @@ static void clear_slot(int slot_num) s->format = -1; } -static void kill_stream_writer(int slot_num) -{ - struct slot_info *s = &slot[slot_num]; - - if (s->format < 0 || s->wkilled || s->wpid <= 0) - return; - PARA_DEBUG_LOG("kill -TERM %d (%s stream writer in slot %d)\n", - s->wpid, audio_formats[s->format], slot_num); - kill(s->wpid, SIGTERM); - s->wkilled = 1; - s->fci->error = 1; -} - -static void set_restart_barrier(int format, struct timeval *now) -{ - struct timeval tmp; - - if (now) - tmp = *now; - else - gettimeofday(&tmp, NULL); - tv_add(&tmp, &restart_delay, &afi[format].restart_barrier); -} - static void close_receiver(int slot_num) { struct slot_info *s = &slot[slot_num]; @@ -473,53 +191,36 @@ static void close_receiver(int slot_num) if (s->format < 0 || !s->receiver_node) return; a = &afi[s->format]; - PARA_NOTICE_LOG("closing %s recevier in slot %d\n", - audio_formats[s->format] , slot_num); + PARA_NOTICE_LOG("closing %s receiver in slot %d (eof = %d)\n", + audio_formats[s->format] , slot_num, s->receiver_node->eof); a->receiver->close(s->receiver_node); free(s->receiver_node); s->receiver_node = NULL; - set_restart_barrier(s->format, NULL); } static void kill_all_decoders(void) { int i; - FOR_EACH_SLOT(i) - if (slot[i].format >= 0) { - PARA_INFO_LOG("stopping decoder in slot %d\n", i); - kill_stream_writer(i); - } -} - -static void check_sigchld(void) -{ - pid_t pid; - int i; - struct timeval now; - gettimeofday(&now, NULL); - -reap_next_child: - pid = para_reap_child(); - if (pid <= 0) - return; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; - long lifetime; - if (s->format < 0) - continue; - if (pid == s->wpid) { - s->wpid = -1; - lifetime = now.tv_sec - s->wstime.tv_sec; - PARA_INFO_LOG("%s stream writer in slot %d died " - "after %li secs\n", - audio_formats[s->format], i, lifetime); - set_restart_barrier(s->format, &now); - goto reap_next_child; + if (s->wng && !s->wng->eof) { + PARA_INFO_LOG("unregistering writer node group in slot %d\n", + i); + wng_unregister(s->wng); + s->wng->eof = 1; + } + if (s->fc && !s->fc->eof) { + PARA_INFO_LOG("unregistering filter chain in slot %d\n", i); + unregister_task(&s->fc->task); + s->fc->eof = 1; + } + if (s->receiver_node && !s->receiver_node->eof) { + PARA_INFO_LOG("unregistering receiver_node in slot %d\n", i); + unregister_task(&s->receiver_node->task); + s->receiver_node->eof = 1; } } - PARA_CRIT_LOG("para_client died (pid %d)\n", pid); - goto reap_next_child; } static int get_empty_slot(void) @@ -533,11 +234,7 @@ static int get_empty_slot(void) clear_slot(i); return i; } - if (s->write_fd > 0 || s->wpid > 0) - continue; - if (s->receiver_node) - continue; - if (s->fci) + if (s->wng || s->receiver_node || s->fc) continue; clear_slot(i); return i; @@ -545,86 +242,58 @@ static int get_empty_slot(void) return -E_NO_MORE_SLOTS; } -static int decoder_running(int format) -{ - int i, ret = 0; - struct slot_info *s; - - FOR_EACH_SLOT(i) { - s = &slot[i]; - if (s->format == format && s->receiver_node) - ret |= 1; - if (s->format == format && s->wpid > 0) - ret |= 2; - } - return ret; -} - static void close_stat_pipe(void) { int i; - if (stat_pipe < 0) + if (!stat_task->pcd) return; - PARA_NOTICE_LOG("%s", "closing status pipe\n"); - close(stat_pipe); - del_close_on_fork_list(stat_pipe); - stat_pipe = -1; - kill_all_decoders(); + client_close(stat_task->pcd); + stat_task->pcd = NULL; for (i = 0; i < NUM_STAT_ITEMS; i++) { - free(stat_item_values[i]); - stat_item_values[i] = NULL; + free(stat_task->stat_item_values[i]); + stat_task->stat_item_values[i] = NULL; } dump_empty_status(); - length_seconds = 0; - offset_seconds = 0; + stat_task->length_seconds = 0; + stat_task->offset_seconds = 0; audiod_status_dump(); - playing = 0; - stat_item_values[SI_STATUS_BAR] = make_message("%s:no connection to para_server\n", + stat_task->playing = 0; + stat_task->stat_item_values[SI_STATUS_BAR] = make_message( + "%s:no connection to para_server\n", status_item_list[SI_STATUS_BAR]); - stat_client_write(stat_item_values[SI_STATUS_BAR], SI_STATUS_BAR); + stat_client_write(stat_task->stat_item_values[SI_STATUS_BAR], SI_STATUS_BAR); } -static void __noreturn clean_exit(int status, const char *msg) +void __noreturn clean_exit(int status, const char *msg) { PARA_EMERG_LOG("%s\n", msg); - kill_all_decoders(); if (socket_name) unlink(socket_name); - if (stat_pipe >= 0) - close_stat_pipe(); + close_stat_pipe(); exit(status); } -__malloc static char *glob_cmd(char *cmd) +/** + * get the number of filters + * + * \param audio_format_num the number identifying the audio format + * + * \return the number of filters for the given audio format + * + * \sa struct filter; + */ +int num_filters(int audio_format_num) { - char *ret, *replacement; - struct timeval tmp, delay, rss; /* real stream start */ - - delay.tv_sec = conf.stream_delay_arg / 1000; - delay.tv_usec = (conf.stream_delay_arg % 1000) * 1000; -// PARA_INFO_LOG("delay: %lu:%lu\n", delay.tv_sec, delay.tv_usec); - if (sa_time_diff_sign < 0) - tv_add(&server_stream_start, &sa_time_diff, &rss); - else - tv_diff(&server_stream_start, &sa_time_diff, &rss); - tv_add(&rss, &delay, &tmp); - replacement = make_message("%lu:%lu", - (long unsigned)tmp.tv_sec, - (long unsigned)tmp.tv_usec); - ret = s_a_r(cmd, "STREAM_START", replacement); - free(replacement); - if (!ret) - goto out; - PARA_INFO_LOG("cmd: %s, repl: %s\n", cmd, ret); -out: - return ret; + return afi[audio_format_num].num_filters; } -/** get the number of filters for the given audio format */ -int num_filters(int audio_format_num) +static void filter_event_handler(struct task *t) { - return afi[audio_format_num].num_filters; + PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-t->ret)); + struct filter_chain *fc = t->private_data; + fc->eof = 1; + unregister_task(t); } static void open_filters(int slot_num) @@ -634,171 +303,210 @@ static void open_filters(int slot_num) int nf = a->num_filters; int i; - s->fci = para_calloc(sizeof(struct filter_chain_info)); - INIT_LIST_HEAD(&s->fci->filters); + s->fc = NULL; if (!nf) return; - s->fci->inbuf = s->receiver_node->buf; - s->fci->in_loaded = &s->receiver_node->loaded; - s->fci->outbuf = s->receiver_node->buf; - s->fci->out_loaded = &s->receiver_node->loaded; - s->fci->eof = &s->receiver_node->eof; + PARA_INFO_LOG("opening %s filters\n", audio_formats[s->format]); + s->fc = para_calloc(sizeof(struct filter_chain)); + INIT_LIST_HEAD(&s->fc->filters); + s->fc->inbuf = s->receiver_node->buf; + s->fc->in_loaded = &s->receiver_node->loaded; + s->fc->input_eof = &s->receiver_node->eof; + s->fc->task.pre_select = filter_pre_select; + s->fc->task.event_handler = filter_event_handler; + s->fc->task.private_data = s->fc; + s->fc->eof = 0; + + s->receiver_node->output_eof = &s->fc->eof; + sprintf(s->fc->task.status, "filter chain"); for (i = 0; i < nf; i++) { struct filter_node *fn = para_calloc(sizeof(struct filter_node)); fn->conf = a->filter_conf[i]; - fn->fci = s->fci; + fn->fc = s->fc; fn->filter = a->filters[i]; INIT_LIST_HEAD(&fn->callbacks); - list_add_tail(&fn->node, &s->fci->filters); + list_add_tail(&fn->node, &s->fc->filters); fn->filter->open(fn); PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n", audio_formats[s->format], i + 1, nf, fn->filter->name, slot_num); - s->fci->outbuf = fn->buf; - s->fci->out_loaded = &fn->loaded; + s->fc->outbuf = fn->buf; + s->fc->out_loaded = &fn->loaded; } - PARA_DEBUG_LOG("output buffer for filter chain %p: %p\n", s->fci, - s->fci->outbuf); + register_task(&s->fc->task); } -static struct filter_node *find_filter_node(int slot_num, int format, int filternum) +static void wng_event_handler(struct task *t) { - struct filter_node *fn; - int i, j; + struct writer_node_group *wng = t->private_data; - FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - if (s->format < 0 || !s->fci) - continue; - if (slot_num >= 0 && slot_num != i) - continue; - if (format >= 0 && s->format != format) - continue; - if (num_filters(i) < filternum) - continue; - /* success */ - j = 1; - list_for_each_entry(fn, &s->fci->filters, node) - if (filternum <= 0 || j++ == filternum) - break; - return fn; - } - return NULL; + PARA_INFO_LOG("%s\n", PARA_STRERROR(-t->ret)); + wng->eof = 1; + wng_unregister(wng); } -static void start_stream_writer(int slot_num) +static void open_writers(int slot_num) { - int ret, fds[3] = {1, -1, -1}; + int ret, i; struct slot_info *s = &slot[slot_num]; struct audio_format_info *a = &afi[s->format]; - char *glob = NULL; - - if (a->write_cmd) - glob = glob_cmd(a->write_cmd); - if (!glob) - glob = para_strdup("para_play"); - PARA_INFO_LOG("starting stream writer: %s\n", glob); - open_filters(slot_num); - ret = para_exec_cmdline_pid(&s->wpid, glob, fds); - free(glob); + + PARA_INFO_LOG("opening %s writers\n", audio_formats[s->format]); + if (!a->num_writers) + s->wng = setup_default_wng(); + else + s->wng = wng_new(a->num_writers); + if (s->fc) { + s->wng->buf = s->fc->outbuf; + s->wng->loaded = s->fc->out_loaded; + s->wng->input_eof = &s->fc->eof; + s->wng->channels = &s->fc->channels; + s->wng->samplerate = &s->fc->samplerate; + s->fc->output_eof = &s->wng->eof; + PARA_INFO_LOG("samplerate: %d\n", *s->wng->samplerate); + } else { + s->wng->buf = s->receiver_node->buf; + s->wng->loaded = &s->receiver_node->loaded; + s->wng->input_eof = &s->receiver_node->eof; + } + s->wng->task.event_handler = wng_event_handler; + for (i = 0; i < a->num_writers; i++) { + s->wng->writer_nodes[i].conf = a->writer_conf[i]; + s->wng->writer_nodes[i].writer = a->writers[i]; + } + ret = wng_open(s->wng); if (ret < 0) { - PARA_ERROR_LOG("exec failed (%d)\n", ret); + PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); return; } - s->write_fd = fds[0]; - add_close_on_fork_list(s->write_fd); - /* we write to this fd in do_select, so we need non-blocking */ - mark_fd_nonblock(s->write_fd); - gettimeofday(&s->wstime, NULL); - current_decoder = slot_num; - activate_inactive_grab_clients(slot_num, s->format, &s->fci->filters); + s->wstime = *now; + activate_inactive_grab_clients(slot_num, s->format, &s->fc->filters); } -static void open_receiver(int format) +static void rn_event_handler(struct task *t) +{ + struct receiver_node *rn = t->private_data; + const struct timeval restart_delay = {0, 10 * 1000}; + int i; + + PARA_NOTICE_LOG("%s\n", PARA_STRERROR(-t->ret)); + unregister_task(t); + rn->eof = 1; + /* set restart barrier */ + FOR_EACH_SLOT(i) { + if (slot[i].receiver_node != rn) + continue; + tv_add(now, &restart_delay, &afi[slot[i].format].restart_barrier); + } +} + +static int open_receiver(int format) { struct audio_format_info *a = &afi[format]; struct slot_info *s; int ret, slot_num; + struct receiver_node *rn; + const struct timeval restart_delay = {1, 0}; - slot_num = get_empty_slot(); - if (slot_num < 0) - clean_exit(EXIT_FAILURE, PARA_STRERROR(-slot_num)); + ret = get_empty_slot(); + if (ret < 0) + goto err; + slot_num = ret; s = &slot[slot_num]; s->format = format; - gettimeofday(&s->rtime, NULL); - s->wtime = s->rtime; s->receiver_node = para_calloc(sizeof(struct receiver_node)); - s->receiver_node->conf = a->receiver_conf; + rn = s->receiver_node; + rn->receiver = a->receiver; + rn->conf = a->receiver_conf; ret = a->receiver->open(s->receiver_node); if (ret < 0) { - PARA_ERROR_LOG("failed to open receiver (%s)\n", - PARA_STRERROR(-ret)); free(s->receiver_node); s->receiver_node = NULL; - return; + goto err; } PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n", audio_formats[s->format], a->receiver->name, slot_num); + rn->task.private_data = s->receiver_node; + rn->task.pre_select = a->receiver->pre_select; + rn->task.post_select = a->receiver->post_select; + rn->task.event_handler = rn_event_handler; + sprintf(rn->task.status, "%s receiver node", rn->receiver->name); + register_task(&rn->task); + return 1; +err: + PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); + tv_add(now, &restart_delay, &afi[format].restart_barrier); + return ret; } -static int is_frozen(int format) +static int receiver_running(int format) { - struct timeval now; - struct audio_format_info *a = &afi[format]; + int i; - gettimeofday(&now, NULL); - return (tv_diff(&now, &a->restart_barrier, NULL) > 0)? 0 : 1; + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + if (s->format == format && s->receiver_node + && !s->receiver_node->eof) + return 1; + } + return 0; } -static void start_current_receiver(void) +static int open_current_receiver(struct sched *s) { int i; + struct timeval diff; + char *audio_format = stat_task->stat_item_values[SI_FORMAT]; - if (!af_status) - return; - i = get_audio_format_num(af_status); + if (!audio_format || !stat_task->pcd) + return 0; + i = get_audio_format_num(audio_format + strlen( + status_item_list[SI_FORMAT]) + 1); if (i < 0) - return; - if ((decoder_running(i) & 1) || is_frozen(i)) - return; - open_receiver(i); + return 0; + if (receiver_running(i)) + return 0; + if (tv_diff(now, &afi[i].restart_barrier, &diff) < 0) { + s->timeout = diff; + return 0; + } + return open_receiver(i) < 0? 0 : 1; } static void compute_time_diff(const struct timeval *status_time) { - struct timeval now, tmp, diff; + struct timeval tmp, diff; static int count; - int sign; + int sign, sa_time_diff_sign = stat_task->sa_time_diff_sign; const struct timeval max_deviation = {0, 500 * 1000}; const int time_smooth = 5; - gettimeofday(&now, NULL); - sign = tv_diff(status_time, &now, &diff); + sign = tv_diff(status_time, now, &diff); // PARA_NOTICE_LOG("%s: sign = %i, sa_time_diff_sign = %i\n", __func__, // sign, sa_time_diff_sign); if (!count) { sa_time_diff_sign = sign; - sa_time_diff = diff; + stat_task->sa_time_diff = diff; count++; return; } if (count > 5) { - int s = tv_diff(&diff, &sa_time_diff, &tmp); + int s = tv_diff(&diff, &stat_task->sa_time_diff, &tmp); if (tv_diff(&max_deviation, &tmp, NULL) < 0) PARA_WARNING_LOG("time diff jump: %lims\n", s * tv2ms(&tmp)); } count++; sa_time_diff_sign = tv_convex_combination( - sa_time_diff_sign * time_smooth, &sa_time_diff, + sa_time_diff_sign * time_smooth, &stat_task->sa_time_diff, count > 10? sign : sign * time_smooth, &diff, &tmp); - sa_time_diff = tmp; - PARA_INFO_LOG("time diff (cur/avg): %s%lums/%s%lums\n", + stat_task->sa_time_diff = tmp; + PARA_DEBUG_LOG("time diff (cur/avg): %s%lums/%s%lums\n", sign > 0? "+" : "-", tv2ms(&diff), sa_time_diff_sign ? "+" : "-", - tv2ms(&sa_time_diff) + tv2ms(&stat_task->sa_time_diff) ); } @@ -809,7 +517,7 @@ static void check_stat_line(char *line) long unsigned sec, usec; char *tmp; - PARA_INFO_LOG("line: %s\n", line); +// PARA_INFO_LOG("line: %s\n", line); if (!line) return; itemnum = stat_line_valid(line); @@ -820,27 +528,33 @@ static void check_stat_line(char *line) tmp = make_message("%s\n", line); stat_client_write(tmp, itemnum); free(tmp); - free(stat_item_values[itemnum]); - stat_item_values[itemnum] = para_strdup(line); + free(stat_task->stat_item_values[itemnum]); + stat_task->stat_item_values[itemnum] = para_strdup(line); ilen = strlen(status_item_list[itemnum]); switch (itemnum) { case SI_STATUS: - playing = strstr(line, "playing")? 1 : 0; - break; - case SI_FORMAT: - free(af_status); - af_status = para_strdup(line + ilen + 1); + stat_task->playing = strstr(line, "playing")? 1 : 0; break; case SI_OFFSET: - offset_seconds = atoi(line + ilen + 1); + stat_task->offset_seconds = atoi(line + ilen + 1); break; case SI_LENGTH: - length_seconds = atoi(line + ilen + 1); + stat_task->length_seconds = atoi(line + ilen + 1); break; case SI_STREAM_START: if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) { - server_stream_start.tv_sec = sec; - server_stream_start.tv_usec = usec; + struct timeval a_start, delay; + delay.tv_sec = conf.stream_delay_arg / 1000; + delay.tv_usec = (conf.stream_delay_arg % 1000) * 1000; + stat_task->server_stream_start.tv_sec = sec; + stat_task->server_stream_start.tv_usec = usec; + if (stat_task->sa_time_diff_sign < 0) + tv_add(&stat_task->server_stream_start, + &stat_task->sa_time_diff, &a_start); + else + tv_diff(&stat_task->server_stream_start, + &stat_task->sa_time_diff, &a_start); + tv_add(&a_start, &delay, &initial_delay_barrier); } break; case SI_CURRENT_TIME: @@ -855,8 +569,6 @@ static void check_stat_line(char *line) static void handle_signal(int sig) { switch (sig) { - case SIGCHLD: - return check_sigchld(); case SIGINT: case SIGTERM: case SIGHUP: @@ -866,178 +578,95 @@ static void handle_signal(int sig) } } -static void check_timeouts(void) -{ - struct timeval now; - int slot_num, timeout = conf.stream_timeout_arg; - - gettimeofday(&now, NULL); - FOR_EACH_SLOT(slot_num) { - struct slot_info *s = &slot[slot_num]; - if (s->format < 0) - continue; - /* check read time */ - if (s->receiver_node && - now.tv_sec > s->rtime.tv_sec + timeout) { - PARA_INFO_LOG("%s input buffer (slot %d) not ready\n", - audio_formats[s->format], slot_num); - if (s->fci) - s->fci->error = 42; - else - close_receiver(slot_num); - } - /* check write time */ - if (s->wpid > 0 && !s->wkilled && - now.tv_sec > s->wtime.tv_sec + timeout) { - PARA_INFO_LOG("%s output buffer (slot %d) not ready\n", - audio_formats[s->format], slot_num); - if (s->fci) - s->fci->error = 42; - } - } -} - -static size_t get_loaded_bytes(int slot_num) -{ - size_t loaded = 0; - struct slot_info *s = &slot[slot_num]; - struct receiver_node *rn = s->receiver_node; - - if (s->format < 0) - goto out; - - if (afi[s->format].num_filters) { - if (s->fci) - loaded = *s->fci->out_loaded; - } else { - if (rn) - loaded = rn->loaded; - } -out: - return loaded; -} - - -static void close_decoder_if_idle(int slot_num) +static void try_to_close_slot(int slot_num) { struct slot_info *s = &slot[slot_num]; - struct receiver_node *rn = s->receiver_node; if (s->format < 0) return; - if (!s->fci) + if (s->receiver_node && !s->receiver_node->eof) return; - if (!rn->eof && !s->fci->error && s->wpid > 0) + if (s->fc && !s->fc->eof) return; - if (!s->fci->error && s->wpid > 0) { /* eof */ - if (filter_io(s->fci) > 0) - return; - if (get_loaded_bytes(slot_num)) - return; - } - if (s->write_fd > 0) { - PARA_INFO_LOG("slot %d: closing write fd %d\n", slot_num, - s->write_fd); - close(s->write_fd); - del_close_on_fork_list(s->write_fd); - s->write_fd = -1; - } - if (s->wpid > 0) - return; /* wait until writer dies before closing filters */ - PARA_INFO_LOG("closing all filters in slot %d (filter_chain %p)\n", - slot_num, s->fci); - close_filters(s->fci); - free(s->fci); + if (s->wng && !s->wng->eof) + return; + PARA_INFO_LOG("closing slot %d \n", slot_num); + wng_close(s->wng); + close_filters(s->fc); + free(s->fc); close_receiver(slot_num); clear_slot(slot_num); } -static void set_stream_fds(fd_set *wfds, int *max_fileno) +/* + * Check if any receivers/filters/writers need to be started and do so if + * neccessary. Since the pre_select function didn't have a chance yet to put + * file descriptors into the fd sets given by s, make the upcoming select() + * return immediately to avoid a long timeout in case we started something. + */ +static void audiod_pre_select(struct sched *s, __a_unused struct task *t) { int i; + struct timeval min_delay = {0, 1}; - check_timeouts(); + t->ret = 1; + if (audiod_status != AUDIOD_ON || !stat_task->playing) + return kill_all_decoders(); + if (open_current_receiver(s)) + s->timeout = min_delay; FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; + struct slot_info *sl = &slot[i]; struct audio_format_info *a; - struct receiver_node *rn; + struct timeval diff; - close_decoder_if_idle(i); - s->wcheck = 0; - if (s->format < 0) + if (sl->format < 0) + continue; + a = &afi[sl->format]; + if (!sl->receiver_node) + continue; + if (!a->num_filters) { + if (sl->receiver_node->loaded && !sl->wng) { + open_writers(i); + s->timeout = min_delay; + } + continue; + } + if (sl->receiver_node->loaded && !sl->fc) { + open_filters(i); + s->timeout = min_delay; continue; - a = &afi[s->format]; - rn = s->receiver_node; - if (rn && rn->loaded && !s->wpid) { - PARA_INFO_LOG("no writer in slot %d\n", i); - start_stream_writer(i); } - if (s->write_fd <= 0) + if (!sl->fc || !*sl->fc->out_loaded || sl->wng) continue; - if (!get_loaded_bytes(i)) + if (tv_diff(now, &initial_delay_barrier, &diff) > 0) { + open_writers(i); + s->timeout = min_delay; continue; - para_fd_set(s->write_fd, wfds, max_fileno); - s->wcheck = 1; + } + PARA_INFO_LOG("inital delay: %lu ms left\n", tv2ms(&diff)); + if (tv_diff(&s->timeout, &diff, NULL) > 0) { + s->timeout = diff; + } } } -static int write_audio_data(int slot_num) +static void audiod_post_select(__a_unused struct sched *s, + __a_unused struct task *t) { - struct slot_info *s = &slot[slot_num]; - struct audio_format_info *a = &afi[s->format]; - struct receiver_node *rn = s->receiver_node; - int rv; - char **buf; - size_t *len; - - if (a->num_filters) { - buf = &s->fci->outbuf; - len = s->fci->out_loaded; - } else { - buf = &rn->buf; - len = &rn->loaded; - } - PARA_DEBUG_LOG("writing %p (%zd bytes)\n", *buf, *len); - rv = write(s->write_fd, *buf, *len); - PARA_DEBUG_LOG("wrote %d/%zd\n", rv, *len); - if (rv < 0) { - PARA_WARNING_LOG("write error in slot %d (fd %d): %s\n", - slot_num, s->write_fd, strerror(errno)); - *len = 0; - s->fci->error = E_WRITE_AUDIO_DATA; - } else if (rv != *len) { - PARA_DEBUG_LOG("partial %s write (%i/%zd) for slot %d\n", - audio_formats[s->format], rv, *len, slot_num); - *len -= rv; - memmove(*buf, *buf + rv, *len); - } else - *len = 0; - if (rv > 0) - gettimeofday(&s->wtime, NULL); - return rv; + int i; + + t->ret = 1; + FOR_EACH_SLOT(i) + try_to_close_slot(i); } -static void slot_io(fd_set *wfds) +static void init_audiod_task(struct task *t) { - int ret, i; - - FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - struct receiver_node *rn = s->receiver_node; - - if (rn && rn->loaded) - gettimeofday(&s->rtime, NULL); - if (s->format >= 0 && s->write_fd > 0 && s->fci) { - ret = filter_io(s->fci); - if (ret < 0) - s->fci->error = -ret; -// PARA_DEBUG_LOG("slot %d, filter io %d bytes, check write: %d, loaded: %d/%d, eof: %d\n", -// i, ret, s->wcheck, rn->loaded, *s->fci->out_loaded, rn->eof); - } - if (s->write_fd <= 0 || !s->wcheck || !FD_ISSET(s->write_fd, wfds)) - continue; - write_audio_data(i); - } + t->pre_select = audiod_pre_select; + t->post_select = audiod_post_select; + t->event_handler = NULL; + t->private_data = t; + sprintf(t->status, "audiod task"); } static int parse_stream_command(const char *txt, char **cmd) @@ -1072,68 +701,68 @@ static int add_filter(int format, char *cmdline) return filter_num; } -static int setup_default_filters(void) +static int init_writers(void) { - int i, ret = 1; + int i, ret, nw; + char *cmd; + struct audio_format_info *a; + init_supported_writers(); + nw = PARA_MAX(1, conf.writer_given); + PARA_INFO_LOG("maximal number of writers: %d\n", nw); FOR_EACH_AUDIO_FORMAT(i) { - struct audio_format_info *a = &afi[i]; - char *tmp; - int j; - if (a->num_filters) - continue; - /* add "dec" to audio format name */ - tmp = make_message("%sdec", audio_formats[i]); - for (j = 0; filters[j].name; j++) - if (!strcmp(tmp, filters[j].name)) - break; - free(tmp); - ret = -E_UNSUPPORTED_FILTER; - if (!filters[j].name) - goto out; - tmp = para_strdup(filters[j].name); - ret = add_filter(i, tmp); - free(tmp); + a = &afi[i]; + a->writer_conf = para_malloc(nw * sizeof(void *)); + a->writers = para_malloc(nw * sizeof(struct writer *)); + a->num_writers = 0; + } + for (i = 0; i < conf.writer_given; i++) { + void *wconf; + int writer_num; + ret = parse_stream_command(conf.writer_arg[i], &cmd); if (ret < 0) goto out; - PARA_INFO_LOG("%s -> default filter: %s\n", audio_formats[i], filters[j].name); - ret = add_filter(i, para_strdup("wav")); - if (ret < 0) + a = &afi[ret]; + nw = a->num_writers; + wconf = check_writer_arg(cmd, &writer_num); + if (!wconf) { + ret = writer_num; goto out; - PARA_INFO_LOG("%s -> default filter: wav\n", audio_formats[i]); + } + a->writers[nw] = &writers[writer_num]; + a->writer_conf[nw] = wconf; + PARA_INFO_LOG("%s writer #%d: %s\n", audio_formats[ret], + nw, writer_names[writer_num]); + a->num_writers++; } + ret = 1; out: return ret; } -static int init_stream_io(void) +static int init_receivers(void) { - int i, ret, receiver_num, nf; - char *cmd; + int i, ret, receiver_num; + char *cmd = NULL; + struct audio_format_info *a; - for (i = 0; i < conf.stream_write_cmd_given; i++) { - ret = parse_stream_command(conf.stream_write_cmd_arg[i], &cmd); - if (ret < 0) - goto out; - afi[ret].write_cmd = para_strdup(cmd); - PARA_INFO_LOG("%s write command: %s\n", audio_formats[ret], afi[ret].write_cmd); - } for (i = 0; receivers[i].name; i++) { PARA_INFO_LOG("initializing %s receiver\n", receivers[i].name); receivers[i].init(&receivers[i]); } - for (i = 0; i < conf.receiver_given; i++) { + for (i = conf.receiver_given - 1; i >= 0; i--) { char *arg = conf.receiver_arg[i]; - char *recv = strchr(arg, ':'); + char *recv_arg = strchr(arg, ':'); + PARA_INFO_LOG("arg: %s\n", arg); ret = -E_MISSING_COLON; - if (!recv) + if (!recv_arg) goto out; - *recv = '\0'; - recv++; + *recv_arg = '\0'; + recv_arg++; ret = get_audio_format_num(arg); if (ret < 0) goto out; - afi[ret].receiver_conf = check_receiver_arg(recv, &receiver_num); + afi[ret].receiver_conf = check_receiver_arg(recv_arg, &receiver_num); if (!afi[ret].receiver_conf) { ret = -E_RECV_SYNTAX; goto out; @@ -1146,7 +775,7 @@ static int init_stream_io(void) */ cmd = para_strdup(receivers[0].name); FOR_EACH_AUDIO_FORMAT(i) { - struct audio_format_info *a = &afi[i]; + a = &afi[i]; if (a->receiver_conf) continue; a->receiver_conf = check_receiver_arg(cmd, &receiver_num); @@ -1154,17 +783,57 @@ static int init_stream_io(void) return -E_RECV_SYNTAX; a->receiver = &receivers[receiver_num]; } + ret = 1; +out: free(cmd); - /* filters */ + return ret; +} + +static int init_default_filters(void) +{ + int i, ret = 1; + + FOR_EACH_AUDIO_FORMAT(i) { + struct audio_format_info *a = &afi[i]; + char *tmp; + int j; + + if (a->num_filters) + continue; /* no default -- nothing to to */ + /* add "dec" to audio format name */ + tmp = make_message("%sdec", audio_formats[i]); + for (j = 0; filters[j].name; j++) + if (!strcmp(tmp, filters[j].name)) + break; + free(tmp); + ret = -E_UNSUPPORTED_FILTER; + if (!filters[j].name) + goto out; + tmp = para_strdup(filters[j].name); + ret = add_filter(i, tmp); + free(tmp); + if (ret < 0) + goto out; + PARA_INFO_LOG("%s -> default filter: %s\n", audio_formats[i], + filters[j].name); + } +out: + return ret; +} + +static int init_filters(void) +{ + int i, ret, nf; + filter_init(filters); - nf = PARA_MAX(2, conf.filter_given) + 1; - PARA_INFO_LOG("allocating space for %d filters\n", nf); + nf = PARA_MAX(1, conf.filter_given); + PARA_INFO_LOG("maximal number of filters: %d\n", nf); FOR_EACH_AUDIO_FORMAT(i) { - afi[i].filter_conf = para_malloc(nf * sizeof(char *)); + afi[i].filter_conf = para_malloc(nf * sizeof(void *)); afi[i].filters = para_malloc(nf * sizeof(struct filter *)); } if (!conf.no_default_filters_given) - return setup_default_filters(); + return init_default_filters(); for (i = 0; i < conf.filter_given; i++) { char *arg = conf.filter_arg[i]; char *filter_name = strchr(arg, ':'); @@ -1180,446 +849,199 @@ static int init_stream_io(void) if (ret < 0) goto out; } - ret = 1; + ret = init_default_filters(); /* use default values for the rest */ out: return ret; } -static int dump_commands(int fd) +static int init_stream_io(void) { - char *buf = para_strdup(""), *tmp = NULL; - int i; - ssize_t ret; + int ret; - FOR_EACH_COMMAND(i) { - tmp = make_message("%s%s\t%s\n", buf, cmds[i].name, - cmds[i].description); - free(buf); - buf = tmp; - } - ret = client_write(fd, buf); - free(buf); - return ret; + ret = init_writers(); + if (ret < 0) + return ret; + ret = init_receivers(); + if (ret < 0) + return ret; + ret = init_filters(); + if (ret < 0) + return ret; + return 1; } -/* - * command handlers don't close their fd on errors (ret < 0) so that - * its caller can send an error message. Otherwise (ret >= 0) it's up - * to each individual command to close the fd if necessary. - */ - -static int com_help(int fd, int argc, char **argv) +static int audiod_get_socket(void) { - int i, ret; - char *buf; - const char *dflt = "No such command. Available commands:\n"; - - if (argc < 2) { - ret = dump_commands(fd); - goto out; - } - FOR_EACH_COMMAND(i) { - if (strcmp(cmds[i].name, argv[1])) - continue; - buf = make_message( - "NAME\n\t%s -- %s\n" - "SYNOPSIS\n\tpara_audioc %s\n" - "DESCRIPTION\n%s\n", - argv[1], - cmds[i].description, - cmds[i].synopsis, - cmds[i].help - ); - ret = client_write(fd, buf); - free(buf); - goto out; - } - ret = client_write(fd, dflt); - if (ret > 0) - ret = dump_commands(fd); -out: - if (ret >= 0) - close(fd); - return ret; -} + struct sockaddr_un unix_addr; + int fd; -static int com_stat(int fd, __a_unused int argc, __a_unused char **argv) -{ - int i, ret; - char *buf = NULL; - long unsigned mask = ~0LU; - - if (argc > 1) { - mask = 0; - for (i = 1; i < argc; i++) { - ret = stat_item_valid(argv[i]); - if (ret < 0) - return ret; - mask |= (1 << ret); - } - } - PARA_INFO_LOG("mask: 0x%lx\n", mask); - if (mask & (1 << SI_PLAY_TIME)) { - struct timeval *t = wstime(); - char *ts = get_time_string(t); - if (ts) { - ret = client_write(fd, ts); - if (ret < 0) - goto out; - free(ts); - } - } - if (mask & (1 << SI_AUDIOD_UPTIME)) { - char *tmp, *us = uptime_str(); - tmp = make_message("%s:%s\n", - status_item_list[SI_AUDIOD_UPTIME], us); - free(us); - ret = client_write(fd, tmp); - if (ret < 0) - goto out; - free(tmp); - } - if (mask & (1 << SI_AUDIOD_STATUS)) { - char *s = audiod_status_string(); - ret = client_write(fd, s); - if (ret < 0) - goto out; - free(s); + if (conf.socket_given) + socket_name = para_strdup(conf.socket_arg); + else { + char *hn = para_hostname(); + socket_name = make_message("/var/paraslash/audiod_socket.%s", + hn); + free(hn); } - if (mask & (1 << SI_DECODER_FLAGS)) { - char *df =decoder_flags(); - ret = client_write(fd, df); - if (ret < 0) - goto out; - free(df); + PARA_NOTICE_LOG("local socket: %s\n", socket_name); + if (conf.force_given) + unlink(socket_name); + fd = create_pf_socket(socket_name, &unix_addr, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); + if (fd < 0) { + PARA_EMERG_LOG("%s", "can not connect to socket\n"); + exit(EXIT_FAILURE); /* do not unlink socket */ } - - for (i = 0; i < NUM_STAT_ITEMS; i++) { - char *tmp, *v; - if (!((1 << i) & mask)) - continue; - v = stat_item_values[i]; - tmp = make_message("%s%s%s", buf? buf: "", - v? v : "", v? "\n" : ""); - free(buf); - buf = tmp; + if (listen(fd , 5) < 0) { + PARA_EMERG_LOG("%s", "can not listen on socket\n"); + exit(EXIT_FAILURE); /* do not unlink socket */ } - ret = client_write(fd, buf); -out: - if (ret > 0) - ret = stat_client_add(fd, mask); - free(buf); - return ret; + mark_fd_nonblock(fd); + return fd; } -#if 0 -static char *list_filters(void) +static void signal_event_handler(struct task *t) { - int i, j; - char *tmp, *msg = make_message("format\tnum\tcmd\n"); - - FOR_EACH_AUDIO_FORMAT(i) { - for (j = 0; j < afi[i].num_filters; j++) { - tmp = make_message("%s\t%i\t%s\n", - afi[i].name, j, afi[i].filter_cmds[j]); - msg = para_strcat(msg, tmp); - free(tmp); - } - tmp = make_message("%s\t%i\t%s\n", afi[i].name, - j, afi[i].write_cmd); - msg = para_strcat(msg, tmp); - free(tmp); - } - return msg; -} -#endif + struct signal_task *st = t->private_data; -static int com_grab(int fd, int argc, char **argv) -{ - struct grab_client *gc; - struct filter_node *fn; - int i, err; - char *msg; - - PARA_INFO_LOG("argc: %d, argv[0]: %s, optind: %d\n", argc, argv[0], optind); - gc = grab_client_new(fd, argc, argv, &err); - PARA_INFO_LOG("argc: %d, argv[0]: %s, optind: %d\n", argc, argv[0], optind); - if (!gc) - goto err_out; - fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg); - if (fn) - activate_grab_client(gc, fn); - return 1; -err_out: - if (err != -E_GC_HELP_GIVEN && err != -E_GC_VERSION_GIVEN) - return err; - if (err == -E_GC_HELP_GIVEN) { - msg = make_message("%s\n\n", grab_client_args_info_usage); - for (i = 0; grab_client_args_info_help[i]; i++) { - char *tmp = make_message("%s%s\n", msg, - grab_client_args_info_help[i]); - free(msg); - msg = tmp; - } - } else - msg = make_message("%s %s\n", - GRAB_CLIENT_CMDLINE_PARSER_PACKAGE, - GRAB_CLIENT_CMDLINE_PARSER_VERSION); - err = client_write(fd, msg); - free(msg); - if (err < 0) - return err; - close(fd); - return 1; + if (t->ret != -E_SIGNAL_CAUGHT) + PARA_ERROR_LOG("%s (ignored)\n", PARA_STRERROR(-t->ret)); + else + handle_signal(st->signum); } -static int __noreturn com_term(int fd, __a_unused int argc, __a_unused char **argv) +static void signal_pre_select(struct sched *s, struct task *t) { - close(fd); - clean_exit(EXIT_SUCCESS, "terminating on user request"); + struct signal_task *st = t->private_data; + t->ret = 1; + para_fd_set(st->fd, &s->rfds, &s->max_fileno); } -static int com_on(int fd, __a_unused int argc, __a_unused char **argv) +static void signal_post_select(struct sched *s, struct task *t) { - audiod_status = AUDIOD_ON; - close(fd); - return 1; + struct signal_task *st = t->private_data; + t->ret = 1; + if (!FD_ISSET(st->fd, &s->rfds)) + return; + t->ret = -E_SIGNAL_CAUGHT; + st->signum = para_next_signal(); } -static int com_off(int fd, __a_unused int argc, __a_unused char **argv) +static void signal_setup_default(struct signal_task *st) { - audiod_status = AUDIOD_OFF; - close(fd); - return 1; + st->task.pre_select = signal_pre_select; + st->task.post_select = signal_post_select; + st->task.private_data = st; + sprintf(st->task.status, "signal task"); } -static int com_sb(int fd, __a_unused int argc, __a_unused char **argv) +static void command_pre_select(struct sched *s, struct task *t) { - audiod_status = AUDIOD_STANDBY; - close(fd); - return 1; -} + struct command_task *ct = t->private_data; + t->ret = 1; + para_fd_set(ct->fd, &s->rfds, &s->max_fileno); -static int com_cycle(int fd, int argc, char **argv) -{ - switch (audiod_status) { - case AUDIOD_ON: - return com_sb(fd, argc, argv); - break; - case AUDIOD_OFF: - return com_on(fd, argc, argv); - break; - case AUDIOD_STANDBY: - return com_off(fd, argc, argv); - break; - } - close(fd); - return 1; } -static int check_perms(uid_t uid) +static void command_post_select(struct sched *s, struct task *t) { - int i; + int ret; + struct command_task *ct = t->private_data; - if (!conf.user_allow_given) - return 1; - for (i = 0; i < conf.user_allow_given; i++) - if (uid == conf.user_allow_arg[i]) - return 1; - return -E_UCRED_PERM; + t->ret = 1; /* always successful */ + audiod_status_dump(); + if (!FD_ISSET(ct->fd, &s->rfds)) + return; + ret = handle_connect(ct->fd); + if (ret < 0) + PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); } -static int handle_connect(void) +static void init_command_task(struct command_task *ct) { - int i, argc, ret, clifd = -1; - char *buf = para_malloc(MAXLINE), **argv = NULL; - struct sockaddr_un unix_addr; - - ret = para_accept(audiod_socket, &unix_addr, sizeof(struct sockaddr_un)); - if (ret < 0) - goto out; - clifd = ret; - ret = recv_cred_buffer(clifd, buf, MAXLINE - 1); - if (ret < 0) - goto out; - PARA_INFO_LOG("connection from user %i\n", ret); - ret = check_perms(ret); - if (ret < 0) - goto out; - argc = split_args(buf, &argv, "\n"); - PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc); - for (i = 0; cmds[i].name; i++) { - if (strcmp(cmds[i].name, argv[0])) - continue; - ret = cmds[i].handler(clifd, argc, argv); - goto out; - } - ret = -E_INVALID_AUDIOD_CMD; /* cmd not found */ -out: - free(buf); - free(argv); - if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) { - char *tmp = make_message("%s\n", PARA_STRERROR(-ret)); - client_write(clifd, tmp); - free(tmp); - close(clifd); - } - return ret; + ct->task.pre_select = command_pre_select; + ct->task.post_select = command_post_select; + ct->task.event_handler = NULL; + ct->task.private_data = ct; + ct->fd = audiod_get_socket(); /* doesn't return on errors */ + sprintf(ct->task.status, "command task"); } -static void audiod_get_socket(void) +static void client_task_event_handler(__a_unused struct task *t) { - struct sockaddr_un unix_addr; - - if (conf.socket_given) - socket_name = para_strdup(conf.socket_arg); - else { - char *hn = para_hostname(); - socket_name = make_message("/var/paraslash/audiod_socket.%s", - hn); - free(hn); - } - PARA_NOTICE_LOG("connecting to local socket %s\n", socket_name); - if (conf.force_given) - unlink(socket_name); - audiod_socket = create_pf_socket(socket_name, &unix_addr, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); - if (audiod_socket < 0) { - PARA_EMERG_LOG("%s", "can not connect to socket\n"); - exit(EXIT_FAILURE); /* do not unlink socket */ - } - if (listen(audiod_socket, 5) < 0) { - PARA_EMERG_LOG("%s", "can not listen on socket\n"); - exit(EXIT_FAILURE); /* do not unlink socket */ - } - add_close_on_fork_list(audiod_socket); + struct private_client_data *pcd = t->private_data; + if (t->ret == -E_HANDSHAKE_COMPLETE) + return; + unregister_task(t); + pcd->eof = 1; } -static int open_stat_pipe(void) +static void status_event_handler(__a_unused struct task *t) { - int ret, fd[3] = {-1, 1, 0}; - pid_t pid; - ret = para_exec_cmdline_pid(&pid, BINDIR "/para_client stat", fd); - if (ret >= 0) { - ret = fd[1]; - PARA_NOTICE_LOG("stat pipe opened, fd %d\n", ret); - add_close_on_fork_list(ret); - } else - clean_exit(EXIT_FAILURE, "failed to open status pipe"); - return ret; + struct timeval delay = {1, 0}; + int i; + struct status_task *st = t->private_data; + + PARA_ERROR_LOG("%s\n", PARA_STRERROR(-t->ret)); + close_stat_pipe(); + /* avoid busy loop if server is down */ + tv_add(now, &delay, &st->restart_barrier); + FOR_EACH_AUDIO_FORMAT(i) + afi[i].restart_barrier = st->restart_barrier; } -static void audiod_pre_select(fd_set *rfds, fd_set *wfds, struct timeval *tv, - int *max_fileno) +static void status_pre_select(struct sched *s, struct task *t) { - int i, ret; - - FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - struct audio_format_info *a; - struct receiver_node *rn = s->receiver_node; - if (s->format < 0 || !rn) - continue; - a = &afi[s->format]; - ret = a->receiver->pre_select(rn, rfds, wfds, tv); -// PARA_NOTICE_LOG("%s preselect: %d\n", a->receiver->name, ret); - *max_fileno = PARA_MAX(*max_fileno, ret); + struct status_task *st = t->private_data; + int argc = 2; + char *argv[] = {"audiod", "stat", NULL}; + t->ret = 1; + if (st->pcd && (audiod_status == AUDIOD_OFF || st->pcd->eof)) + close_stat_pipe(); + if (!st->pcd && audiod_status != AUDIOD_OFF + && tv_diff(now, &st->restart_barrier, NULL) > 0) { + t->ret = client_parse_config(argc, argv, &st->pcd); + if (t->ret < 0) + return; + t->ret = client_open(st->pcd); + if (t->ret < 0) + return; + st->pcd->task.event_handler = client_task_event_handler; + s->timeout.tv_sec = 0; + s->timeout.tv_usec = 1; } } -static void audiod_post_select(int select_ret, fd_set *rfds, fd_set *wfds) + +static void status_post_select(__a_unused struct sched *s, struct task *t) { - int i, ret; + struct status_task *st = t->private_data; + unsigned bytes_left; - FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - struct audio_format_info *a; - struct receiver_node *rn = s->receiver_node; - if (s->format < 0 || !rn || rn->eof) - continue; - a = &afi[s->format]; - ret = a->receiver->post_select(rn, select_ret, rfds, wfds); - if (ret <= 0) { - if (ret) - PARA_ERROR_LOG("%s post select failed: %s (slot %d)\n", - a->receiver->name, PARA_STRERROR(-ret), i); - else - PARA_INFO_LOG("eof in slot %d\n", i); - rn->eof = 1; - } - if (ret < 0 && s->fci) - s->fci->error = ret; + t->ret = 1; + if (!st->pcd || st->pcd->status != CL_RECEIVING) + return; + bytes_left = for_each_line(st->pcd->buf, st->pcd->loaded, + &check_stat_line); + if (st->pcd->loaded != bytes_left) { + st->last_status_read = *now; + st->pcd->loaded = bytes_left; + } else { + struct timeval diff; + tv_diff(now, &st->last_status_read, &diff); + if (diff.tv_sec > 61) + close_stat_pipe(); } } -/* TODO: move everything before the select call to pre_select() */ -static void __noreturn audiod_mainloop(void) +static void init_status_task(struct status_task *st) { - fd_set rfds, wfds; - int ret, max_fileno, sbo = 0; - char status_buf[STRINGSIZE] = ""; - struct timeval tv; -repeat: - FD_ZERO(&wfds); - FD_ZERO(&rfds); - /* always check signal pipe and the local socket */ - FD_SET(signal_pipe, &rfds); - max_fileno = signal_pipe; - para_fd_set(audiod_socket, &rfds, &max_fileno); - - if (audiod_status != AUDIOD_ON) - kill_all_decoders(); - else if (playing) - start_current_receiver(); - - set_stream_fds(&wfds, &max_fileno); - /* status pipe */ - if (stat_pipe >= 0 && audiod_status == AUDIOD_OFF) - close_stat_pipe(); - if (stat_pipe < 0 && audiod_status != AUDIOD_OFF) { - stat_pipe = open_stat_pipe(); - sbo = 0; - status_buf[0] = '\0'; - } - if (stat_pipe >= 0 && audiod_status != AUDIOD_OFF) - para_fd_set(stat_pipe, &rfds, &max_fileno); - /* local socket */ - tv.tv_sec = 0; - tv.tv_usec = 200 * 1000; - audiod_pre_select(&rfds, &wfds, &tv, &max_fileno); - ret = para_select(max_fileno + 1, &rfds, &wfds, &tv); - if (ret < 0) - goto repeat; - if (audiod_status != AUDIOD_OFF) - audiod_status_dump(); - audiod_post_select(ret, &rfds, &wfds); - /* read status pipe */ - if (stat_pipe >=0 && FD_ISSET(stat_pipe, &rfds)) { - ret = read(stat_pipe, status_buf + sbo, STRINGSIZE - 1 - sbo); - if (ret <= 0) { - close_stat_pipe(); - /* avoid busy loop if server is down */ - while (sleep(1) > 0) - ; /* try again*/ - } else { - status_buf[ret + sbo] = '\0'; - sbo = for_each_line(status_buf, ret + sbo, - &check_stat_line); - } - } - slot_io(&wfds); - if (FD_ISSET(audiod_socket, &rfds)) { - ret = handle_connect(); - if (ret < 0) { - PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); - } - } - /* signals */ - if (FD_ISSET(signal_pipe, &rfds)) { - int sig_nr = para_next_signal(); - if (sig_nr > 0) - handle_signal(sig_nr); - } - goto repeat; + memset(st, 0, sizeof(struct status_task)); + st->task.pre_select = status_pre_select; + st->task.post_select = status_post_select; + st->task.event_handler = status_event_handler; + st->task.private_data = st; + st->sa_time_diff_sign = 1; + sprintf(st->task.status, "status task"); } static void set_initial_status(void) @@ -1639,19 +1061,20 @@ static void set_initial_status(void) PARA_WARNING_LOG("%s", "invalid mode\n"); } -int __noreturn main(int argc, char *argv[]) +int main(int argc, char *argv[]) { char *cf; - int i; + int ret, i; + struct sched s; + struct command_task command_task_struct, *cmd_task = &command_task_struct; + struct task audiod_task_struct, *audiod_task = &audiod_task_struct; - fprintf(stderr, "argc: %d\n", argc); valid_fd_012(); - hostname = para_hostname(); - cmdline_parser(argc, argv, &conf); + audiod_cmdline_parser(argc, argv, &conf); para_drop_privileges(conf.user_arg, conf.group_arg); cf = configfile_exists(); if (cf) { - if (cmdline_parser_configfile(cf, &conf, 0, 0, 0)) { + if (audiod_cmdline_parser_configfile(cf, &conf, 0, 0, 0)) { PARA_EMERG_LOG("%s", "parse error in config file\n"); exit(EXIT_FAILURE); } @@ -1670,8 +1093,24 @@ int __noreturn main(int argc, char *argv[]) clear_slot(i); init_grabbing(); setup_signal_handling(); + signal_setup_default(sig_task); + sig_task->task.event_handler = signal_event_handler; + + init_status_task(stat_task); + init_command_task(cmd_task); + init_audiod_task(audiod_task); + if (conf.daemon_given) daemon_init(); - audiod_get_socket(); /* doesn't return on errors */ - audiod_mainloop(); + + register_task(&sig_task->task); + register_task(&cmd_task->task); + register_task(&stat_task->task); + register_task(audiod_task); + s.default_timeout.tv_sec = 0; + s.default_timeout.tv_usec = 99 * 1000; + ret = sched(&s); + + PARA_EMERG_LOG("%s\n", PARA_STRERROR(-ret)); + return EXIT_FAILURE; }