X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=audiod.c;h=7be40de04dc9f9ef6fbd1f89bbf43838dc955c69;hp=948204dc8829a78c3a4595d5a03dde48733d0462;hb=20635453048bdf7f99d9fb5fbe53178ff386f74c;hpb=0b43ec5dfd9e301257e3c0b31733ea67c1db4426 diff --git a/audiod.c b/audiod.c index 948204dc..7be40de0 100644 --- a/audiod.c +++ b/audiod.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2014 Andre Noll + * Copyright (C) 2005 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -14,6 +14,7 @@ #include #include #include +#include #include "para.h" #include "error.h" @@ -68,6 +69,30 @@ 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; +}; + +/** 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 @@ -93,14 +118,10 @@ enum vss_status_flags { */ struct sched sched = {.max_fileno = 0}; -/** - * The task for obtaining para_server's status (para_client stat). - * - * \sa struct task, struct sched. - */ +/* 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. */ @@ -148,10 +169,12 @@ 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 * @@ -159,16 +182,24 @@ static struct status_task status_task_struct; */ static struct status_task *stat_task = &status_task_struct; -/** - * 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 */ @@ -194,6 +225,35 @@ static int get_audio_format_num(const char *name) return -E_UNSUPPORTED_AUDIO_FORMAT; } +/** + * 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 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; @@ -210,53 +270,69 @@ static int get_matching_audio_format_nums(const char *re) 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 given slot. - * - * \param slot_num The slot number (negative means: no 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 given slot. + * (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 of the given slot. + * the prebuffer filter is used in the filter configuration. * - * If no writer is active in the given slot, or \a slot_num is negative - * (indicating that para_audiod runs in standby mode), an approximation based - * only on the status items is computed and the returned string is prefixed - * with "~". + * 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(int slot_num) +char *get_time_string(void) { - int ret, seconds = 0, length; + 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, 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. */ - length = stat_task->length_seconds; 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); @@ -275,7 +351,7 @@ char *get_time_string(int slot_num) tv_diff(tmp, &stat_task->sa_time_diff, &sss); else tv_add(tmp, &stat_task->sa_time_diff, &sss); - if (!s || !s->wns || !s->wns[0].btrn) { + 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; @@ -314,20 +390,9 @@ 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, @@ -347,6 +412,7 @@ 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) { @@ -354,16 +420,35 @@ static void parse_config_or_die(void) daemon_set_loglevel(conf.loglevel_arg); } free(config_file); + 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); @@ -394,6 +479,7 @@ static void close_receiver(int slot_num) 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); } @@ -438,11 +524,11 @@ static void close_filters(struct slot_info *s) return; for (i = a->num_filters - 1; i >= 0; i--) { struct filter_node *fn = s->fns + i; - struct filter *f; + const struct filter *f; if (!fn) continue; - f = filters + fn->filter_num; + f = filter_get(fn->filter_num); if (f->close) f->close(fn); btr_remove_node(&fn->btrn); @@ -501,7 +587,7 @@ static void open_filters(struct slot_info *s) parent = s->receiver_node->btrn; for (i = 0; i < nf; i++) { char buf[20]; - struct filter *f = filters + a->filter_nums[i]; + 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]; @@ -593,7 +679,7 @@ static bool receiver_running(void) if (!s->receiver_node) continue; - if (s->receiver_node->task->error >= 0) + if (task_status(s->receiver_node->task) >= 0) return true; if (ss1 == ss2) return true; @@ -620,7 +706,7 @@ struct btr_node *audiod_get_btr_root(void) struct timeval rstime; if (!s->receiver_node) continue; - if (s->receiver_node->task->error < 0) + 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) @@ -771,7 +857,7 @@ static int add_filter(int format, char *cmdline) 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; } @@ -906,20 +992,20 @@ static int init_default_filters(void) } /* 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; @@ -970,11 +1056,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 { @@ -986,63 +1069,70 @@ 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, + 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_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)); + 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) -{ - struct signal_task *st = task_context(t); - para_fd_set(st->fd, &s->rfds, &s->max_fileno); -} - -static int signal_post_select(struct sched *s, __a_unused struct task *t) +static int signal_post_select(struct sched *s, void *context) { - int signum; + struct signal_task *st = context; + int ret, 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_NOTICE_LOG("received signal %d\n", signum); - clean_exit(EXIT_FAILURE, "caught deadly signal"); + task_notify_all(s, E_AUDIOD_SIGNAL); + return -E_AUDIOD_SIGNAL; } return 0; } -static void command_pre_select(struct sched *s, struct task *t) +static void command_pre_select(struct sched *s, void *context) { - struct command_task *ct = container_of(t, struct command_task, task); - para_fd_set(ct->fd, &s->rfds, &s->max_fileno); + struct command_task *ct = context; + int i; + + for (i = 0; i < 2; i++) + if (ct->fd[i] >= 0) + para_fd_set(ct->fd[i], &s->rfds, &s->max_fileno); } -static int 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, delay; - bool force = true; + bool force = false; - ret = handle_connect(ct->fd, &s->rfds); + ret = task_get_notification(ct->task); if (ret < 0) - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - else 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, uid_whitelist); + 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 */ @@ -1059,8 +1149,8 @@ static int command_post_select(struct sched *s, struct task *t) delay.tv_sec = 5; delay.tv_usec = 0; tv_add(&last_status_dump, &delay, &tmp); - if (tv_diff(now, &tmp, NULL) < 0) - force = false; + if (tv_diff(now, &tmp, NULL) > 0) + force = true; dump: audiod_status_dump(force); last_status_dump = *now; @@ -1069,19 +1159,22 @@ dump: 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; - client_close(stat_task->ct); task_reap(&stat_task->ct->task); + client_close(stat_task->ct); stat_task->ct = NULL; clear_and_dump_items(); stat_task->length_seconds = 0; @@ -1106,17 +1199,17 @@ static bool must_close_slot(int slot_num) if (s->format < 0) return false; - if (s->receiver_node && s->receiver_node->task->error >= 0) + if (s->receiver_node && task_status(s->receiver_node->task) >= 0) return false; for (i = 0; i < a->num_filters; i++) - if (s->fns && s->fns[i].task->error >= 0) + 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 && s->wns[i].task->error >= 0) + if (s->wns && task_status(s->wns[i].task) >= 0) return false; } else { - if (s->wns && s->wns[0].task->error >= 0) + if (s->wns && task_status(s->wns[0].task) >= 0) return false; } return true; @@ -1136,25 +1229,24 @@ static void close_slot(int slot_num) static void close_unused_slots(void) { int i; + bool dump = false; FOR_EACH_SLOT(i) - if (must_close_slot(i)) + if (must_close_slot(i)) { close_slot(i); + dump = true; + } + if (dump) + audiod_status_dump(true); } -/** - * 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 and - * all slots, and call \p exit(status). \a status should be either EXIT_SUCCESS - * or EXIT_FAILURE. +/* + * Cleanup all resources. * - * \sa exit(3). + * This performs various cleanups, removes the audiod socket and closes the + * connection to para_server. */ -void __noreturn clean_exit(int status, const char *msg) +static void audiod_cleanup(void) { if (socket_name) unlink(socket_name); @@ -1162,8 +1254,7 @@ void __noreturn clean_exit(int status, const char *msg) close_unused_slots(); audiod_cmdline_parser_free(&conf); close_stat_clients(); - PARA_EMERG_LOG("%s\n", msg); - exit(status); + free(uid_whitelist); } /* @@ -1191,11 +1282,12 @@ static void start_stop_decoders(void) 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, struct task *t) +static void status_pre_select(struct sched *s, void *context) { - struct status_task *st = container_of(t, struct status_task, task); + struct status_task *st = context; int i, ret, cafn = stat_task->current_audio_format_num; if (must_start_decoder()) @@ -1225,14 +1317,18 @@ min_delay: } /* restart the client task if necessary */ -static int status_post_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) { + if (task_status(st->ct->task) >= 0) { task_notify(st->ct->task, E_AUDIOD_OFF); goto out; } @@ -1243,7 +1339,6 @@ static int status_post_select(struct sched *s, struct task *t) if (st->ct) { char *buf; size_t sz; - int ret; ret = btr_node_status(st->btrn, st->min_iqs, BTR_NT_LEAF); if (ret < 0) { @@ -1305,14 +1400,18 @@ out: static void init_status_task(struct status_task *st) { memset(st, 0, sizeof(struct status_task)); - st->task.pre_select = status_pre_select; - st->task.post_select = status_post_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, "stat"); 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) @@ -1348,18 +1447,6 @@ __noreturn static void print_help_and_die(void) exit(0); } -static void init_colors_or_die(void) -{ - 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++) - daemon_set_log_color_or_die(conf.log_color_arg[i]); -} - /** * the main function of para_audiod * @@ -1392,9 +1479,11 @@ int main(int argc, char *argv[]) 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(); + daemon_init_colors_or_die(conf.color_arg, color_arg_auto, color_arg_no, + conf.logfile_given, conf.log_color_arg, conf.log_color_given); init_random_seed_or_die(); daemon_set_flag(DF_LOG_TIME); daemon_set_flag(DF_LOG_HOSTNAME); @@ -1410,8 +1499,8 @@ int main(int argc, char *argv[]) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } - log_welcome("para_audiod"); - set_server_start_time(NULL); + daemon_log_welcome("audiod"); + daemon_set_start_time(); set_initial_status(); FOR_EACH_SLOT(i) clear_slot(i); @@ -1423,19 +1512,21 @@ int main(int argc, char *argv[]) if (conf.daemon_given) daemonize(false /* parent exits immediately */); - sig_task->task = task_register(&(struct task_info) { + signal_task->task = task_register(&(struct task_info) { .name = "signal", .pre_select = signal_pre_select, .post_select = signal_post_select, - .context = sig_task, + .context = signal_task, }, &sched); - register_task(&sched, &cmd_task->task); - register_task(&sched, &stat_task->task); + 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; }