/*
- * Copyright (C) 2005-2013 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
*
* Licensed under the GPL v2. For licencing details see COPYING.
*/
#include <sys/un.h>
#include <netdb.h>
#include <signal.h>
+#include <pwd.h>
+#include <lopsub.h>
+#include "audiod.lsg.h"
+#include "recv_cmd.lsg.h"
#include "para.h"
#include "error.h"
#include "crypt.h"
-#include "audiod.cmdline.h"
#include "list.h"
#include "sched.h"
-#include "ggo.h"
#include "buffer_tree.h"
#include "recv.h"
#include "filter.h"
#include "grab_client.h"
-#include "client.cmdline.h"
#include "client.h"
#include "audiod.h"
#include "net.h"
#include "string.h"
#include "fd.h"
#include "write.h"
-#include "write_common.h"
#include "signal.h"
#include "version.h"
+/** Array of error strings. */
+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)))
+#define ENUM_STRING_VAL(_name) (lls_enum_string_val(OPT_UINT32_VAL(_name), \
+ lls_opt(LSG_AUDIOD_PARA_AUDIOD_OPT_ ## _name, CMD_PTR)))
+
__printf_2_3 void (*para_log)(int, const char*, ...) = daemon_log;
-/** define the array of error lists needed by para_audiod */
-INIT_AUDIOD_ERRLISTS;
/** define the array containing all supported audio formats */
const char *audio_formats[] = {AUDIOD_AUDIO_FORMAT_ARRAY NULL};
-DEFINE_RECEIVER_ARRAY;
-
/** 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. */
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 numbers to be activated. */
- int *writer_nums;
- /** pointer to the array of writer configurations */
- void **writer_conf;
+ /** Array of writer IDs to be activated. */
+ int *wids;
+ /** Parsed writer command line(s) */
+ struct lls_parse_result **writer_lpr;
/** do not start receiver/filters/writer before this time */
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
*/
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. */
*/
int audiod_status = AUDIOD_ON;
-/**
- * the gengetopt args_info struct that holds information on all command line
- * arguments
- */
-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
*/
static struct status_task *stat_task = &status_task_struct;
-/**
- * the task for handling audiod commands
+/*
+ * The task for handling audiod commands.
*
- * \sa struct task, struct sched
+ * 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.
+ *
+ * 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 */
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;
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 to take into account that probably the stream was not started at
+ * 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);
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;
if (s->receiver_node->btrn) {
btr_get_node_start(s->receiver_node->btrn, &rstime);
ret = tv_diff(&rstime, &sss, &rskip);
- if (ret > 0) { /* audiod was started in the middle of the stream */
+ 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
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;
- char *config_file;
- struct audiod_cmdline_parser_params params = {
- .override = 0,
- .initialize = 0,
- .check_required = 1,
- .check_ambiguity = 0,
- .print_errors = 1
- };
-
- if (conf.config_file_given)
- config_file = para_strdup(conf.config_file_arg);
+ char *cf, *errctx = NULL;
+ void *map;
+ size_t sz;
+
+ if (OPT_GIVEN(CONFIG_FILE))
+ cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
else {
char *home = para_homedir();
- config_file = make_message("%s/.paraslash/audiod.conf", home);
+ cf = make_message("%s/.paraslash/audiod.conf", home);
free(home);
}
- ret = file_exists(config_file);
- if (conf.config_file_given && !ret) {
- PARA_EMERG_LOG("can not read config file %s\n", config_file);
- goto err;
+ ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+ if (ret < 0) {
+ if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
+ goto free_cf;
+ if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
+ goto free_cf;
+ } else {
+ int cf_argc;
+ char **cf_argv;
+ struct lls_parse_result *cf_lpr, *merged_lpr;
+ ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
+ para_munmap(map, sz);
+ if (ret < 0)
+ goto free_cf;
+ cf_argc = ret;
+ ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr, &errctx));
+ lls_free_argv(cf_argv);
+ if (ret < 0)
+ goto free_cf;
+ ret = lls(lls_merge(lpr, cf_lpr, CMD_PTR, &merged_lpr,
+ &errctx));
+ lls_free_parse_result(cf_lpr, CMD_PTR);
+ if (ret < 0)
+ goto free_cf;
+ lls_free_parse_result(lpr, CMD_PTR);
+ lpr = merged_lpr;
}
- if (ret) {
- audiod_cmdline_parser_config_file(config_file, &conf, ¶ms);
- daemon_set_loglevel(conf.loglevel_arg);
+ daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+ if (OPT_GIVEN(USER_ALLOW)) {
+ uint32_t n = OPT_GIVEN(USER_ALLOW);
+ int i;
+
+ uid_whitelist = para_malloc(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;
+ }
+ errno = 0; /* see getpwnam(3) */
+ pw = getpwnam(arg);
+ if (!pw) {
+ PARA_EMERG_LOG("invalid username: %s\n", arg);
+ free(uid_whitelist);
+ goto free_cf;
+ }
+ uid_whitelist[i] = pw->pw_uid;
+ }
+ }
+ ret = 0;
+free_cf:
+ free(cf);
+ if (ret < 0) {
+ if (errctx)
+ PARA_ERROR_LOG("%s\n", errctx);
+ free(errctx);
+ lls_free_parse_result(lpr, CMD_PTR);
+ PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+ exit(EXIT_FAILURE);
}
- free(config_file);
- 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);
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);
+ 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)
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);
+ task_reap(&fn->task);
}
free(s->fns);
s->fns = NULL;
continue;
if (!s->receiver_node)
continue;
- task_notify(&s->receiver_node->task, error);
+ task_notify(s->receiver_node->task, error);
}
}
s->fns = para_calloc(nf * sizeof(struct filter_node));
parent = s->receiver_node->btrn;
for (i = 0; i < nf; i++) {
- struct filter *f = filters + a->filter_nums[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->task.pre_select = f->pre_select;
- fn->task.post_select = f->post_select;
+ fn->lpr = a->filter_lpr[i];
+ name = filter_name(fn->filter_num);
fn->btrn = btr_new_node(&(struct btr_node_description)
- EMBRACE(.name = f->name, .parent = parent,
+ EMBRACE(.name = name, .parent = parent,
.handler = f->execute, .context = fn));
- f->open(fn);
- register_task(&sched, &fn->task);
+ 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_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, (int)(s - slot));
- sprintf(fn->task.status, "%s (slot %d)", f->name, (int)(s - slot));
+ audio_formats[s->format], i, nf, name, (int)(s - slot));
}
}
* sizeof(struct writer_node));
for (i = 0; i < a->num_writers; i++) {
wn = s->wns + i;
- wn->conf = a->writer_conf[i];
- wn->writer_num = a->writer_nums[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_names[a->writer_nums[i]], (int)(s - slot));
+ writer_name(a->wids[i]), (int)(s - slot));
}
}
struct audio_format_info *a = &afi[format];
struct slot_info *s;
int ret, slot_num;
- struct receiver *r = a->receiver;
+ const struct receiver *r = RECEIVER(a);
+ const char *name = lls_command_name(RECEIVER_CMD(a));
struct receiver_node *rn;
tv_add(now, &(struct timeval)EMBRACE(2, 0), &a->restart_barrier);
slot_num = ret;
rn = para_calloc(sizeof(*rn));
rn->receiver = r;
- rn->conf = a->receiver_conf;
+ rn->lpr = a->receiver_lpr;
rn->btrn = btr_new_node(&(struct btr_node_description)
- EMBRACE(.name = r->name, .context = rn));
+ EMBRACE(.name = name, .context = rn));
ret = r->open(rn);
if (ret < 0) {
btr_remove_node(&rn->btrn);
s->format = format;
s->receiver_node = rn;
PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n",
- audio_formats[format], r->name, slot_num);
- rn->task.pre_select = r->pre_select;
- rn->task.post_select = r->post_select;
- sprintf(rn->task.status, "%s receiver node", r->name);
- register_task(&sched, &rn->task);
+ 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;
}
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;
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)
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;
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);
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++;
);
out:
stat_task->sa_time_diff_sign = sa_time_diff_sign;
- return count;
}
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)
{
int ret, len;
char *re, *p = strchr(txt, ':');
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;
+ struct lls_parse_result *flpr;
- filter_num = check_filter_arg(cmdline, &cfg);
- if (filter_num < 0)
- return filter_num;
+ filter_num = filter_setup(cmdline, &cfg, &flpr);
+ a->filter_lpr = para_realloc(a->filter_lpr,
+ (nf + 1) * sizeof(flpr));
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->filter_lpr[nf] = flpr;
a->num_filters++;
PARA_INFO_LOG("%s filter %d: %s\n", audio_formats[format], nf,
- filters[filter_num].name);
+ filter_name(filter_num));
return filter_num;
}
static int parse_writer_args(void)
{
int i, ret;
- char *cmd;
+ const char *cmd;
struct audio_format_info *a;
- for (i = 0; i < conf.writer_given; i++) {
- void *wconf;
- int j, nw, writer_num, af_mask;
+ for (i = 0; i < OPT_GIVEN(WRITER); i++) {
+ int j, nw, af_mask;
- ret = parse_stream_command(conf.writer_arg[i], &cmd);
+ ret = parse_stream_command(lls_string_val(i,
+ OPT_RESULT(WRITER)), &cmd);
if (ret < 0)
return ret;
af_mask = ret;
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;
+ 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_names[writer_num]);
+ 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) {
- 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;
+ a->wids = para_malloc(sizeof(int));
+ a->writer_lpr = para_malloc(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_names[writer_num]);
+ writer_name(a->wids[0]));
}
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 (i = conf.receiver_given - 1; i >= 0; i--) {
- char *arg;
+ 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(conf.receiver_arg[i], &arg);
+ ret = parse_stream_command(lls_string_val(i,
+ OPT_RESULT(RECEIVER)), &arg);
if (ret < 0)
goto out;
af_mask = ret;
* config here. Since we are iterating backwards, the winning
* receiver arg is in fact the first one given.
*/
- if (a->receiver_conf)
- a->receiver->free_config(a->receiver_conf);
- a->receiver_conf = check_receiver_arg(arg, &receiver_num);
- ret = -E_RECV_SYNTAX;
- if (!a->receiver_conf)
- goto out;
- a->receiver = receivers + receiver_num;
+ lls_free_parse_result(a->receiver_lpr, RECEIVER_CMD(a));
+ a->receiver_num = check_receiver_arg(arg, &a->receiver_lpr);
}
}
/*
- * 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], a->receiver->name);
+ audio_formats[i], lls_command_name(RECEIVER_CMD(a)));
}
ret = 1;
out:
- free(cmd);
return ret;
}
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;
* udp and dccp streams are fec-encoded, so add fecdec as the
* first filter.
*/
- if (strcmp(afi[i].receiver->name, "udp") == 0 ||
- strcmp(afi[i].receiver->name, "dccp") == 0) {
+ if (strcmp(name, "udp") == 0 || strcmp(name, "dccp") == 0) {
tmp = para_strdup("fecdec");
add_filter(i, tmp);
free(tmp);
}
/* 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_name(j)))
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_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],
- filters[j].name);
+ filter_name(j));
}
out:
return ret;
{
int i, j, ret, af_mask, num_matches;
- for (i = 0; i < conf.filter_given; i++) {
- char *arg;
- ret = parse_stream_command(conf.filter_arg[i], &arg);
+ 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;
}
if (num_matches == 0)
PARA_WARNING_LOG("ignoring filter spec: %s\n",
- conf.filter_arg[i]);
+ lls_string_val(i, OPT_RESULT(FILTER)));
}
ret = init_default_filters(); /* use default values for the rest */
out:
}
/* 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);
+ if (OPT_GIVEN(SOCKET))
+ socket_name = para_strdup(OPT_STRING_VAL(SOCKET));
else {
char *hn = para_hostname();
socket_name = make_message("/var/paraslash/audiod_socket.%s",
free(hn);
}
PARA_NOTICE_LOG("local socket: %s\n", socket_name);
- if (conf.force_given)
+ if (OPT_GIVEN(FORCE))
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);
-}
-
-static int signal_post_select(struct sched *s, __a_unused struct task *t)
-{
- 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 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 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);
+ 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 = 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;
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();
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;
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)
{
- int i;
-
if (socket_name)
unlink(socket_name);
close_stat_pipe();
- FOR_EACH_SLOT(i)
- close_slot(i);
- audiod_cmdline_parser_free(&conf);
+ close_unused_slots();
close_stat_clients();
- PARA_EMERG_LOG("%s\n", msg);
- exit(status);
+ free(uid_whitelist);
}
/*
{
int ret;
struct slot_info *sl;
- struct audio_format_info *a;
close_unused_slots();
if (audiod_status != AUDIOD_ON ||
return;
}
sl = slot + ret;
- a = afi + sl->format;
- if (a->num_filters)
- open_filters(sl);
+ 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, 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())
}
/* 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) {
- task_notify(&st->ct->task, E_AUDIOD_OFF);
+ if (task_status(st->ct->task) >= 0) {
+ task_notify(st->ct->task, E_AUDIOD_OFF);
goto out;
}
close_stat_pipe();
- st->clock_diff_count = conf.clock_diff_count_arg;
+ st->clock_diff_count = OPT_UINT32_VAL(CLOCK_DIFF_COUNT);
goto out;
}
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) {
struct timeval diff;
tv_diff(now, &st->last_status_read, &diff);
if (diff.tv_sec > 61)
- task_notify(&st->ct->task, 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);
+ task_notify(st->ct->task, -ret);
goto out;
}
if (sz != ret) {
if (st->clock_diff_count) { /* get status only one time */
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, NULL, st->btrn, s);
set_stat_task_restart_barrier(2);
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->clock_diff_count = OPT_UINT32_VAL(CLOCK_DIFF_COUNT);
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)
{
audiod_status = AUDIOD_ON;
- if (!conf.mode_given)
+ if (!OPT_GIVEN(MODE))
return;
- if (!strcmp(conf.mode_arg, "sb")) {
+ if (!strcmp(OPT_STRING_VAL(MODE), "sb")) {
audiod_status = AUDIOD_STANDBY;
return;
}
- if (!strcmp(conf.mode_arg, "off")) {
+ if (!strcmp(OPT_STRING_VAL(MODE), "off")) {
audiod_status = AUDIOD_OFF;
return;
}
- if (strcmp(conf.mode_arg, "on"))
+ if (strcmp(OPT_STRING_VAL(MODE), "on"))
PARA_WARNING_LOG("invalid mode\n");
}
-__noreturn static void print_help_and_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)
{
- 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);
+ 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 init_colors_or_die(void)
+static void handle_help_flags(void)
{
- int i;
+ char *help;
+ bool d = OPT_GIVEN(DETAILED_HELP);
- if (!want_colors())
+ if (d)
+ help = lls_long_help(CMD_PTR);
+ else if (OPT_GIVEN(HELP))
+ help = lls_short_help(CMD_PTR);
+ else
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]);
+ printf("%s\n", help);
+ free(help);
+ print_receiver_helps(d);
+ print_filter_helps(d);
+ print_writer_helps(d);
+ exit(EXIT_SUCCESS);
}
/**
{
int ret, i;
struct command_task command_task_struct, *cmd_task = &command_task_struct;
- struct audiod_cmdline_parser_params params = {
- .override = 0,
- .initialize = 1,
- .check_required = 0,
- .check_ambiguity = 0,
- .print_errors = 1
- };
+ char *errctx;
valid_fd_012();
- 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);
+ ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+ if (ret < 0)
+ goto out;
+ daemon_set_loglevel(ENUM_STRING_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();
- init_colors_or_die();
init_random_seed_or_die();
+ daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
+ recv_init();
+ 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 (conf.log_timing_given)
+ if (OPT_GIVEN(LOG_TIMING))
daemon_set_flag(DF_LOG_TIMING);
- if (conf.logfile_given) {
- daemon_set_logfile(conf.logfile_arg);
+ if (OPT_GIVEN(LOGFILE)) {
+ daemon_set_logfile(OPT_STRING_VAL(LOGFILE));
daemon_open_log_or_die();
}
ret = parse_stream_args();
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);
setup_signal_handling();
- signal_setup_default(sig_task);
init_status_task(stat_task);
init_command_task(cmd_task);
- if (conf.daemon_given)
+ if (OPT_GIVEN(DAEMON))
daemonize(false /* parent exits immediately */);
- register_task(&sched, &sig_task->task);
- register_task(&sched, &cmd_task->task);
- register_task(&sched, &stat_task->task);
+ 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;
+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;
}