X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=audiod.c;h=181d6ab12e6370a4e0df6b661ce20132d6036e78;hb=fc8dfbb416ff07cca08fbf4e13efcaa25e17cc54;hp=074c261067c4c7d0b8533dc989b77caff9020ac7;hpb=b55f996a9d756a84b7fb880df6a9b917215ea058;p=paraslash.git diff --git a/audiod.c b/audiod.c index 074c2610..181d6ab1 100644 --- a/audiod.c +++ b/audiod.c @@ -1,16 +1,23 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file audiod.c the paraslash's audio daemon */ +/** \file audiod.c The paraslash's audio daemon. */ + +#include +#include #include #include -#include +#include +#include +#include #include -#include +#include +#include +#include "recv_cmd.lsg.h" #include "para.h" #include "error.h" #include "crypt.h" @@ -18,6 +25,7 @@ #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "recv.h" #include "filter.h" #include "grab_client.h" @@ -31,18 +39,21 @@ #include "write.h" #include "write_common.h" #include "signal.h" +#include "version.h" + +/** Array of error strings. */ +DEFINE_PARA_ERRLIST; -/** define the array of error lists needed by para_audiod */ -INIT_AUDIOD_ERRLISTS; +__printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log; /** define the array containing all supported audio formats */ const char *audio_formats[] = {AUDIOD_AUDIO_FORMAT_ARRAY NULL}; /** Defines how audiod handles one supported audio format. */ struct audio_format_info { - /** pointer to the receiver for this audio format */ - struct receiver *receiver; - /** the receiver configuration */ - void *receiver_conf; + /** the receiver for this audio format */ + int receiver_num; + /** Parsed receiver command line. */ + struct lls_parse_result *receiver_lpr; /** the number of filters that should be activated for this audio format */ unsigned int num_filters; /** Array of filter numbers to be activated. */ @@ -59,6 +70,33 @@ struct audio_format_info { struct timeval restart_barrier; }; +/* Describes one instance of a receiver-filter-writer chain. */ +struct slot_info { + /* Number of the audio format in this slot. */ + int format; + /* The stream_start status item announced by para_server. */ + struct timeval server_stream_start; + /* The offset status item announced by para_server. */ + unsigned offset_seconds; + /* The seconds_total status item announced by para_server. */ + unsigned seconds_total; + /* The receiver info associated with this slot. */ + struct receiver_node *receiver_node; + /* The array of filter nodes. */ + struct filter_node *fns; + /* The array of writers attached to the last filter. */ + struct writer_node *wns; +}; + +#define RECEIVER_CMD(_a) lls_cmd((_a)->receiver_num, recv_cmd_suite) +#define RECEIVER(_a) ((const struct receiver *)lls_user_data(RECEIVER_CMD(_a))) + +/** Maximal number of simultaneous instances. */ +#define MAX_STREAM_SLOTS 5 + +/** Iterate over all slots. */ +#define FOR_EACH_SLOT(_slot) for (_slot = 0; _slot < MAX_STREAM_SLOTS; _slot++) + /** * para_audiod uses \p MAX_STREAM_SLOTS different slots, each of which may * be associated with a receiver/filter/writer triple. This array holds all @@ -77,19 +115,24 @@ enum vss_status_flags { }; /** - * The task for obtaining para_server's status (para_client stat). + * The scheduler instance of para_audiod. * - * \sa struct task, struct sched. + * This is needed also in audiod_command.c (for the tasks command), so it can + * not be made static. */ +struct sched sched = {.max_fileno = 0}; + +/* The task for obtaining para_server's status (para_client stat). */ struct status_task { /** The associated task structure of audiod. */ - struct task task; + struct task *task; /** Client data associated with the stat task. */ struct client_task *ct; /** Do not restart client command until this time. */ struct timeval restart_barrier; /** Last time we received status data from para_server. */ struct timeval last_status_read; + size_t min_iqs; /** The offset value announced by para_server. */ int offset_seconds; /** The length of the current audio file as announced by para_server. */ @@ -108,6 +151,8 @@ struct status_task { struct timeval clock_diff_barrier; /** Number of the audio format as announced by para_server. */ int current_audio_format_num; + /* The status task btrn is the child of the client task. */ + struct btr_node *btrn; }; /** The array of status items sent by para_server. */ @@ -123,46 +168,56 @@ int audiod_status = AUDIOD_ON; * the gengetopt args_info struct that holds information on all command line * arguments */ -struct audiod_args_info conf; +static struct audiod_args_info conf; static char *socket_name; static struct audio_format_info afi[NUM_AUDIO_FORMATS]; -static struct signal_task signal_task_struct, *sig_task = &signal_task_struct; +static struct signal_task *signal_task; static struct status_task status_task_struct; +static uid_t *uid_whitelist; + /** * the task that calls the status command of para_server * * \sa struct status_task */ static struct status_task *stat_task = &status_task_struct; -static struct timeval initial_delay_barrier; -/** - * the task for handling audiod commands +/* + * The task for handling audiod commands. + * + * We need two listening sockets for backward compability: on Linux systems + * fd[0] is an abstract socket (more precisely, a socket bound to an address in + * the abstract namespace), and fd[1] is the usual pathname socket. On other + * systems, fd[0] is negative, and only the pathname socket is used. * - * \sa struct task, struct sched + * For 0.5.x we accept connections on both sockets to make sure that old + * para_audioc versions can still connect. New versions use only the abstract + * socket. Hence after v0.6.0 we can go back to a single socket, either an + * abstract one (Linux) or a pathname socket (all other systems). */ struct command_task { - /** the local listening socket */ - int fd; + /** The local listening sockets. */ + int fd[2]; /** the associated task structure */ - struct task task; + struct task *task; }; /** iterate over all supported audio formats */ #define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++) /** - * get the audio format number - * \param name the name of the audio format + * Get the audio format number. + * + * \param name The name of the audio format. * * \return The audio format number on success, -E_UNSUPPORTED_AUDIO_FORMAT if * \a name is not a supported audio format. */ -int get_audio_format_num(const char *name) +static int get_audio_format_num(const char *name) { int i; @@ -174,53 +229,156 @@ int get_audio_format_num(const char *name) return -E_UNSUPPORTED_AUDIO_FORMAT; } -char *get_time_string(int slot_num) +/** + * Return the flags for the \a decoder_flags status item. + * + * Allocates a string which contains one octal digit per slot. Bit zero (value + * 1) is set if a receiver is active. Bit one (value 2) and bit three (value 4) + * have the analogous meaning for filter and writer, respectively. + * + * \return String that must be freed by the caller. + */ +__malloc char *audiod_get_decoder_flags(void) { - int ret, seconds = 0, length; + int i; + char 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->fns) + flag += 2; + if (s->wns) + flag += 4; + flags[i] = flag; + } + flags[MAX_STREAM_SLOTS] = '\0'; + return para_strdup(flags); +} + +static int get_matching_audio_format_nums(const char *re) +{ + int i, ret; + regex_t preg; + + ret = para_regcomp(&preg, re, REG_EXTENDED | REG_NOSUB); + if (ret < 0) + return ret; + ret = 0; + FOR_EACH_AUDIO_FORMAT(i) + if (regexec(&preg, audio_formats[i], 0, NULL, 0) != REG_NOMATCH) + ret |= (1 << i); + regfree(&preg); + return ret; +} + +static int get_play_time_slot_num(void) +{ + int i, oldest_slot = -1; + struct timeval oldest_wstime = {0, 0}; + + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + struct timeval wstime; + if (!s->wns || !s->wns[0].btrn) + continue; + btr_get_node_start(s->wns[0].btrn, &wstime); + if (oldest_slot >= 0 && tv_diff(&wstime, &oldest_wstime, NULL) > 0) + continue; + oldest_wstime = wstime; + oldest_slot = i; + } + return oldest_slot; +} + +/** + * Compute the play time based on information of the current slot. + * + * This computes a string of the form "0:07 [3:33] (3%/3:40)" using information + * from the status items received from para_server and the start time of the + * (first) writer of the current slot. + * + * It has to take into account that the stream was probably not started at + * the beginning of the file, that the clock between the server and the client + * host may differ and that playback of the stream was delayed, e.g. because + * the prebuffer filter is used in the filter configuration. + * + * If no writer is active, for example because para_audiod runs in standby + * mode, an approximation based only on the status items is computed and the + * returned string is prefixed with "~". + * + * \return A string that must be freed by the caller. + */ +char *get_time_string(void) +{ + int ret, seconds = 0, length = stat_task->length_seconds; struct timeval *tmp, sum, sss, /* server stream start */ + rstime, /* receiver start time */ + wstime, /* writer start time */ wtime, /* now - writer start */ rskip; /* receiver start - sss */ + int slot_num = get_play_time_slot_num(); struct slot_info *s = slot_num < 0? NULL : &slot[slot_num]; + char *msg; if (audiod_status == AUDIOD_OFF) goto empty; - if (!(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)) { - if (stat_task->length_seconds) /* paused */ + if (stat_task->server_stream_start.tv_sec == 0) { + if (stat_task->vss_status & VSS_STATUS_FLAG_PLAYING) + goto out; /* server is about to change file */ + if (length > 0) /* paused */ return NULL; goto empty; /* stopped */ } - if (audiod_status == AUDIOD_ON && !s) - goto empty; - /* valid status items and playing */ - if (s) { /* writer active in this slot */ - length = s->seconds_total; - tmp = &s->server_stream_start; - } else { /* standby mode, rely on status items */ - length = stat_task->length_seconds; - tmp = &stat_task->server_stream_start; + /* + * Valid status items and playing, set length and tmp to the stream + * start. We use the slot info and fall back to the info from current + * status items if no slot info is available. + */ + tmp = &stat_task->server_stream_start; + if (s && s->wns && s->wns[0].btrn) { /* writer active in this slot */ + btr_get_node_start(s->wns[0].btrn, &wstime); + if (wstime.tv_sec != 0) { /* writer wrote something */ + if (s->server_stream_start.tv_sec == 0) { + /* copy status info to slot */ + s->server_stream_start = stat_task->server_stream_start; + s->offset_seconds = stat_task->offset_seconds; + s->seconds_total = stat_task->length_seconds; + } + length = s->seconds_total; + tmp = &s->server_stream_start; + } } if (stat_task->sa_time_diff_sign > 0) tv_diff(tmp, &stat_task->sa_time_diff, &sss); else tv_add(tmp, &stat_task->sa_time_diff, &sss); - if (!s) { + if (!s || !s->wns || !s->wns[0].btrn || wstime.tv_sec == 0) { struct timeval diff; tv_diff(now, &sss, &diff); seconds = diff.tv_sec + stat_task->offset_seconds; goto out; } - tv_diff(now, &s->wstime, &wtime); + tv_diff(now, &wstime, &wtime); + //PARA_CRIT_LOG("offset %d\n", s->offset_seconds); seconds = s->offset_seconds; - ret = tv_diff(&s->rstime, &sss, &rskip); - if (ret > 0) { /* audiod was started in the middle of the stream */ - tv_add(&wtime, &rskip, &sum); - seconds += sum.tv_sec; + if (s->receiver_node->btrn) { + btr_get_node_start(s->receiver_node->btrn, &rstime); + ret = tv_diff(&rstime, &sss, &rskip); + if (ret > 0 && rskip.tv_sec > 2) { + /* audiod was started in the middle of the stream */ + tv_add(&wtime, &rskip, &sum); + seconds += sum.tv_sec; + } else + seconds += wtime.tv_sec; } else seconds += wtime.tv_sec; out: seconds = PARA_MIN(seconds, length); seconds = PARA_MAX(seconds, 0); - return make_message( + msg = make_message( "%s%d:%02d [%d:%02d] (%d%%/%d:%02d)", s? "" : "~", seconds / 60, @@ -231,24 +389,15 @@ out: length / 60, length % 60 ); + //PARA_DEBUG_LOG("slot %d: %s\n", slot_num, msg); + return msg; empty: return para_strdup(NULL); } -static int want_colors(void) -{ - if (conf.color_arg == color_arg_no) - return 0; - if (conf.color_arg == color_arg_yes) - return 1; - if (conf.logfile_given) - return 0; - return isatty(STDERR_FILENO); -} - static void parse_config_or_die(void) { - int ret; + int ret, i; char *config_file; struct audiod_cmdline_parser_params params = { .override = 0, @@ -268,22 +417,43 @@ static void parse_config_or_die(void) ret = file_exists(config_file); if (conf.config_file_given && !ret) { PARA_EMERG_LOG("can not read config file %s\n", config_file); + free(config_file); goto err; } - if (ret) + if (ret) { audiod_cmdline_parser_config_file(config_file, &conf, ¶ms); + daemon_set_loglevel(conf.loglevel_arg); + } free(config_file); - daemon_set_loglevel(conf.loglevel_arg); + if (conf.user_allow_given > 0) { + uid_whitelist = para_malloc(conf.user_allow_given + * sizeof(uid_t)); + for (i = 0; i < conf.user_allow_given; i++) { + int32_t val; + struct passwd *pw; + ret = para_atoi32(conf.user_allow_arg[i], &val); + if (ret >= 0) { + uid_whitelist[i] = val; + continue; + } + errno = 0; /* see getpwnam(3) */ + pw = getpwnam(conf.user_allow_arg[i]); + if (!pw) { + PARA_EMERG_LOG("invalid username: %s\n", + conf.user_allow_arg[i]); + goto err; + } + uid_whitelist[i] = pw->pw_uid; + } + } return; err: - free(config_file); exit(EXIT_FAILURE); } static void setup_signal_handling(void) { - sig_task->fd = para_signal_init(); - PARA_INFO_LOG("signal pipe: fd %d\n", sig_task->fd); + signal_task = signal_init_or_die(); para_install_sighandler(SIGINT); para_install_sighandler(SIGTERM); para_install_sighandler(SIGHUP); @@ -309,31 +479,81 @@ static void close_receiver(int slot_num) a = &afi[s->format]; PARA_NOTICE_LOG("closing %s receiver in slot %d\n", audio_formats[s->format], slot_num); - a->receiver->close(s->receiver_node); + RECEIVER(a)->close(s->receiver_node); + btr_remove_node(&s->receiver_node->btrn); + task_reap(&s->receiver_node->task); free(s->receiver_node); s->receiver_node = NULL; stat_task->current_audio_format_num = -1; + tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000), + &a->restart_barrier); +} + +static void writer_cleanup(struct writer_node *wn) +{ + struct writer *w; + + if (!wn) + return; + w = writers + wn->writer_num; + PARA_INFO_LOG("closing %s\n", writer_names[wn->writer_num]); + w->close(wn); + btr_remove_node(&wn->btrn); + task_reap(&wn->task); +} + +static void close_writers(struct slot_info *s) +{ + struct audio_format_info *a; + int i; + + if (s->format < 0) + return; + assert(s->wns); + a = afi + s->format; + if (a->num_writers == 0) + writer_cleanup(s->wns); + else { + for (i = 0; i < a->num_writers; i++) + writer_cleanup(s->wns + i); + } + free(s->wns); + s->wns = NULL; } -static void kill_all_decoders(int error) +static void close_filters(struct slot_info *s) +{ + int i; + struct audio_format_info *a = afi + s->format; + if (a->num_filters == 0) + return; + for (i = a->num_filters - 1; i >= 0; i--) { + struct filter_node *fn = s->fns + i; + const struct filter *f; + + if (!fn) + continue; + f = filter_get(fn->filter_num); + if (f->close) + f->close(fn); + btr_remove_node(&fn->btrn); + task_reap(&fn->task); + } + free(s->fns); + s->fns = NULL; +} + +static void notify_receivers(int error) { int i; FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - if (s->wng && s->wng->task.error >= 0) { - PARA_INFO_LOG("deactivating wng in slot %d\n", - i); - s->wng->task.error = error; - } - if (s->fc && s->fc->task.error >= 0) { - PARA_INFO_LOG("deactivatimg filter chain in slot %d\n", i); - s->fc->task.error = error; - } - if (s->receiver_node && s->receiver_node->task.error >= 0) { - PARA_INFO_LOG("deactivating receiver_node in slot %d\n", i); - s->receiver_node->task.error = error; - } + struct slot_info *s = slot + i; + if (s->format < 0) + continue; + if (!s->receiver_node) + continue; + task_notify(s->receiver_node->task, error); } } @@ -348,7 +568,7 @@ static int get_empty_slot(void) clear_slot(i); return i; } - if (s->wng || s->receiver_node || s->fc) + if (s->wns || s->receiver_node || s->fns) continue; clear_slot(i); return i; @@ -356,189 +576,180 @@ static int get_empty_slot(void) return -E_NO_MORE_SLOTS; } -/** - * 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) +static void open_filters(struct slot_info *s) { - return afi[audio_format_num].num_filters; -} - -static void open_filters(int slot_num) -{ - struct slot_info *s = &slot[slot_num]; - struct audio_format_info *a = &afi[s->format]; + struct audio_format_info *a = afi + s->format; struct filter_node *fn; int nf = a->num_filters; + struct btr_node *parent; int i; - s->fc = NULL; - if (!nf) + if (nf == 0) return; PARA_INFO_LOG("opening %s filters\n", audio_formats[s->format]); - s->fc = para_calloc(sizeof(struct filter_chain)); - s->fc->filter_nodes = para_malloc(nf * sizeof(struct filter_node)); - s->fc->inbufp = &s->receiver_node->buf; - s->fc->in_loaded = &s->receiver_node->loaded; - s->fc->input_error = &s->receiver_node->task.error; - s->fc->task.pre_select = NULL; - s->fc->task.post_select = filter_post_select; - s->fc->task.error = 0; - s->fc->num_filters = nf; - - s->receiver_node->output_error = &s->fc->task.error; - sprintf(s->fc->task.status, "filter chain"); - FOR_EACH_FILTER_NODE(fn, s->fc, i) { - struct filter *f = filters + a->filter_nums[i]; + assert(s->fns == NULL); + s->fns = para_calloc(nf * sizeof(struct filter_node)); + parent = s->receiver_node->btrn; + for (i = 0; i < nf; i++) { + char buf[20]; + const struct filter *f = filter_get(a->filter_nums[i]); + fn = s->fns + i; fn->filter_num = a->filter_nums[i]; fn->conf = a->filter_conf[i]; - fn->fc = s->fc; - fn->loaded = 0; - INIT_LIST_HEAD(&fn->callbacks); - f->open(fn); + fn->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = f->name, .parent = parent, + .handler = f->execute, .context = fn)); + + if (f->open) + f->open(fn); + sprintf(buf, "%s (slot %d)", f->name, (int)(s - slot)); + fn->task = task_register(&(struct task_info) { + .name = buf, + .pre_select = f->pre_select, + .post_select = f->post_select, + .context = fn, + }, &sched); + parent = fn->btrn; PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n", - audio_formats[s->format], i, nf, f->name, slot_num); - s->fc->outbufp = &fn->buf; - s->fc->out_loaded = &fn->loaded; + audio_formats[s->format], i, nf, f->name, (int)(s - slot)); } - register_task(&s->fc->task); } -static void open_writers(int slot_num) +static void open_writers(struct slot_info *s) { - int ret, i; - struct slot_info *s = &slot[slot_num]; - struct audio_format_info *a = &afi[s->format]; + int i; + struct audio_format_info *a = afi + s->format; + struct writer_node *wn; + struct btr_node *parent = s->fns[a->num_filters - 1].btrn; - 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->bufp = s->fc->outbufp; - s->wng->loaded = s->fc->out_loaded; - s->wng->input_error = &s->fc->task.error; - s->wng->channels = &s->fc->channels; - s->wng->samplerate = &s->fc->samplerate; - s->fc->output_error = &s->wng->task.error; - PARA_INFO_LOG("samplerate: %d\n", *s->wng->samplerate); - } else { - s->wng->bufp = &s->receiver_node->buf; - s->wng->loaded = &s->receiver_node->loaded; - s->wng->input_error = &s->receiver_node->task.error; - } + assert(s->wns == NULL); + s->wns = para_calloc(PARA_MAX(1U, a->num_writers) + * sizeof(struct writer_node)); for (i = 0; i < a->num_writers; i++) { - s->wng->writer_nodes[i].conf = a->writer_conf[i]; - s->wng->writer_nodes[i].writer_num = a->writer_nums[i]; + wn = s->wns + i; + wn->conf = a->writer_conf[i]; + wn->writer_num = a->writer_nums[i]; + register_writer_node(wn, parent, &sched); + PARA_NOTICE_LOG("%s writer started in slot %d\n", + writer_names[a->writer_nums[i]], (int)(s - slot)); } - ret = wng_open(s->wng); - if (ret < 0) - return; - s->wstime = *now; - s->server_stream_start = stat_task->server_stream_start.tv_sec? - stat_task->server_stream_start : *now; - s->offset_seconds = stat_task->offset_seconds; - s->seconds_total = stat_task->length_seconds; - activate_inactive_grab_clients(s->format, s->fc); } +/* returns slot num on success */ static int open_receiver(int format) { struct audio_format_info *a = &afi[format]; struct slot_info *s; int ret, slot_num; + const struct receiver *r = RECEIVER(a); + const char *name = lls_command_name(RECEIVER_CMD(a)); struct receiver_node *rn; - const struct timeval restart_delay = {2, 0}; + tv_add(now, &(struct timeval)EMBRACE(2, 0), &a->restart_barrier); ret = get_empty_slot(); if (ret < 0) - goto err; + return ret; slot_num = ret; - s = &slot[slot_num]; - s->format = format; - s->receiver_node = para_calloc(sizeof(struct receiver_node)); - rn = s->receiver_node; - rn->receiver = a->receiver; - rn->conf = a->receiver_conf; - ret = a->receiver->open(s->receiver_node); + rn = para_calloc(sizeof(*rn)); + rn->receiver = r; + rn->lpr = a->receiver_lpr; + rn->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = name, .context = rn)); + ret = r->open(rn); if (ret < 0) { - free(s->receiver_node); - s->receiver_node = NULL; - goto err; + btr_remove_node(&rn->btrn); + free(rn); + return ret; } + s = &slot[slot_num]; + s->format = format; + s->receiver_node = rn; PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n", - audio_formats[s->format], a->receiver->name, slot_num); - rn->task.pre_select = a->receiver->pre_select; - rn->task.post_select = a->receiver->post_select; - s->rstime = *now; - sprintf(rn->task.status, "%s receiver node", rn->receiver->name); - register_task(&rn->task); - ret = 1; -err: - if (ret < 0) - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - tv_add(now, &restart_delay, &afi[format].restart_barrier); - return ret; + audio_formats[format], name, slot_num); + rn->task = task_register(&(struct task_info) { + .name = name, + .pre_select = r->pre_select, + .post_select = r->post_select, + .context = rn, + }, &sched); + return slot_num; } -/* return: 0: Not running, 1: Running, -1: Running but eof (or error) */ -static int receiver_running(int format) +static bool receiver_running(void) { - int i, ret = 0; + int i; + long unsigned ss1 = stat_task->server_stream_start.tv_sec; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; - if (s->format != format) - continue; + long unsigned ss2 = s->server_stream_start.tv_sec; + if (!s->receiver_node) continue; - if (s->receiver_node->task.error >= 0) - return 1; - ret = -1; + if (task_status(s->receiver_node->task) >= 0) + return true; + if (ss1 == ss2) + return true; } - return ret; + return false; } -static void open_current_receiver(struct sched *s) +/** + * Return the root node of the current buffer tree. + * + * This is only used for stream grabbing. + * + * \return \p NULL if no slot is currently active. If more than one buffer tree + * exists, the node corresponding to the most recently started receiver is + * returned. + */ +struct btr_node *audiod_get_btr_root(void) { - struct timeval diff; - int ret, cafn = stat_task->current_audio_format_num; + int i, newest_slot = -1; + struct timeval newest_rstime = {0, 0}; - if (cafn < 0 || !stat_task->ct) - return; - /* Do nothing if the 'N' flag is set or the 'P' flag is unset */ - if (stat_task->vss_status != VSS_STATUS_FLAG_PLAYING) - return; - ret = receiver_running(cafn); - if (ret > 0) /* already running and not eof */ - return; - if (ret < 0) { /* eof */ - /* - * para_server uses a zero start time during the announcement - * period, i.e. before it sends the first chunk. Wait until - * this period begins to avoid restarting the receiver that - * belongs to the file just completed. - */ - if (stat_task->server_stream_start.tv_sec) - return; - } - if (tv_diff(now, &afi[cafn].restart_barrier, &diff) < 0) { - /* avoid busy loop */ - s->timeout = diff; - return; + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + struct timeval rstime; + if (!s->receiver_node) + continue; + if (task_status(s->receiver_node->task) < 0) + continue; + btr_get_node_start(s->receiver_node->btrn, &rstime); + if (newest_slot >= 0 && tv_diff(&rstime, &newest_rstime, NULL) < 0) + continue; + newest_rstime = rstime; + newest_slot = i; } - /* start a new receiver */ - open_receiver(cafn); + if (newest_slot == -1) + return NULL; + return slot[newest_slot].receiver_node->btrn; +} + +/* whether a new instance of a decoder should be started. */ +static bool must_start_decoder(void) +{ + int cafn = stat_task->current_audio_format_num; + unsigned vs = stat_task->vss_status; + + if (audiod_status != AUDIOD_ON) + return false; + if (cafn < 0) + return false; + if (!stat_task->ct) + return false; + if (vs & VSS_STATUS_FLAG_NEXT) + return false; + if (!(vs & VSS_STATUS_FLAG_PLAYING)) + return false; + if (receiver_running()) + return false; + if (tv_diff(now, &afi[cafn].restart_barrier, NULL) < 0) + return false; + return true; } -static unsigned compute_time_diff(const struct timeval *status_time) +static void compute_time_diff(const struct timeval *status_time) { struct timeval tmp, diff; static unsigned count; @@ -546,8 +757,6 @@ static unsigned compute_time_diff(const struct timeval *status_time) const struct timeval max_deviation = {0, 500 * 1000}; const int time_smooth = 5; - if (!status_time) - return count; 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); @@ -560,7 +769,7 @@ static unsigned compute_time_diff(const struct timeval *status_time) if (count > 5) { 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", + PARA_WARNING_LOG("time diff jump: %lums\n", s * tv2ms(&tmp)); } count++; @@ -570,14 +779,13 @@ static unsigned compute_time_diff(const struct timeval *status_time) &tmp); stat_task->sa_time_diff = tmp; PARA_INFO_LOG("time diff (cur/avg): %s%lums/%s%lums\n", - sign > 0? "+" : "-", + sign < 0? "-" : "+", tv2ms(&diff), - sa_time_diff_sign ? "+" : "-", + sa_time_diff_sign < 0? "-" : "+", tv2ms(&stat_task->sa_time_diff) ); out: stat_task->sa_time_diff_sign = sa_time_diff_sign; - return count; } static int update_item(int itemnum, char *buf) @@ -605,20 +813,8 @@ static int update_item(int itemnum, char *buf) break; case SI_STREAM_START: if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { - 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 (compute_time_diff(NULL) > 2) { - 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: @@ -634,117 +830,138 @@ static int update_item(int itemnum, char *buf) return 1; } -static int parse_stream_command(const char *txt, char **cmd) +static int parse_stream_command(const char *txt, const char **cmd) { - char *p = strchr(txt, ':'); - int i; + int ret, len; + char *re, *p = strchr(txt, ':'); if (!p) return -E_MISSING_COLON; - p++; - FOR_EACH_AUDIO_FORMAT(i) { - if (strncmp(txt, audio_formats[i], strlen(audio_formats[i]))) - continue; - *cmd = p; - return i; - } - return -E_UNSUPPORTED_AUDIO_FORMAT; + *cmd = p + 1; + len = p - txt; + re = malloc(len + 1); + strncpy(re, txt, len); + re[len] = '\0'; + ret = get_matching_audio_format_nums(re); + free(re); + return ret; } -static int add_filter(int format, char *cmdline) +static int add_filter(int format, const char *cmdline) { struct audio_format_info *a = &afi[format]; int filter_num, nf = a->num_filters; + void *cfg; - filter_num = check_filter_arg(cmdline, &a->filter_conf[nf]); + filter_num = check_filter_arg(cmdline, &cfg); if (filter_num < 0) return filter_num; + a->filter_conf = para_realloc(a->filter_conf, + (nf + 1) * sizeof(void *)); + a->filter_nums = para_realloc(a->filter_nums, + (nf + 1) * sizeof(unsigned)); a->filter_nums[nf] = filter_num; + a->filter_conf[nf] = cfg; a->num_filters++; PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf, - filters[filter_num].name); + filter_get(filter_num)->name); return filter_num; } static int parse_writer_args(void) { - int i, ret, nw; - char *cmd; + int i, ret; + const char *cmd; struct audio_format_info *a; - nw = PARA_MAX(1U, conf.writer_given); - PARA_INFO_LOG("maximal number of writers: %d\n", nw); - FOR_EACH_AUDIO_FORMAT(i) { - a = &afi[i]; - a->writer_conf = para_malloc(nw * sizeof(void *)); - a->writer_nums = para_malloc(nw * sizeof(int)); - a->num_writers = 0; - } for (i = 0; i < conf.writer_given; i++) { void *wconf; - int writer_num; + int j, nw, writer_num, af_mask; + ret = parse_stream_command(conf.writer_arg[i], &cmd); if (ret < 0) - goto out; - a = &afi[ret]; - nw = a->num_writers; - wconf = check_writer_arg(cmd, &writer_num); - if (!wconf) { - ret = writer_num; - goto out; + return ret; + af_mask = ret; + FOR_EACH_AUDIO_FORMAT(j) { + a = afi + j; + if ((af_mask & (1 << j)) == 0) /* no match */ + continue; + wconf = check_writer_arg_or_die(cmd, &writer_num); + nw = a->num_writers; + a->writer_nums = para_realloc(a->writer_nums, (nw + 1) * sizeof(int)); + a->writer_conf = para_realloc(a->writer_conf, (nw + 1) * sizeof(void *)); + a->writer_nums[nw] = writer_num; + a->writer_conf[nw] = wconf; + PARA_INFO_LOG("%s writer #%d: %s\n", audio_formats[j], + nw, writer_names[writer_num]); + a->num_writers++; } - a->writer_nums[nw] = 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; + /* Use default writer for audio formats which are not yet set up. */ + FOR_EACH_AUDIO_FORMAT(i) { + void *writer_conf; + int writer_num; + a = afi + i; + if (a->num_writers > 0) + continue; /* already set up */ + writer_conf = check_writer_arg_or_die(NULL, &writer_num); + a->writer_nums = para_malloc(sizeof(int)); + a->writer_nums[0] = writer_num; + a->writer_conf = para_malloc(sizeof(void *)); + a->writer_conf[0] = writer_conf; + a->num_writers = 1; + PARA_INFO_LOG("%s writer: %s (default)\n", audio_formats[i], + writer_names[writer_num]); + } + return 1; } static int parse_receiver_args(void) { - int i, ret, receiver_num; - char *cmd = NULL; + int i, ret; + const char *arg; struct audio_format_info *a; + FOR_EACH_AUDIO_FORMAT(i) + afi[i].receiver_num = -1; for (i = conf.receiver_given - 1; i >= 0; i--) { - char *arg = conf.receiver_arg[i]; - char *recv_arg = strchr(arg, ':'); - ret = -E_MISSING_COLON; - if (!recv_arg) - goto out; - *recv_arg = '\0'; - recv_arg++; - ret = get_audio_format_num(arg); + int j, af_mask; + + ret = parse_stream_command(conf.receiver_arg[i], &arg); if (ret < 0) goto out; - afi[ret].receiver_conf = check_receiver_arg(recv_arg, &receiver_num); - if (!afi[ret].receiver_conf) { - ret = -E_RECV_SYNTAX; - goto out; + af_mask = ret; + FOR_EACH_AUDIO_FORMAT(j) { + a = afi + j; + if ((af_mask & (1 << j)) == 0) /* no match */ + continue; + /* + * If multiple receivers are given for this audio format, the + * last one wins and we have to free the previous receiver + * config here. Since we are iterating backwards, the winning + * receiver arg is in fact the first one given. + */ + lls_free_parse_result(a->receiver_lpr, RECEIVER_CMD(a)); + a->receiver_num = check_receiver_arg(arg, &a->receiver_lpr); } - afi[ret].receiver = &receivers[receiver_num]; } - /* use the first available receiver with no arguments - * for those audio formats for which no receiver - * was specified + /* + * Use the default receiver for those audio formats for which no + * receiver was specified. */ - cmd = para_strdup(receivers[0].name); FOR_EACH_AUDIO_FORMAT(i) { - a = &afi[i]; - if (a->receiver_conf) + a = afi + i; + if (a->receiver_num >= 0) continue; - a->receiver_conf = check_receiver_arg(cmd, &receiver_num); - if (!a->receiver_conf) - return -E_RECV_SYNTAX; - a->receiver = &receivers[receiver_num]; + a->receiver_num = check_receiver_arg(NULL, &a->receiver_lpr); + } + FOR_EACH_AUDIO_FORMAT(i) { + a = afi + i; + PARA_INFO_LOG("receiving %s streams via %s receiver\n", + audio_formats[i], lls_command_name(RECEIVER_CMD(a))); } ret = 1; out: - free(cmd); return ret; } @@ -754,27 +971,39 @@ static int init_default_filters(void) FOR_EACH_AUDIO_FORMAT(i) { struct audio_format_info *a = &afi[i]; + const char *name = lls_command_name(RECEIVER_CMD(a)); char *tmp; int j; if (a->num_filters) continue; /* no default -- nothing to to */ + /* + * udp and dccp streams are fec-encoded, so add fecdec as the + * first filter. + */ + if (strcmp(name, "udp") == 0 || strcmp(name, "dccp") == 0) { + tmp = para_strdup("fecdec"); + add_filter(i, tmp); + free(tmp); + if (ret < 0) + goto out; + } /* 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)) + for (j = 0; filter_get(j); j++) + if (!strcmp(tmp, filter_get(j)->name)) break; free(tmp); ret = -E_UNSUPPORTED_FILTER; - if (!filters[j].name) + if (!filter_get(j)) goto out; - tmp = para_strdup(filters[j].name); + tmp = para_strdup(filter_get(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); + filter_get(j)->name); } out: return ret; @@ -782,30 +1011,26 @@ out: static int parse_filter_args(void) { - int i, ret, nf; + int i, j, ret, af_mask, num_matches; - nf = PARA_MAX(1U, 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(void *)); - afi[i].filter_nums = para_malloc(nf * sizeof(unsigned)); - } - if (!conf.no_default_filters_given) - return init_default_filters(); for (i = 0; i < conf.filter_given; i++) { - char *arg = conf.filter_arg[i]; - char *filter_name = strchr(arg, ':'); - ret = -E_MISSING_COLON; - if (!filter_name) - goto out; - *filter_name = '\0'; - filter_name++; - ret = get_audio_format_num(arg); - if (ret < 0) - goto out; - ret = add_filter(ret, filter_name); + const char *arg; + ret = parse_stream_command(conf.filter_arg[i], &arg); if (ret < 0) goto out; + af_mask = ret; + num_matches = 0; + FOR_EACH_AUDIO_FORMAT(j) { + if ((af_mask & (1 << j)) == 0) /* no match */ + continue; + ret = add_filter(j, arg); + if (ret < 0) + goto out; + num_matches++; + } + if (num_matches == 0) + PARA_WARNING_LOG("ignoring filter spec: %s\n", + conf.filter_arg[i]); } ret = init_default_filters(); /* use default values for the rest */ out: @@ -829,11 +1054,8 @@ static int parse_stream_args(void) } /* does not unlink socket on errors */ -static int audiod_get_socket(void) +static void init_local_sockets(struct command_task *ct) { - struct sockaddr_un unix_addr; - int ret, fd; - if (conf.socket_given) socket_name = para_strdup(conf.socket_arg); else { @@ -845,93 +1067,111 @@ static int audiod_get_socket(void) PARA_NOTICE_LOG("local socket: %s\n", socket_name); if (conf.force_given) unlink(socket_name); - ret = create_local_socket(socket_name, &unix_addr, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); - if (ret < 0) - goto err; - fd = ret; - if (listen(fd , 5) < 0) { - ret = -ERRNO_TO_PARA_ERROR(errno); - goto err; - } - ret = mark_fd_nonblocking(fd); - if (ret < 0) - goto err; - return fd; -err: - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + ct->fd[0] = create_local_socket(socket_name, 0); + ct->fd[1] = create_local_socket(socket_name, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (ct->fd[0] >= 0 || ct->fd[1] >= 0) + return; + PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd[1])); exit(EXIT_FAILURE); } -static void signal_pre_select(struct sched *s, struct task *t) +static int signal_post_select(struct sched *s, void *context) { - struct signal_task *st = container_of(t, struct signal_task, task); - para_fd_set(st->fd, &s->rfds, &s->max_fileno); -} + struct signal_task *st = context; + int ret, signum; -static void signal_post_select(struct sched *s, struct task *t) -{ - struct signal_task *st = container_of(t, struct signal_task, task); - - if (!FD_ISSET(st->fd, &s->rfds)) - return; - - st->signum = para_next_signal(); - switch (st->signum) { + ret = task_get_notification(st->task); + if (ret < 0) + return ret; + signum = para_next_signal(&s->rfds); + switch (signum) { case SIGINT: case SIGTERM: case SIGHUP: - PARA_EMERG_LOG("terminating on signal %d\n", st->signum); - clean_exit(EXIT_FAILURE, "caught deadly signal"); + PARA_NOTICE_LOG("received signal %d\n", signum); + task_notify_all(s, E_AUDIOD_SIGNAL); + return -E_AUDIOD_SIGNAL; } + return 0; } -static void signal_setup_default(struct signal_task *st) +static void command_pre_select(struct sched *s, void *context) { - st->task.pre_select = signal_pre_select; - st->task.post_select = signal_post_select; - sprintf(st->task.status, "signal task"); -} + struct command_task *ct = context; + int i; -static void command_pre_select(struct sched *s, struct task *t) -{ - struct command_task *ct = container_of(t, struct command_task, task); - para_fd_set(ct->fd, &s->rfds, &s->max_fileno); + for (i = 0; i < 2; i++) + if (ct->fd[i] >= 0) + para_fd_set(ct->fd[i], &s->rfds, &s->max_fileno); } -static void command_post_select(struct sched *s, struct task *t) +static int command_post_select(struct sched *s, void *context) { - int ret; - struct command_task *ct = container_of(t, struct command_task, task); + int ret, i; + struct command_task *ct = context; static struct timeval last_status_dump; - struct timeval tmp; + struct timeval tmp, delay; + bool force = false; - tv_add(&last_status_dump, &(struct timeval){0, 500 * 1000}, &tmp); - if (tv_diff(&tmp, now, NULL) < 0) { - audiod_status_dump(); - last_status_dump = *now; + ret = task_get_notification(ct->task); + if (ret < 0) + return ret; + for (i = 0; i < 2; i++) { + if (ct->fd[i] < 0) + continue; + ret = handle_connect(ct->fd[i], &s->rfds); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + if (ret == -E_AUDIOD_TERM) { + task_notify_all(s, -ret); + return ret; + } + } else if (ret > 0) + force = true; } + if (force == true) + goto dump; + + /* if last status dump was less than 500ms ago, do nothing */ + delay.tv_sec = 0; + delay.tv_usec = 500 * 1000; + tv_add(&last_status_dump, &delay, &tmp); + if (tv_diff(now, &tmp, NULL) < 0) + return 0; - if (!FD_ISSET(ct->fd, &s->rfds)) - return; - ret = handle_connect(ct->fd); - if (ret < 0) - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + /* + * If last status dump was more than 5s ago, force update. Otherwise, + * update only those items that have changed. + */ + delay.tv_sec = 5; + delay.tv_usec = 0; + tv_add(&last_status_dump, &delay, &tmp); + if (tv_diff(now, &tmp, NULL) > 0) + force = true; +dump: + audiod_status_dump(force); + last_status_dump = *now; + return 1; } static void init_command_task(struct command_task *ct) { - ct->task.pre_select = command_pre_select; - ct->task.post_select = command_post_select; - ct->task.error = 0; - ct->fd = audiod_get_socket(); /* doesn't return on errors */ - sprintf(ct->task.status, "command task"); + init_local_sockets(ct); /* doesn't return on errors */ + + ct->task = task_register(&(struct task_info) { + .name = "command", + .pre_select = command_pre_select, + .post_select = command_post_select, + .context = ct, + }, &sched); } static void close_stat_pipe(void) { if (!stat_task->ct) return; + task_reap(&stat_task->ct->task); client_close(stat_task->ct); stat_task->ct = NULL; clear_and_dump_items(); @@ -939,28 +1179,7 @@ static void close_stat_pipe(void) stat_task->offset_seconds = 0; stat_task->vss_status = 0; stat_task->current_audio_format_num = -1; - audiod_status_dump(); -} - -/** - * close the connection to para_server and exit - * - * \param status the exit status which is passed to exit(3) - * \param msg the log message - * - * Log \a msg with loglevel \p EMERG, close the connection to para_server if - * open, and call \p exit(status). \a status should be either EXIT_SUCCESS or - * EXIT_FAILURE. - * - * \sa exit(3) - */ -void __noreturn clean_exit(int status, const char *msg) -{ - PARA_EMERG_LOG("%s\n", msg); - if (socket_name) - unlink(socket_name); - close_stat_pipe(); - exit(status); + audiod_status_dump(true); } /* avoid busy loop if server is down */ @@ -970,135 +1189,200 @@ static void set_stat_task_restart_barrier(unsigned seconds) tv_add(now, &delay, &stat_task->restart_barrier); } -static void try_to_close_slot(int slot_num) +static bool must_close_slot(int slot_num) { struct slot_info *s = &slot[slot_num]; + struct audio_format_info *a = afi + s->format; + int i; if (s->format < 0) - return; - if (s->receiver_node && s->receiver_node->task.error != -E_TASK_UNREGISTERED) - return; - if (s->fc && s->fc->task.error != -E_TASK_UNREGISTERED) - return; - if (s->wng && s->wng->task.error != -E_TASK_UNREGISTERED) - return; + return false; + if (s->receiver_node && task_status(s->receiver_node->task) >= 0) + return false; + for (i = 0; i < a->num_filters; i++) + if (s->fns && task_status(s->fns[i].task) >= 0) + return false; + if (a->num_writers > 0) { + for (i = 0; i < a->num_writers; i++) + if (s->wns && task_status(s->wns[i].task) >= 0) + return false; + } else { + if (s->wns && task_status(s->wns[0].task) >= 0) + return false; + } + return true; +} + +static void close_slot(int slot_num) +{ + struct slot_info *s = slot + slot_num; + PARA_INFO_LOG("closing slot %d\n", slot_num); - wng_close(s->wng); - close_filters(s->fc); - free(s->fc); + close_writers(s); + close_filters(s); close_receiver(slot_num); clear_slot(slot_num); } +static void close_unused_slots(void) +{ + int i; + bool dump = false; + + FOR_EACH_SLOT(i) + if (must_close_slot(i)) { + close_slot(i); + dump = true; + } + if (dump) + audiod_status_dump(true); +} + +/* + * Cleanup all resources. + * + * This performs various cleanups, removes the audiod socket and closes the + * connection to para_server. + */ +static void audiod_cleanup(void) +{ + if (socket_name) + unlink(socket_name); + close_stat_pipe(); + close_unused_slots(); + audiod_cmdline_parser_free(&conf); + close_stat_clients(); + free(uid_whitelist); +} + /* * Check if any receivers/filters/writers need to be started and do so if * necessary. */ -static void start_stop_decoders(struct sched *s) +static void start_stop_decoders(void) { - int i; + int ret; + struct slot_info *sl; - FOR_EACH_SLOT(i) - try_to_close_slot(i); + close_unused_slots(); if (audiod_status != AUDIOD_ON || !(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)) - return kill_all_decoders(-E_NOT_PLAYING); - open_current_receiver(s); - FOR_EACH_SLOT(i) { - struct slot_info *sl = &slot[i]; - struct audio_format_info *a; - struct timeval diff; - - if (sl->format < 0) - continue; - a = &afi[sl->format]; - if (!sl->receiver_node) - continue; - if ((!a->num_filters || sl->fc) && sl->wng) - continue; /* everything already started */ - if (!a->num_filters) { - if (sl->receiver_node->loaded && !sl->wng) { - open_writers(i); - } - continue; - } - if (sl->receiver_node->loaded && !sl->fc) { - open_filters(i); - continue; - } - if (sl->wng || !sl->fc || !*sl->fc->out_loaded) - continue; - if (tv_diff(now, &initial_delay_barrier, &diff) > 0) { - open_writers(i); - continue; - } - PARA_INFO_LOG("initial delay: %lu ms left\n", tv2ms(&diff)); - if (tv_diff(&s->timeout, &diff, NULL) > 0) { - s->timeout = diff; - } + return notify_receivers(E_NOT_PLAYING); + if (!must_start_decoder()) + return; + ret = open_receiver(stat_task->current_audio_format_num); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + return; } + sl = slot + ret; + open_filters(sl); + open_writers(sl); + activate_grab_clients(&sched); + btr_log_tree(sl->receiver_node->btrn, LL_NOTICE); + audiod_status_dump(true); } +static void status_pre_select(struct sched *s, void *context) +{ + struct status_task *st = context; + int i, ret, cafn = stat_task->current_audio_format_num; + + if (must_start_decoder()) + goto min_delay; + FOR_EACH_SLOT(i) + if (must_close_slot(i)) + goto min_delay; + ret = btr_node_status(st->btrn, st->min_iqs, BTR_NT_LEAF); + if (ret > 0) + goto min_delay; + if (st->ct && audiod_status == AUDIOD_OFF) + goto min_delay; + if (!st->ct && audiod_status != AUDIOD_OFF) + sched_request_barrier_or_min_delay(&st->restart_barrier, s); + if (cafn >= 0) + sched_request_barrier(&afi[cafn].restart_barrier, s); + /* + * If para_server is playing we'd like to have a smooth time display + * even if we are running in standby mode. So we request a timeout that + * expires at the next full second. + */ + if (stat_task->vss_status & VSS_STATUS_FLAG_PLAYING) + sched_request_timeout_ms(1000 - now->tv_usec / 1000, s); + return; +min_delay: + sched_min_delay(s); +} /* restart the client task if necessary */ -static void status_pre_select(struct sched *s, struct task *t) +static int status_post_select(struct sched *s, void *context) { - struct status_task *st = container_of(t, struct status_task, task); + struct status_task *st = context; + int ret; + ret = task_get_notification(st->task); + if (ret < 0) + return ret; if (audiod_status == AUDIOD_OFF) { if (!st->ct) goto out; - if (st->ct->task.error >= 0) { - st->ct->task.error = -E_AUDIOD_OFF; + if (task_status(st->ct->task) >= 0) { + task_notify(st->ct->task, E_AUDIOD_OFF); goto out; } - if (st->ct->task.error != -E_TASK_UNREGISTERED) - goto out; close_stat_pipe(); st->clock_diff_count = conf.clock_diff_count_arg; goto out; } if (st->ct) { - int ret; - if (st->ct->task.error < 0) { - if (st->ct->task.error != -E_TASK_UNREGISTERED) - goto out; + char *buf; + size_t sz; + + ret = btr_node_status(st->btrn, st->min_iqs, BTR_NT_LEAF); + if (ret < 0) { close_stat_pipe(); goto out; } - if (st->ct->status != CL_RECEIVING) + if (st->ct->status != CL_EXECUTING) goto out; - ret = for_each_stat_item(st->ct->buf, st->ct->loaded, - update_item); - if (ret < 0) { - st->ct->task.error = ret; - goto out; - } - if (st->ct->loaded != ret) { - st->last_status_read = *now; - st->ct->loaded = ret; - } else { + if (ret == 0) { struct timeval diff; tv_diff(now, &st->last_status_read, &diff); if (diff.tv_sec > 61) - st->ct->task.error = -E_STATUS_TIMEOUT; + task_notify(st->ct->task, E_STATUS_TIMEOUT); + goto out; + } + btr_merge(st->btrn, st->min_iqs); + sz = btr_next_buffer(st->btrn, &buf); + ret = for_each_stat_item(buf, sz, update_item); + if (ret < 0) { + task_notify(st->ct->task, -ret); + goto out; } + if (sz != ret) { + btr_consume(st->btrn, sz - ret); + st->last_status_read = *now; + st->min_iqs = 0; + } else /* current status item crosses buffers */ + st->min_iqs = sz + 1; goto out; } + btr_drain(st->btrn); + st->current_audio_format_num = -1; if (tv_diff(now, &st->restart_barrier, NULL) < 0) goto out; if (st->clock_diff_count) { /* get status only one time */ - char *argv[] = {"audiod", "--", "stat", "-p", "1", NULL}; + char *argv[] = {"audiod", "--", "stat", "-p", "-n=1", NULL}; int argc = 5; - PARA_INFO_LOG("clock diff count: %d\n", st->clock_diff_count); + PARA_INFO_LOG("clock diff count: %u\n", st->clock_diff_count); st->clock_diff_count--; - client_open(argc, argv, &st->ct, NULL); + client_open(argc, argv, &st->ct, NULL, NULL, st->btrn, s); set_stat_task_restart_barrier(2); } else { char *argv[] = {"audiod", "--", "stat", "-p", NULL}; int argc = 4; - client_open(argc, argv, &st->ct, NULL); + client_open(argc, argv, &st->ct, NULL, NULL, st->btrn, s); set_stat_task_restart_barrier(5); } free(stat_item_values[SI_BASENAME]); @@ -1107,17 +1391,25 @@ static void status_pre_select(struct sched *s, struct task *t) stat_client_write_item(SI_BASENAME); st->last_status_read = *now; out: - start_stop_decoders(s); + start_stop_decoders(); + return 0; } static void init_status_task(struct status_task *st) { memset(st, 0, sizeof(struct status_task)); - st->task.pre_select = status_pre_select; st->sa_time_diff_sign = 1; st->clock_diff_count = conf.clock_diff_count_arg; st->current_audio_format_num = -1; - sprintf(st->task.status, "status task"); + st->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "stat")); + + stat_task->task = task_register(&(struct task_info) { + .name = "stat", + .pre_select = status_pre_select, + .post_select = status_post_select, + .context = stat_task, + }, &sched); } static void set_initial_status(void) @@ -1139,34 +1431,41 @@ static void set_initial_status(void) __noreturn static void print_help_and_die(void) { - int d = conf.detailed_help_given; - const char **p = d? audiod_args_info_detailed_help - : audiod_args_info_help; - - printf_or_die("%s\n\n", AUDIOD_CMDLINE_PARSER_PACKAGE "-" - AUDIOD_CMDLINE_PARSER_VERSION); - printf_or_die("%s\n\n", audiod_args_info_usage); - for (; *p; p++) - printf_or_die("%s\n", *p); - print_receiver_helps(d); - print_filter_helps(d); - print_writer_helps(d); + struct ggo_help h = DEFINE_GGO_HELP(audiod); + bool d = conf.detailed_help_given; + unsigned flags; + + flags = d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS; + ggo_print_help(&h, flags); + + flags = d? GPH_MODULE_FLAGS_DETAILED : GPH_MODULE_FLAGS; + print_receiver_helps(flags); + print_filter_helps(flags); + print_writer_helps(flags); exit(0); } -static void init_colors_or_die(void) +/** + * Lookup the given UID in the whitelist. + * + * The whitelist is the array of arguments to the --user-allow opion. If the + * option was not given, the array is empty, in which case the check succeeds. + * + * \param uid User ID to look up. + * + * \return True if --user-allow was not given, or if uid matches an element of + * the whitelist. + */ +bool uid_is_whitelisted(uid_t uid) { - int ret, i; + int i; - if (!want_colors()) - return; - daemon_set_default_log_colors(); - daemon_set_flag(DF_COLOR_LOG); - for (i = 0; i < conf.log_color_given; i++) { - ret = daemon_set_log_color(conf.log_color_arg[i]); - if (ret < 0) - exit(EXIT_FAILURE); - } + if (!conf.user_allow_given) + return true; + for (i = 0; i < conf.user_allow_given; i++) + if (uid == uid_whitelist[i]) + return true; + return false; } /** @@ -1182,7 +1481,6 @@ static void init_colors_or_die(void) int main(int argc, char *argv[]) { int ret, i; - static struct sched s; struct command_task command_task_struct, *cmd_task = &command_task_struct; struct audiod_cmdline_parser_params params = { .override = 0, @@ -1193,18 +1491,23 @@ int main(int argc, char *argv[]) }; valid_fd_012(); - if (audiod_cmdline_parser_ext(argc, argv, &conf, ¶ms)) - exit(EXIT_FAILURE); - HANDLE_VERSION_FLAG("audiod", conf); + audiod_cmdline_parser_ext(argc, argv, &conf, ¶ms); + daemon_set_loglevel(conf.loglevel_arg); + version_handle_flag("audiod", conf.version_given); /* init receivers/filters/writers early to make help work */ recv_init(); filter_init(); writer_init(); if (conf.help_given || conf.detailed_help_given) print_help_and_die(); - drop_privileges_or_die(conf.user_arg, conf.group_arg); + daemon_set_priority(conf.priority_arg); + daemon_drop_privileges_or_die(conf.user_arg, conf.group_arg); parse_config_or_die(); - init_colors_or_die(); + if (daemon_init_colors_or_die(conf.color_arg, color_arg_auto, color_arg_no, + conf.logfile_given)) { + for (i = 0; i < conf.log_color_given; i++) + daemon_set_log_color_or_die(conf.log_color_arg[i]); + } init_random_seed_or_die(); daemon_set_flag(DF_LOG_TIME); daemon_set_flag(DF_LOG_HOSTNAME); @@ -1220,28 +1523,34 @@ int main(int argc, char *argv[]) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } - log_welcome("para_audiod"); - server_uptime(UPTIME_SET); + daemon_log_welcome("audiod"); + daemon_set_start_time(); set_initial_status(); FOR_EACH_SLOT(i) clear_slot(i); - init_grabbing(); setup_signal_handling(); - signal_setup_default(sig_task); init_status_task(stat_task); init_command_task(cmd_task); if (conf.daemon_given) - daemonize(); - - register_task(&sig_task->task); - register_task(&cmd_task->task); - register_task(&stat_task->task); - s.default_timeout.tv_sec = 0; - s.default_timeout.tv_usec = 999 * 1000; - ret = schedule(&s); + daemonize(false /* parent exits immediately */); + + signal_task->task = task_register(&(struct task_info) { + .name = "signal", + .pre_select = signal_pre_select, + .post_select = signal_post_select, + .context = signal_task, + }, &sched); + + sched.default_timeout.tv_sec = 2; + sched.default_timeout.tv_usec = 999 * 1000; + ret = schedule(&sched); + audiod_cleanup(); + sched_shutdown(&sched); + signal_shutdown(signal_task); - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); - return EXIT_FAILURE; + if (ret < 0) + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; }