gcrypt: Some trivial fixes.
[paraslash.git] / audiod.c
index ee1364433e77de965e715fe59692040e6e590989..1f226265b0c9c79593f255bba4805cfdb8a53e9a 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005-2014 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
@@ -68,6 +68,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,11 +117,7 @@ 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;
@@ -148,7 +168,7 @@ 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;
 
@@ -159,14 +179,22 @@ 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;
 };
@@ -194,6 +222,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,28 +267,44 @@ 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;
        struct timeval *tmp, sum, sss, /* server stream start */
@@ -239,6 +312,7 @@ char *get_time_string(int slot_num)
                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;
 
@@ -314,17 +388,6 @@ 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;
@@ -362,8 +425,7 @@ err:
 
 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 +456,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);
 }
@@ -970,11 +1033,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 +1046,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 = task_context(t);
-       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 = task_context(t);
+       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 */
@@ -1059,8 +1126,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,7 +1136,7 @@ dump:
 
 static void init_command_task(struct command_task *ct)
 {
-       ct->fd = audiod_get_socket(); /* doesn't return on errors */
+       init_local_sockets(ct); /* doesn't return on errors */
 
        ct->task = task_register(&(struct task_info) {
                .name = "command",
@@ -1083,8 +1150,8 @@ 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;
@@ -1145,19 +1212,13 @@ static void close_unused_slots(void)
                        close_slot(i);
 }
 
-/**
- * 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);
@@ -1165,8 +1226,6 @@ 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);
 }
 
 /*
@@ -1196,9 +1255,9 @@ static void start_stop_decoders(void)
        btr_log_tree(sl->receiver_node->btrn, LL_NOTICE);
 }
 
-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 = task_context(t);
+       struct status_task *st = context;
        int i, ret, cafn = stat_task->current_audio_format_num;
 
        if (must_start_decoder())
@@ -1228,10 +1287,14 @@ 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 = task_context(t);
+       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;
@@ -1246,7 +1309,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) {
@@ -1355,18 +1417,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
  *
@@ -1399,9 +1449,10 @@ 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_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);
@@ -1417,8 +1468,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("para_audiod");
+       daemon_set_start_time();
        set_initial_status();
        FOR_EACH_SLOT(i)
                clear_slot(i);
@@ -1430,18 +1481,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);
 
        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;
 }