play: Convert com_quit() to lopsub.
[paraslash.git] / audiod.c
index f12dbc1b999a0892abdcea5791692f1f5dfab3c3..fa4019160bf8148a8503fa856c5dac3f9103b260 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -1,5 +1,5 @@
 /*
- * 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.
  */
@@ -14,6 +14,8 @@
 #include <sys/un.h>
 #include <netdb.h>
 #include <signal.h>
+#include <pwd.h>
+#include <lopsub.h>
 
 #include "para.h"
 #include "error.h"
 #include "signal.h"
 #include "version.h"
 
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
+
 __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};
 
@@ -68,6 +71,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 +120,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. */
@@ -143,15 +166,17 @@ int audiod_status = AUDIOD_ON;
  * the gengetopt args_info struct that holds information on all command line
  * arguments
  */
-struct audiod_args_info conf;
+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_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 +184,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 +227,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 +272,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 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);
@@ -275,7 +353,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;
@@ -287,7 +365,8 @@ char *get_time_string(int slot_num)
        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
@@ -314,20 +393,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 +415,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 +423,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);
@@ -391,8 +479,10 @@ static void close_receiver(int slot_num)
                audio_formats[s->format], slot_num);
        a->receiver->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);
 }
@@ -407,6 +497,7 @@ static void writer_cleanup(struct writer_node *wn)
        PARA_INFO_LOG("closing %s\n", writer_names[wn->writer_num]);
        w->close(wn);
        btr_remove_node(&wn->btrn);
+       task_reap(&wn->task);
 }
 
 static void close_writers(struct slot_info *s)
@@ -436,14 +527,15 @@ 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);
+               task_reap(&fn->task);
        }
        free(s->fns);
        s->fns = NULL;
@@ -459,7 +551,7 @@ static void notify_receivers(int error)
                        continue;
                if (!s->receiver_node)
                        continue;
-               task_notify(&s->receiver_node->task, error);
+               task_notify(s->receiver_node->task, error);
        }
 }
 
@@ -497,22 +589,27 @@ static void open_filters(struct slot_info *s)
        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 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->btrn = btr_new_node(&(struct btr_node_description)
                        EMBRACE(.name = f->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)", f->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));
        }
 }
 
@@ -566,10 +663,12 @@ static int open_receiver(int 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);
+       rn->task = task_register(&(struct task_info) {
+               .name = r->name,
+               .pre_select = r->pre_select,
+               .post_select = r->post_select,
+               .context = rn,
+       }, &sched);
        return slot_num;
 }
 
@@ -584,7 +683,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;
@@ -611,7 +710,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)
@@ -647,7 +746,7 @@ static bool must_start_decoder(void)
        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;
@@ -655,8 +754,6 @@ static unsigned compute_time_diff(const struct timeval *status_time)
        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);
@@ -669,7 +766,7 @@ static unsigned compute_time_diff(const struct timeval *status_time)
        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++;
@@ -686,7 +783,6 @@ static unsigned compute_time_diff(const struct timeval *status_time)
        );
 out:
        stat_task->sa_time_diff_sign = sa_time_diff_sign;
-       return count;
 }
 
 static int update_item(int itemnum, char *buf)
@@ -765,7 +861,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;
 }
 
@@ -900,20 +996,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;
@@ -964,11 +1060,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 {
@@ -980,70 +1073,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,
-               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)
-{
-       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)
+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 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 */
@@ -1060,8 +1153,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;
@@ -1070,17 +1163,21 @@ 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;
+       task_reap(&stat_task->ct->task);
        client_close(stat_task->ct);
        stat_task->ct = NULL;
        clear_and_dump_items();
@@ -1106,17 +1203,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,37 +1233,32 @@ 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)
 {
-       int i;
-
        if (socket_name)
                unlink(socket_name);
        close_stat_pipe();
-       FOR_EACH_SLOT(i)
-               close_slot(i);
+       close_unused_slots();
        audiod_cmdline_parser_free(&conf);
        close_stat_clients();
-       PARA_EMERG_LOG("%s\n", msg);
-       exit(status);
+       free(uid_whitelist);
 }
 
 /*
@@ -1177,7 +1269,6 @@ static void start_stop_decoders(void)
 {
        int ret;
        struct slot_info *sl;
-       struct audio_format_info *a;
 
        close_unused_slots();
        if (audiod_status != AUDIOD_ON ||
@@ -1191,17 +1282,16 @@ static void start_stop_decoders(void)
                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())
@@ -1231,15 +1321,19 @@ 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) {
-                       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();
@@ -1249,7 +1343,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) {
@@ -1262,14 +1355,14 @@ static int status_post_select(struct sched *s, struct task *t)
                        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) {
@@ -1287,7 +1380,7 @@ static int status_post_select(struct sched *s, struct task *t)
        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);
@@ -1311,14 +1404,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)
@@ -1354,16 +1451,27 @@ __noreturn static void print_help_and_die(void)
        exit(0);
 }
 
-static void init_colors_or_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)
 {
        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]);
+       if (!conf.user_allow_given)
+               return true;
+       for (i = 0; i < conf.user_allow_given; i++)
+               if (uid == uid_whitelist[i])
+                       return true;
+       return false;
 }
 
 /**
@@ -1398,9 +1506,14 @@ 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();
+       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]);
+       }
        init_random_seed_or_die();
        daemon_set_flag(DF_LOG_TIME);
        daemon_set_flag(DF_LOG_HOSTNAME);
@@ -1416,13 +1529,12 @@ 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);
        setup_signal_handling();
-       signal_setup_default(sig_task);
 
        init_status_task(stat_task);
        init_command_task(cmd_task);
@@ -1430,13 +1542,21 @@ int main(int argc, char *argv[])
        if (conf.daemon_given)
                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;
+       if (ret < 0)
+               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
 }