/* SPDX-License-Identifier: GPL-2.0 */ /** \file audiod.c The paraslash's audio daemon. * * The para_audiod(1) executable is a single-threaded program which combines * receivers, filters, writers, a client which runs the stat server command, * and a dispatcher for command requests which arrive at the local socket. * * The functions in this file implement setup, teardown and streaming, * employing the buffer tree API for the latter. The command dispatcher * calls \ref dispatch_local_connection() of \ref audiod_command.c where the * command handlers are defined. The functions of that file call the handful * of public functions defined here or use the few non-static variables. */ #include #include #include #include #include #include #include #include #include #include "audiod.lsg.h" #include "recv_cmd.lsg.h" #include "filter_cmd.lsg.h" #include "write_cmd.lsg.h" #include "para.h" #include "error.h" #include "lsu.h" #include "crypt.h" #include "list.h" #include "sched.h" #include "buffer_tree.h" #include "recv.h" #include "filter.h" #include "grab_client.h" #include "client.h" #include "sideband.h" #include "audiod.h" #include "net.h" #include "daemon.h" #include "string.h" #include "fd.h" #include "write.h" #include "signal.h" /** \cond doxygen_ignore */ DEFINE_PARA_ERRLIST; static struct lls_parse_result *lpr; #define CMD_PTR (lls_cmd(0, audiod_suite)) #define OPT_RESULT(_name) (lls_opt_result(LSG_AUDIOD_PARA_AUDIOD_OPT_ ## _name, lpr)) #define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name))) #define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name))) #define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name))) __printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log; /* Audio formats supported by audiod */ static const char *audio_formats[] = {AUDIOD_AUDIO_FORMAT_ARRAY}; #define NUM_AUDIO_FORMATS ARRAY_SIZE(audio_formats) /* Iterate over all supported audio formats. */ #define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++) /* Defines how audiod handles one supported audio format. */ struct audio_format_info { /* 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. */ unsigned *filter_nums; /* Pointer to the array of filter configurations. */ void **filter_conf; /* Parsed filter command line, one parse result per filter. */ struct lls_parse_result **filter_lpr; /* the number of filters that should be activated for this audio format */ unsigned int num_writers; /* Array of writer IDs to be activated. */ int *wids; /* Parsed writer command line(s) */ struct lls_parse_result **writer_lpr; }; /* 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 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 maintains MAX_STREAM_SLOTS different slots, each of which may * be associated with a receiver/filter/writer triple. */ static struct slot_info slot[MAX_STREAM_SLOTS]; /* The vss status flags audiod is interested in. */ enum vss_status_flags { /* Whether the 'N' flag is set. */ VSS_STATUS_FLAG_NEXT = 1, /* The 'P' flag is set. */ VSS_STATUS_FLAG_PLAYING = 2, }; /* \endcond */ /** * The scheduler instance of para_audiod. * * This is needed also in audiod_command.c (for the tasks command), so it can * not be made static. */ struct sched *sched; /* The task for obtaining para_server's status (para_client stat). */ static struct status_task { /* The associated task structure of audiod. */ struct task *task; /* Client data associated with the stat task. */ struct client_task *ct; /* Do not restart client command until this time. */ struct timeval client_restart_barrier; /* do not start receiver/filters/writer before this time */ struct timeval slot_restart_barrier; /* Last time we received status data from para_server. */ struct timeval last_status_read; /* Minimal input queue size. */ 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. */ int length_seconds; /* The start of the current stream from the view of para_server. */ struct timeval server_stream_start; /* The average time deviation between para_server and para_audiod. */ struct timeval sa_time_diff; /* Whether client time is ahead of server time. */ int sa_time_diff_sign; /* The 'P' and the 'N' flags as announced by para_server. */ enum vss_status_flags vss_status; /* The status task btrn is the child of the client task. */ struct btr_node *btrn; } status_task_struct, *stat_task = &status_task_struct; /** The array of status items sent by para_server. */ char *stat_item_values[NUM_STAT_ITEMS] = {NULL}; /** * The current mode of operation (AUDIOD_OFF, AUDIOD_ON or AUDIOD_STANDBY). * Set by the on/off/cycle commands. */ int audiod_status = AUDIOD_ON; static struct audio_format_info afi[NUM_AUDIO_FORMATS]; static uid_t *uid_whitelist; struct command_task { /* The local listening socket. */ int fd; /* The associated task structure. */ struct task *task; }; /* * Get the audio format number. * * Returns the audio format number on success, -E_UNSUPPORTED_AUDIO_FORMAT * if the given name is not a supported audio format. */ static int get_audio_format_num(void) { int i; const char *name = stat_item_values[SI_format]; if (!name || !*name) return -E_UNSUPPORTED_AUDIO_FORMAT; FOR_EACH_AUDIO_FORMAT(i) if (!strcmp(name, audio_formats[i])) return i; return -E_UNSUPPORTED_AUDIO_FORMAT; } /** * Return the flags for the 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 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); } struct slot_info *get_play_time_slot(void) { int i; struct slot_info *oldest_slot = NULL; struct timeval oldest_wstime = {0, 0}; FOR_EACH_SLOT(i) { struct timeval wstime; struct slot_info *s = slot + i; if (!s->wns || !s->wns[0].btrn) continue; btr_get_node_start(s->wns[0].btrn, &wstime); if (oldest_slot && tv_diff(&wstime, &oldest_wstime, NULL) > 0) continue; oldest_wstime = wstime; oldest_slot = s; } 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 receiver might have been started in * the middle of the stream and that playback is delayed due to the prebuffer * filter. If audiod operates in standby mode, no writer is active. In this * case an approximation based only on the status items is computed and the * returned string is prefixed with "~". * * \return A string as described above on success, the empty string if no * stream is currently announced or if para_audiod is running in "off" mode, * NULL if the stream is currently paused or repositioned. The memory * for the returned string is allocated on the heap and must be freed by * the caller. */ __malloc char *get_time_string(void) { int ret, seconds = 0, length = stat_task->length_seconds; struct timeval *sss = &stat_task->server_stream_start, tmp, wstime; struct slot_info *s = get_play_time_slot(); bool writer_active = s && s->wns && s->wns[0].btrn; if (audiod_status == AUDIOD_OFF) return para_strdup(NULL); if (sss->tv_sec == 0 || length == 0) { /* no stream */ if (stat_task->vss_status & VSS_STATUS_FLAG_PLAYING) return NULL; /* repositioning or about to change file */ if (length > 0) { /* paused */ seconds = stat_task->offset_seconds; goto out; } return para_strdup(NULL); /* stopped */ } if (!writer_active) { struct timeval diff; tv_diff(now, sss, &diff); seconds = diff.tv_sec + stat_task->offset_seconds; goto out; } btr_get_node_start(s->wns[0].btrn, &wstime); if (wstime.tv_sec == 0) return NULL; if (s->server_stream_start.tv_sec == 0) { /* copy status info to slot */ s->offset_seconds = stat_task->offset_seconds; s->server_stream_start = *sss; } seconds = s->offset_seconds; /* check if audiod was started in the middle of the stream */ ret = tv_diff(&wstime, &s->server_stream_start, &tmp); if (ret > 0 && tv2ms(&tmp) > 2000) /* assume it was */ tv_diff(now, &s->server_stream_start, &tmp); else tv_diff(now, &wstime, &tmp); seconds += tmp.tv_sec + (tmp.tv_usec + 500 * 1000) / 1000 / 1000; out: seconds = PARA_MIN(seconds, length); seconds = PARA_MAX(seconds, 0); return make_message( "%s%d:%02d [%d:%02d] (%d%%/%d:%02d)", writer_active? "" : "~", seconds / 60, seconds % 60, (length - seconds) / 60, (length - seconds) % 60, length? (seconds * 100 + length / 2) / length : 0, length / 60, length % 60 ); } static void parse_config_or_die(void) { int i, ret; uint32_t n; ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE), "audiod.conf", &lpr, CMD_PTR, audiod_suite, 0U /* flags */); if (ret < 0) { PARA_EMERG_LOG("failed to parse config file: %s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL)); n = OPT_GIVEN(USER_ALLOW); if (n == 0) return; uid_whitelist = arr_alloc(n, sizeof(uid_t)); for (i = 0; i < n; i++) { const char *arg = lls_string_val(i, OPT_RESULT(USER_ALLOW)); int32_t val; struct passwd *pw; ret = para_atoi32(arg, &val); if (ret >= 0) { uid_whitelist[i] = val; continue; } pw = getpwnam(arg); if (!pw) { PARA_EMERG_LOG("invalid username: %s\n", arg); exit(EXIT_FAILURE); } uid_whitelist[i] = pw->pw_uid; } } static void clear_slot(int slot_num) { struct slot_info *s = &slot[slot_num]; PARA_INFO_LOG("clearing slot %d\n", slot_num); memset(s, 0, sizeof(struct slot_info)); s->format = -1; } static void close_receiver(int slot_num) { struct slot_info *s = &slot[slot_num]; struct audio_format_info *a; if (s->format < 0 || !s->receiver_node) return; a = &afi[s->format]; PARA_NOTICE_LOG("closing %s receiver in slot %d\n", audio_formats[s->format], slot_num); 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; tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000), &stat_task->slot_restart_barrier); } static void writer_cleanup(struct writer_node *wn) { if (!wn) return; PARA_INFO_LOG("closing %s\n", writer_name(wn->wid)); writer_get(wn->wid)->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 notify_writers(int error) { int i; FOR_EACH_SLOT(i) { struct slot_info *s = slot + i; struct audio_format_info *a; int j; if (s->format < 0) continue; a = afi + s->format; for (j = 0; j < a->num_writers; j++) task_notify(s->wns[j].task, 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->format < 0) continue; if (!s->receiver_node) continue; task_notify(s->receiver_node->task, error); } } static void open_filters(struct slot_info *s) { struct audio_format_info *a = afi + s->format; struct filter_node *fn; int nf = a->num_filters; struct btr_node *parent; int i; if (nf == 0) return; PARA_INFO_LOG("opening %s filters\n", audio_formats[s->format]); assert(s->fns == NULL); s->fns = zalloc(nf * sizeof(struct filter_node)); parent = s->receiver_node->btrn; for (i = 0; i < nf; i++) { char buf[20]; const char *name; 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->lpr = a->filter_lpr[i]; name = filter_name(fn->filter_num); fn->btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.name = name, .parent = parent, .handler = f->execute, .context = fn)); if (f->open) f->open(fn); sprintf(buf, "%s (slot %d)", name, (int)(s - slot)); fn->task = task_register(&(struct task_info) { .name = buf, .pre_monitor = f->pre_monitor, .post_monitor = f->post_monitor, .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, name, (int)(s - slot)); } } static void open_writers(struct slot_info *s) { 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; assert(s->wns == NULL); s->wns = zalloc(PARA_MAX(1U, a->num_writers) * sizeof(struct writer_node)); for (i = 0; i < a->num_writers; i++) { wn = s->wns + i; wn->wid = a->wids[i]; wn->lpr = a->writer_lpr[i]; register_writer_node(wn, parent, sched); PARA_NOTICE_LOG("%s writer started in slot %d\n", writer_name(a->wids[i]), (int)(s - slot)); } } /* 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; FOR_EACH_SLOT(slot_num) if (slot[slot_num].format < 0) break; if (slot_num >= MAX_STREAM_SLOTS) return -E_NO_MORE_SLOTS; rn = zalloc(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) { PARA_ERROR_LOG("could not open %s receiver\n", name); 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[format], name, slot_num); rn->task = task_register(&(struct task_info) { .name = name, .pre_monitor = r->pre_monitor, .post_monitor = r->post_monitor, .context = rn, }, sched); return slot_num; } static bool receiver_running(void) { int i; long unsigned ss1 = stat_task->server_stream_start.tv_sec; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; long unsigned ss2 = s->server_stream_start.tv_sec; if (!s->receiver_node) continue; if (task_status(s->receiver_node->task) >= 0) return true; if (ss1 == ss2) return true; } return false; } /** * Return the root node of the current buffer tree. * * This is only used for stream grabbing. * * \return 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) { int i, newest_slot = -1; struct timeval newest_rstime = {0, 0}; 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; } if (newest_slot == -1) return NULL; return slot[newest_slot].receiver_node->btrn; } /* always initializes num */ static bool must_start_decoder(int *num) { unsigned vs = stat_task->vss_status; *num = get_audio_format_num(); if (*num < 0) return false; if (audiod_status != AUDIOD_ON) 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, &stat_task->slot_restart_barrier, NULL) < 0) return false; return true; } static void compute_time_diff(const struct timeval *status_time) { struct timeval tmp, diff; static unsigned count; 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; 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; stat_task->sa_time_diff = diff; count++; goto out; } 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: %c%lums\n", s < 0? '-' : '+', tv2ms(&tmp)); } count++; sa_time_diff_sign = tv_convex_combination( sa_time_diff_sign * time_smooth, &stat_task->sa_time_diff, count > 10? sign : sign * time_smooth, &diff, &tmp); stat_task->sa_time_diff = tmp; PARA_INFO_LOG("time diff (cur/avg): %s%lums/%s%lums\n", sign < 0? "-" : "+", tv2ms(&diff), sa_time_diff_sign < 0? "-" : "+", tv2ms(&stat_task->sa_time_diff) ); out: stat_task->sa_time_diff_sign = sa_time_diff_sign; } static void update_items(uint64_t mask) { int i; FOR_EACH_STATUS_ITEM(i) { long unsigned sec, usec; char *buf = stat_item_values[i]; if (!status_item_bit_set(i, mask)) continue; stat_client_write_item(i); switch (i) { case SI_status_flags: stat_task->vss_status = 0; if (strchr(buf, 'N')) stat_task->vss_status |= VSS_STATUS_FLAG_NEXT; if (strchr(buf, 'P')) stat_task->vss_status |= VSS_STATUS_FLAG_PLAYING; break; case SI_offset: stat_task->offset_seconds = atoi(buf); break; case SI_seconds_total: stat_task->length_seconds = atoi(buf); break; case SI_stream_start: if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { struct timeval tmp = {.tv_sec = sec, .tv_usec = usec}; if (stat_task->sa_time_diff_sign > 0) tv_diff(&tmp, &stat_task->sa_time_diff, &stat_task->server_stream_start); else tv_add(&tmp, &stat_task->sa_time_diff, &stat_task->server_stream_start); } break; case SI_current_time: if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { struct timeval tv = {sec, usec}; compute_time_diff(&tv); } break; } } if (status_item_bit_set(SI_offset, mask)) audiod_status_dump(false); } static int add_filter(int format, const char *cmdline) { struct audio_format_info *a = &afi[format]; int ret, filter_num, nf = a->num_filters; void *cfg; struct lls_parse_result *flpr; ret = filter_setup(cmdline, &cfg, &flpr); if (ret < 0) return ret; filter_num = ret; a->filter_lpr = arr_realloc(a->filter_lpr, nf + 1, sizeof(flpr)); a->filter_conf = arr_realloc(a->filter_conf, nf + 1, sizeof(void *)); a->filter_nums = arr_realloc(a->filter_nums, nf + 1, sizeof(unsigned)); a->filter_nums[nf] = filter_num; a->filter_conf[nf] = cfg; a->filter_lpr[nf] = flpr; a->num_filters++; PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf, filter_name(filter_num)); return filter_num; } static int parse_stream_command(const char *txt, const char **cmd) { int i, ret, len; regex_t preg; char *re; const char *p = strchr(txt, ':'); if (!p) return -E_MISSING_COLON; *cmd = p + 1; len = p - txt; re = alloc(len + 1); strncpy(re, txt, len); re[len] = '\0'; ret = para_regcomp(&preg, re, REG_EXTENDED | REG_NOSUB); free(re); 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 parse_writer_args(void) { int i, ret; const char *cmd; struct audio_format_info *a; for (i = 0; i < OPT_GIVEN(WRITER); i++) { int j, nw, af_mask; ret = parse_stream_command(lls_string_val(i, OPT_RESULT(WRITER)), &cmd); if (ret < 0) return ret; af_mask = ret; FOR_EACH_AUDIO_FORMAT(j) { a = afi + j; if ((af_mask & (1 << j)) == 0) /* no match */ continue; nw = a->num_writers; a->wids = para_realloc(a->wids, (nw + 1) * sizeof(int)); a->writer_lpr = para_realloc(a->writer_lpr, (nw + 1) * sizeof(struct lls_parse_result *)); a->wids[nw] = check_writer_arg_or_die(cmd, a->writer_lpr + nw); PARA_INFO_LOG("%s writer #%d: %s\n", audio_formats[j], nw, writer_name(a->wids[nw])); a->num_writers++; } } /* Use default writer for audio formats which are not yet set up. */ FOR_EACH_AUDIO_FORMAT(i) { a = afi + i; if (a->num_writers > 0) continue; /* already set up */ a->num_writers = 1; a->wids = alloc(sizeof(int)); a->writer_lpr = alloc(sizeof(struct lls_parse_result *)); a->wids[0] = check_writer_arg_or_die(NULL, a->writer_lpr); PARA_INFO_LOG("%s writer: %s (default)\n", audio_formats[i], writer_name(a->wids[0])); } return 1; } static int parse_receiver_args(void) { int i, ret; const char *arg; struct audio_format_info *a; FOR_EACH_AUDIO_FORMAT(i) afi[i].receiver_num = -1; for (i = OPT_GIVEN(RECEIVER) - 1; i >= 0; i--) { int j, af_mask; ret = parse_stream_command(lls_string_val(i, OPT_RESULT(RECEIVER)), &arg); if (ret < 0) 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); } } /* * Use the default receiver for those audio formats for which no * receiver was specified. */ FOR_EACH_AUDIO_FORMAT(i) { a = afi + i; if (a->receiver_num >= 0) continue; 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: return ret; } static int init_default_filters(void) { int i, ret = 1; 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 streams are fec-encoded, so add fecdec as the first * filter. */ if (strcmp(name, "udp") == 0) { tmp = para_strdup("fecdec"); ret = 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 = 1; filter_get(j); j++) if (!strcmp(tmp, filter_name(j))) break; free(tmp); ret = -E_UNSUPPORTED_FILTER; if (!filter_get(j)) goto out; tmp = para_strdup(filter_name(j)); ret = add_filter(i, tmp); free(tmp); if (ret < 0) goto out; PARA_INFO_LOG("%s -> default filter: %s\n", audio_formats[i], filter_name(j)); } out: return ret; } static int parse_filter_args(void) { int i, j, ret, af_mask, num_matches; for (i = 0; i < OPT_GIVEN(FILTER); i++) { const char *arg; ret = parse_stream_command(lls_string_val(i, OPT_RESULT(FILTER)), &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", lls_string_val(i, OPT_RESULT(FILTER))); } ret = init_default_filters(); /* use default values for the rest */ out: return ret; } static void parse_stream_args(void) { int ret; ret = parse_receiver_args(); if (ret < 0) goto fail; ret = parse_filter_args(); if (ret < 0) goto fail; ret = parse_writer_args(); if (ret < 0) goto fail; return; fail: PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } /* does not unlink socket on errors */ static char *init_local_socket(struct command_task *ct) { char *socket_name; if (OPT_GIVEN(SOCKET)) socket_name = para_strdup(OPT_STRING_VAL(SOCKET)); else socket_name = make_message("/var/paraslash/audiod_socket.%s", para_hostname()); PARA_NOTICE_LOG("local socket: %s\n", socket_name); if (OPT_GIVEN(FORCE)) unlink(socket_name); ct->fd = create_local_socket(socket_name); if (ct->fd >= 0) return socket_name; PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd)); exit(EXIT_FAILURE); } static int signal_post_monitor(struct sched *s, void *context) { struct signal_task *st = context; int ret, signum; ret = task_get_notification(st->task); if (ret < 0) return ret; signum = para_next_signal(); switch (signum) { case SIGINT: case SIGTERM: case SIGHUP: PARA_WARNING_LOG("terminating on signal %d\n", signum); task_notify_all(s, E_AUDIOD_SIGNAL); return -E_AUDIOD_SIGNAL; } return 0; } static void command_pre_monitor(struct sched *s, void *context) { struct command_task *ct = context; sched_monitor_readfd(ct->fd, s); } static int command_post_monitor(struct sched *s, void *context) { int ret; struct command_task *ct = context; static struct timeval last_status_dump; struct timeval tmp, delay; ret = task_get_notification(ct->task); if (ret < 0) return ret; ret = dispatch_local_connection(ct->fd); if (ret < 0) { PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); if (ret == -E_AUDIOD_TERM) { task_notify_all(s, -ret); return ret; } } /* if last status dump was less than 50ms ago, do nothing */ delay.tv_sec = 0; delay.tv_usec = 50 * 1000; tv_add(&last_status_dump, &delay, &tmp); if (tv_diff(now, &tmp, NULL) < 0) return 0; audiod_status_dump(false); last_status_dump = *now; return 1; } /* Free all status items and reset the corresponding pointers. */ static void clear_status_items(void) { int i; FOR_EACH_STATUS_ITEM(i) { free(stat_item_values[i]); stat_item_values[i] = NULL; } } static void close_stat_pipe(void) { int i; if (!stat_task->ct) return; task_reap(&stat_task->ct->task); client_close(stat_task->ct); stat_task->ct = NULL; clear_status_items(); stat_task->length_seconds = 0; stat_task->offset_seconds = 0; stat_task->vss_status = 0; FOR_EACH_STATUS_ITEM(i) stat_client_write_item(i); audiod_status_dump(true); } 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 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_unused_slots(void) { int i; bool dump = false; FOR_EACH_SLOT(i) { struct slot_info *s = slot + i; if (!must_close_slot(i)) continue; PARA_INFO_LOG("closing slot %d\n", i); close_writers(s); close_filters(s); close_receiver(i); clear_slot(i); dump = true; } if (dump) audiod_status_dump(true); } static void audiod_cleanup(void) { int i, j; FOR_EACH_AUDIO_FORMAT(i) { struct audio_format_info *a = afi + i; unsigned idx; const struct lls_command *cmd; for (j = 0; j < a->num_filters; j++) { idx = a->filter_nums[j]; cmd = lls_cmd(idx, filter_cmd_suite); lls_free_parse_result(a->filter_lpr[j], cmd); } free(a->filter_nums); free(a->filter_lpr); free(a->filter_conf); for (j = 0; j < a->num_writers; j++) { idx = a->wids[j]; cmd = lls_cmd(idx, write_cmd_suite); lls_free_parse_result(a->writer_lpr[j], cmd); } free(a->wids); free(a->writer_lpr); lls_free_parse_result(a->receiver_lpr, RECEIVER_CMD(a)); } close_stat_pipe(); close_unused_slots(); close_stat_clients(); clear_status_items(); free(uid_whitelist); } /* * Check if any receivers/filters/writers need to be started and do so if * necessary. */ static void start_stop_decoders(void) { int ret, num; struct slot_info *sl; close_unused_slots(); if (audiod_status != AUDIOD_ON) return notify_writers(E_NOT_PLAYING); if (!(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)) return notify_receivers(E_NOT_PLAYING); if (!must_start_decoder(&num)) return; ret = open_receiver(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_monitor(struct sched *s, void *context) { struct status_task *st = context; int i, ret, num; if (must_start_decoder(&num)) 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->client_restart_barrier, s); if (num >= 0) sched_request_barrier(&st->slot_restart_barrier, s); return; min_delay: sched_min_delay(s); } /* restart the client task if necessary */ static int status_post_monitor(struct sched *s, void *context) { char *argv[] = {"audiod", "--", "stat", "-p", NULL}; int argc = 4; struct status_task *st = context; int ret; ret = task_get_notification(st->task); if (ret < 0) { btr_remove_node(&st->btrn); return ret; } if (audiod_status == AUDIOD_OFF) { if (!st->ct) goto out; if (task_status(st->ct->task) >= 0) { task_notify(st->ct->task, E_AUDIOD_OFF); goto out; } close_stat_pipe(); goto out; } if (st->ct) { uint64_t mask; 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_EXECUTING) goto out; if (ret == 0) { struct timeval diff; tv_diff(now, &st->last_status_read, &diff); if (diff.tv_sec > 61) task_notify(st->ct->task, E_STATUS_TIMEOUT); goto out; } btr_merge(st->btrn, st->min_iqs); sz = btr_next_buffer(st->btrn, &buf); mask = parse_status_items(buf, sz, stat_item_values); btr_consume(st->btrn, sz); st->last_status_read = *now; st->min_iqs = 0; update_items(mask); goto out; } btr_drain(st->btrn); if (tv_diff(now, &st->client_restart_barrier, NULL) < 0) goto out; ret = client_open(argc, argv, &st->ct, NULL, st->btrn, s); if (ret < 0) PARA_NOTICE_LOG("no connection to para_server: %s\n", para_strerror(-ret)); /* avoid busy loop if server is down */ tv_add(now, &(struct timeval){5, 0}, &st->client_restart_barrier); st->last_status_read = *now; out: start_stop_decoders(); return 0; } static int init_tasks_and_schedule(void) { struct signal_task signal_task; struct command_task command_task; char *socket_name; int ret; sched = sched_new(NULL); stat_task->sa_time_diff_sign = 1; stat_task->btrn = btr_new_node(&(struct btr_node_description) EMBRACE(.name = "stat")); if (OPT_GIVEN(CLOCK_DIFF_COUNT)) PARA_WARNING_LOG("--clock-diff-count is deprecated\n"); stat_task->task = task_register(&(struct task_info) { .name = "stat", .pre_monitor = status_pre_monitor, .post_monitor = status_post_monitor, .context = stat_task, }, sched); socket_name = init_local_socket(&command_task); command_task.task = task_register(&(struct task_info) { .name = "command", .pre_monitor = command_pre_monitor, .post_monitor = command_post_monitor, .context = &command_task, }, sched); signal_task.fd = signal_init(); para_install_sighandler(SIGINT); para_install_sighandler(SIGTERM); para_install_sighandler(SIGHUP); para_sigaction(SIGPIPE, SIG_IGN); signal_task.task = task_register(&(struct task_info) { .name = "signal", .pre_monitor = signal_pre_monitor, .post_monitor = signal_post_monitor, .context = &signal_task, }, sched); ret = schedule(sched); unlink(socket_name); free(socket_name); return ret; } static void set_initial_status(void) { audiod_status = AUDIOD_ON; if (!OPT_GIVEN(MODE)) return; if (!strcmp(OPT_STRING_VAL(MODE), "sb")) { audiod_status = AUDIOD_STANDBY; return; } if (!strcmp(OPT_STRING_VAL(MODE), "off")) { audiod_status = AUDIOD_OFF; return; } if (strcmp(OPT_STRING_VAL(MODE), "on")) PARA_WARNING_LOG("invalid mode\n"); } /** * Lookup the given UID in the whitelist. * * The whitelist is the array of arguments to the --user-allow option. 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 i; if (!OPT_GIVEN(USER_ALLOW)) return true; for (i = 0; i < OPT_GIVEN(USER_ALLOW); i++) if (uid == uid_whitelist[i]) return true; return false; } static void handle_help_flags(void) { char *help; bool d = OPT_GIVEN(DETAILED_HELP); if (d) help = lls_long_help(CMD_PTR); else if (OPT_GIVEN(HELP)) help = lls_short_help(CMD_PTR); else return; printf("%s\n", help); free(help); print_receiver_helps(d); print_filter_helps(d); print_writer_helps(d); exit(EXIT_SUCCESS); } /** * The main function of para_audiod(1). * * \param argc Options are defined in the audiod lopsub suite. * \param argv The subcommands are defined in the audiod_command suite. * * At startup we create and register tasks to handle signals, to run the * stat server subcommand and to dispatch requests from para_audioc(1) on * the local socket. Additional tasks are created to download, filter and * write the stream. * * \return EXIT_SUCCESS or EXIT_FAILURE * * \sa para_audiod(1) * */ int main(int argc, char *argv[]) { int ret, i; char *errctx; valid_fd_012(); ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx)); if (ret < 0) goto out; daemon_set_loglevel(OPT_UINT32_VAL(LOGLEVEL)); daemon_drop_privileges_or_die(OPT_STRING_VAL(USER), OPT_STRING_VAL(GROUP)); version_handle_flag("audiod", OPT_GIVEN(VERSION)); handle_help_flags(); parse_config_or_die(); crypt_init(); daemon_set_priority(OPT_UINT32_VAL(PRIORITY)); if (daemon_init_colors_or_die(OPT_UINT32_VAL(COLOR), COLOR_AUTO, COLOR_NO, OPT_GIVEN(LOGFILE))) { for (i = 0; i < OPT_GIVEN(LOG_COLOR); i++) daemon_set_log_color_or_die(lls_string_val(i, OPT_RESULT(LOG_COLOR))); } daemon_set_flag(DF_LOG_TIME); daemon_set_flag(DF_LOG_HOSTNAME); daemon_set_flag(DF_LOG_LL); if (OPT_GIVEN(LOG_TIMING)) daemon_set_flag(DF_LOG_TIMING); if (OPT_GIVEN(LOGFILE)) { daemon_set_logfile(OPT_STRING_VAL(LOGFILE)); daemon_open_log_or_die(); } parse_stream_args(); daemon_log_welcome("audiod"); daemon_set_start_time(); set_initial_status(); FOR_EACH_SLOT(i) clear_slot(i); if (OPT_GIVEN(DAEMON)) daemonize(false /* parent exits immediately */); ret = init_tasks_and_schedule(); audiod_cleanup(); sched_shutdown(sched); crypt_shutdown(); out: lls_free_parse_result(lpr, CMD_PTR); if (errctx) PARA_ERROR_LOG("%s\n", errctx); if (ret < 0) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; }