-/*
- * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
/** \file audiod.c The paraslash's audio daemon. */
#include <pwd.h>
#include <lopsub.h>
+#include "audiod.lsg.h"
#include "recv_cmd.lsg.h"
-#include "client.lsg.h"
#include "para.h"
#include "error.h"
+#include "lsu.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"
/** 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 containing all supported audio formats */
const char *audio_formats[] = {AUDIOD_AUDIO_FORMAT_ARRAY NULL};
* 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
* information on the status of these slots.
- *
- * \sa struct slot_info
- * */
+ */
struct slot_info slot[MAX_STREAM_SLOTS];
/** The vss status flags audiod is interested in. */
char *stat_item_values[NUM_STAT_ITEMS] = {NULL};
/**
- * the current mode of operation of which can be changed by the on/off/cycle
- * commands. It is either, AUDIOD_OFF, AUDIOD_ON or AUDIOD_STANDBY.
+ * The current mode of operation (AUDIOD_OFF, AUDIOD_ON or AUDIOD_STANDBY).
+ * Set by the on/off/cycle commands.
*/
int audiod_status = AUDIOD_ON;
-/**
- * the gengetopt args_info struct that holds information on all command line
- * arguments
- */
-static struct audiod_args_info conf;
-
static char *socket_name;
static struct audio_format_info afi[NUM_AUDIO_FORMATS];
-
static struct signal_task *signal_task;
-
static struct status_task status_task_struct;
-
static uid_t *uid_whitelist;
/**
- * the task that calls the status command of para_server
+ * The task that calls the status command of para_server.
*
- * \sa struct status_task
+ * \sa \ref struct status_task.
*/
static struct status_task *stat_task = &status_task_struct;
-/*
- * 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.
- *
- * 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 sockets. */
- int fd[2];
- /** the associated task structure */
+ /** The local listening socket. */
+ int fd;
+ /** The associated task structure. */
struct task *task;
};
-/** iterate over all supported audio formats */
+/** Iterate over all supported audio formats. */
#define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++)
/**
rskip; /* receiver start - sss */
int slot_num = get_play_time_slot_num();
struct slot_info *s = slot_num < 0? NULL : &slot[slot_num];
+ bool writer_active = s && s->wns && s->wns[0].btrn;
char *msg;
if (audiod_status == AUDIOD_OFF)
}
/*
* 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.
+ * start. We use the writer start time from the slot info and fall back
+ * to the info from current status items if no writer is active yet.
*/
tmp = &stat_task->server_stream_start;
- if (s && s->wns && s->wns[0].btrn) { /* writer active in this slot */
+ if (writer_active) {
btr_get_node_start(s->wns[0].btrn, &wstime);
if (wstime.tv_sec != 0) { /* writer wrote something */
if (s->server_stream_start.tv_sec == 0) {
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 || wstime.tv_sec == 0) {
+ if (!writer_active) {
struct timeval diff;
tv_diff(now, &sss, &diff);
seconds = diff.tv_sec + stat_task->offset_seconds;
static void parse_config_or_die(void)
{
- int ret, i;
- 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);
- else {
- char *home = para_homedir();
- config_file = 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);
- free(config_file);
- goto err;
- }
- if (ret) {
- audiod_cmdline_parser_config_file(config_file, &conf, ¶ms);
- daemon_set_loglevel(conf.loglevel_arg);
+ 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);
}
- 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;
+ daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
+ n = OPT_GIVEN(USER_ALLOW);
+ if (n == 0)
+ return;
+ 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;
+ }
+ pw = getpwnam(arg);
+ if (!pw) {
+ PARA_EMERG_LOG("invalid username: %s\n", arg);
+ exit(EXIT_FAILURE);
}
+ uid_whitelist[i] = pw->pw_uid;
}
- return;
-err:
- exit(EXIT_FAILURE);
}
static void setup_signal_handling(void)
task_reap(&s->receiver_node->task);
free(s->receiver_node);
s->receiver_node = NULL;
- stat_task->current_audio_format_num = -1;
+ if (audiod_status == AUDIOD_ON)
+ stat_task->current_audio_format_num = -1;
tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000),
&a->restart_barrier);
}
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;
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;
{
long unsigned sec, usec;
- if (stat_task->clock_diff_count && itemnum != SI_CURRENT_TIME)
+ if (stat_task->clock_diff_count && itemnum != SI_current_time)
return 1;
free(stat_item_values[itemnum]);
stat_item_values[itemnum] = para_strdup(buf);
stat_client_write_item(itemnum);
switch (itemnum) {
- case SI_STATUS_FLAGS:
+ 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:
+ case SI_offset:
stat_task->offset_seconds = atoi(buf);
break;
- case SI_SECONDS_TOTAL:
+ case SI_seconds_total:
stat_task->length_seconds = atoi(buf);
break;
- case SI_STREAM_START:
+ case SI_stream_start:
if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) {
stat_task->server_stream_start.tv_sec = sec;
stat_task->server_stream_start.tv_usec = usec;
}
break;
- case SI_CURRENT_TIME:
+ case SI_current_time:
if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) {
struct timeval tv = {sec, usec};
compute_time_diff(&tv);
}
break;
- case SI_FORMAT:
+ case SI_format:
stat_task->current_audio_format_num
= get_audio_format_num(buf);
}
return -E_MISSING_COLON;
*cmd = p + 1;
len = p - txt;
- re = malloc(len + 1);
+ re = para_malloc(len + 1);
strncpy(re, txt, len);
re[len] = '\0';
ret = get_matching_audio_format_nums(re);
const char *cmd;
struct audio_format_info *a;
- for (i = 0; i < conf.writer_given; i++) {
+ 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;
FOR_EACH_AUDIO_FORMAT(i)
afi[i].receiver_num = -1;
- for (i = conf.receiver_given - 1; i >= 0; i--) {
+ 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;
}
/* add "dec" to audio format name */
tmp = make_message("%sdec", audio_formats[i]);
- for (j = 0; filter_get(j); j++)
+ for (j = 1; filter_get(j); j++)
if (!strcmp(tmp, filter_name(j)))
break;
free(tmp);
{
int i, j, ret, af_mask, num_matches;
- for (i = 0; i < conf.filter_given; i++) {
+ for (i = 0; i < OPT_GIVEN(FILTER); i++) {
const char *arg;
- ret = parse_stream_command(conf.filter_arg[i], &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 void init_local_sockets(struct command_task *ct)
+static void init_local_socket(struct command_task *ct)
{
- 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);
- 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)
+ ct->fd = create_local_socket(socket_name);
+ if (ct->fd >= 0)
return;
- PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd[1]));
+ PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd));
exit(EXIT_FAILURE);
}
case SIGINT:
case SIGTERM:
case SIGHUP:
- PARA_NOTICE_LOG("received signal %d\n", signum);
+ PARA_WARNING_LOG("terminating on signal %d\n", signum);
task_notify_all(s, E_AUDIOD_SIGNAL);
return -E_AUDIOD_SIGNAL;
}
static void command_pre_select(struct sched *s, void *context)
{
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);
+ para_fd_set(ct->fd, &s->rfds, &s->max_fileno);
}
static int command_post_select(struct sched *s, void *context)
{
- int ret, i;
+ 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;
- 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;
- }
+ ret = handle_connect(ct->fd, &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;
static void init_command_task(struct command_task *ct)
{
- init_local_sockets(ct); /* doesn't return on errors */
+ init_local_socket(ct); /* doesn't return on errors */
ct->task = task_register(&(struct task_info) {
.name = "command",
unlink(socket_name);
close_stat_pipe();
close_unused_slots();
- audiod_cmdline_parser_free(&conf);
close_stat_clients();
free(uid_whitelist);
}
struct slot_info *sl;
close_unused_slots();
- if (audiod_status != AUDIOD_ON ||
- !(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING))
+ 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())
return;
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) {
client_open(argc, argv, &st->ct, NULL, NULL, st->btrn, s);
set_stat_task_restart_barrier(5);
}
- free(stat_item_values[SI_BASENAME]);
- stat_item_values[SI_BASENAME] = para_strdup(
+ free(stat_item_values[SI_basename]);
+ stat_item_values[SI_basename] = para_strdup(
"no connection to para_server");
- stat_client_write_item(SI_BASENAME);
+ stat_client_write_item(SI_basename);
st->last_status_read = *now;
out:
start_stop_decoders();
{
memset(st, 0, sizeof(struct status_task));
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;
st->btrn = btr_new_node(&(struct btr_node_description)
EMBRACE(.name = "stat"));
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)
-{
- 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);
-}
-
/**
* Lookup the given UID in the whitelist.
*
{
int i;
- if (!conf.user_allow_given)
+ if (!OPT_GIVEN(USER_ALLOW))
return true;
- for (i = 0; i < conf.user_allow_given; i++)
+ 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
*
{
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();
- if (conf.help_given || conf.detailed_help_given)
- print_help_and_die();
- daemon_set_priority(conf.priority_arg);
- daemon_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();
- if (daemon_init_colors_or_die(conf.color_arg, color_arg_auto, color_arg_no,
- conf.logfile_given)) {
- for (i = 0; i < conf.log_color_given; i++)
- daemon_set_log_color_or_die(conf.log_color_arg[i]);
+ 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)));
}
- init_random_seed_or_die();
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();
init_status_task(stat_task);
init_command_task(cmd_task);
- if (conf.daemon_given)
+ if (OPT_GIVEN(DAEMON))
daemonize(false /* parent exits immediately */);
signal_task->task = task_register(&(struct task_info) {
audiod_cleanup();
sched_shutdown(&sched);
signal_shutdown(signal_task);
-
+ 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;