Merge branch 'refs/heads/t/si_conversion'
[paraslash.git] / audiod.c
index 40f02f74907c1b1b60778acb4a742c018fb51014..42f4c0a3be06b5aad9a00cf6a351f8b4287d0af7 100644 (file)
--- a/audiod.c
+++ b/audiod.c
 #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
  * 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. */
@@ -130,44 +165,32 @@ struct status_task {
 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
- */
-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
+ * 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
- *
- * \sa struct task, struct sched
- */
 struct command_task {
-       /** the local listening socket */
+       /** The local listening socket. */
        int fd;
-       /** the associated task structure */
+       /** 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++)
 
 /**
@@ -190,6 +213,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;
@@ -206,55 +258,72 @@ static int get_matching_audio_format_nums(const char *re)
        return ret;
 }
 
+static int get_play_time_slot_num(void)
+{
+       int i, oldest_slot = -1;
+       struct timeval oldest_wstime = {0, 0};
+
+       FOR_EACH_SLOT(i) {
+               struct slot_info *s = &slot[i];
+               struct timeval wstime;
+               if (!s->wns || !s->wns[0].btrn)
+                       continue;
+               btr_get_node_start(s->wns[0].btrn, &wstime);
+               if (oldest_slot >= 0 && tv_diff(&wstime, &oldest_wstime, NULL) > 0)
+                       continue;
+               oldest_wstime = wstime;
+               oldest_slot = i;
+       }
+       return oldest_slot;
+}
+
 /**
- * Compute the play time based on information of the given slot.
- *
- * \param slot_num The slot number (negative means: no slot).
+ * Compute the play time based on information of the current slot.
  *
  * This computes a string of the form "0:07 [3:33] (3%/3:40)" using information
  * from the status items received from para_server and the start time of the
- * (first) writer of the given slot.
+ * (first) writer of the current slot.
  *
  * It has to take into account that the stream was probably not started at
  * the beginning of the file, that the clock between the server and the client
  * host may differ and that playback of the stream was delayed, e.g. because
- * the prebuffer filter is used in the filter configuration of the given slot.
+ * the prebuffer filter is used in the filter configuration.
  *
- * If no writer is active in the given slot, or \a slot_num is negative
- * (indicating that para_audiod runs in standby mode), an approximation based
- * only on the status items is computed and the returned string is prefixed
- * with "~".
+ * If no writer is active, for example because para_audiod runs in standby
+ * mode, an approximation based only on the status items is computed and the
+ * returned string is prefixed with "~".
  *
  * \return A string that must be freed by the caller.
  */
-char *get_time_string(int slot_num)
+char *get_time_string(void)
 {
-       int ret, seconds = 0, length;
+       int ret, seconds = 0, length = stat_task->length_seconds;
        struct timeval *tmp, sum, sss, /* server stream start */
                rstime, /* receiver start time */
                wstime, /* writer start time */
                wtime, /* now - writer start */
                rskip; /* receiver start - sss */
+       int slot_num = get_play_time_slot_num();
        struct slot_info *s = slot_num < 0? NULL : &slot[slot_num];
+       bool writer_active = s && s->wns && s->wns[0].btrn;
        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.
+        * 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.
         */
-       length = stat_task->length_seconds;
        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) {
@@ -271,7 +340,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 (!writer_active) {
                struct timeval diff;
                tv_diff(now, &sss, &diff);
                seconds = diff.tv_sec + stat_task->offset_seconds;
@@ -283,7 +352,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
@@ -313,42 +383,86 @@ empty:
 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, &params);
-               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);
@@ -374,7 +488,7 @@ static void close_receiver(int slot_num)
        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);
@@ -386,13 +500,10 @@ static void close_receiver(int slot_num)
 
 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);
 }
@@ -424,11 +535,11 @@ static void close_filters(struct slot_info *s)
                return;
        for (i = a->num_filters - 1; i >= 0; i--) {
                struct filter_node *fn = s->fns + i;
-               struct filter *f;
+               const struct filter *f;
 
                if (!fn)
                        continue;
-               f = filters + fn->filter_num;
+               f = filter_get(fn->filter_num);
                if (f->close)
                        f->close(fn);
                btr_remove_node(&fn->btrn);
@@ -487,16 +598,20 @@ static void open_filters(struct slot_info *s)
        parent = s->receiver_node->btrn;
        for (i = 0; i < nf; i++) {
                char buf[20];
-               struct filter *f = filters + a->filter_nums[i];
+               const 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->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);
-               sprintf(buf, "%s (slot %d)", f->name, (int)(s - slot));
+               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,
@@ -505,7 +620,7 @@ static void open_filters(struct slot_info *s)
                }, &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));
+                       audio_formats[s->format], i,  nf, name, (int)(s - slot));
        }
 }
 
@@ -521,11 +636,11 @@ static void open_writers(struct slot_info *s)
                * 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));
        }
 }
 
@@ -535,7 +650,8 @@ static int open_receiver(int format)
        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);
@@ -545,11 +661,12 @@ static int open_receiver(int format)
        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) {
+               PARA_ERROR_LOG("could not open %s receiver\n", name);
                btr_remove_node(&rn->btrn);
                free(rn);
                return ret;
@@ -558,9 +675,9 @@ static int open_receiver(int format)
        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);
+               audio_formats[format], name, slot_num);
        rn->task = task_register(&(struct task_info) {
-               .name = r->name,
+               .name = name,
                .pre_select = r->pre_select,
                .post_select = r->post_select,
                .context = rn,
@@ -662,7 +779,7 @@ static void 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++;
@@ -685,45 +802,45 @@ static int update_item(int itemnum, char *buf)
 {
        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 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, ':');
@@ -740,38 +857,41 @@ static int parse_stream_command(const char *txt, char **cmd)
        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;
@@ -779,47 +899,45 @@ static int parse_writer_args(void)
                        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;
@@ -833,37 +951,27 @@ static int parse_receiver_args(void)
                         * 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;
 }
 
@@ -873,6 +981,7 @@ static int init_default_filters(void)
 
        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;
 
@@ -882,8 +991,7 @@ static int init_default_filters(void)
                 * 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);
@@ -892,20 +1000,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 = 1; 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;
@@ -915,9 +1023,10 @@ static int parse_filter_args(void)
 {
        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;
@@ -932,7 +1041,7 @@ static int parse_filter_args(void)
                }
                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:
@@ -956,12 +1065,10 @@ static int parse_stream_args(void)
 }
 
 /* does not unlink socket on errors */
-static int audiod_get_socket(void)
+static void init_local_socket(struct command_task *ct)
 {
-       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",
@@ -969,32 +1076,15 @@ static int audiod_get_socket(void)
                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,
-               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 = create_local_socket(socket_name);
+       if (ct->fd >= 0)
+               return;
+       PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd));
        exit(EXIT_FAILURE);
 }
 
-static void signal_pre_select(struct sched *s, void *context)
-{
-       struct signal_task *st = context;
-       para_fd_set(st->fd, &s->rfds, &s->max_fileno);
-}
-
 static int signal_post_select(struct sched *s, void *context)
 {
        struct signal_task *st = context;
@@ -1027,7 +1117,7 @@ static int command_post_select(struct sched *s, void *context)
        struct command_task *ct = context;
        static struct timeval last_status_dump;
        struct timeval tmp, delay;
-       bool force = true;
+       bool force = false;
 
        ret = task_get_notification(ct->task);
        if (ret < 0)
@@ -1040,6 +1130,8 @@ static int command_post_select(struct sched *s, void *context)
                        return ret;
                }
        } else if (ret > 0)
+               force = true;
+       if (force == true)
                goto dump;
 
        /* if last status dump was less than 500ms ago, do nothing */
@@ -1056,8 +1148,8 @@ static int command_post_select(struct sched *s, void *context)
        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;
@@ -1066,7 +1158,7 @@ dump:
 
 static void init_command_task(struct command_task *ct)
 {
-       ct->fd = audiod_get_socket(); /* doesn't return on errors */
+       init_local_socket(ct); /* doesn't return on errors */
 
        ct->task = task_register(&(struct task_info) {
                .name = "command",
@@ -1136,10 +1228,15 @@ 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);
 }
 
 /*
@@ -1154,8 +1251,8 @@ static void audiod_cleanup(void)
                unlink(socket_name);
        close_stat_pipe();
        close_unused_slots();
-       audiod_cmdline_parser_free(&conf);
        close_stat_clients();
+       free(uid_whitelist);
 }
 
 /*
@@ -1183,6 +1280,7 @@ static void start_stop_decoders(void)
        open_writers(sl);
        activate_grab_clients(&sched);
        btr_log_tree(sl->receiver_node->btrn, LL_NOTICE);
+       audiod_status_dump(true);
 }
 
 static void status_pre_select(struct sched *s, void *context)
@@ -1233,7 +1331,7 @@ static int status_post_select(struct sched *s, void *context)
                        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) {
@@ -1276,7 +1374,7 @@ static int status_post_select(struct sched *s, void *context)
        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);
@@ -1287,10 +1385,10 @@ static int status_post_select(struct sched *s, void *context)
                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();
@@ -1301,7 +1399,7 @@ static void init_status_task(struct status_task *st)
 {
        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"));
@@ -1317,34 +1415,60 @@ static void init_status_task(struct status_task *st)
 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 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);
 }
 
 /**
@@ -1361,36 +1485,34 @@ int main(int argc, char *argv[])
 {
        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, &params);
-       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();
-       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();
-       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_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();
@@ -1398,7 +1520,7 @@ int main(int argc, char *argv[])
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
                exit(EXIT_FAILURE);
        }
-       daemon_log_welcome("para_audiod");
+       daemon_log_welcome("audiod");
        daemon_set_start_time();
        set_initial_status();
        FOR_EACH_SLOT(i)
@@ -1408,14 +1530,14 @@ int main(int argc, char *argv[])
        init_status_task(stat_task);
        init_command_task(cmd_task);
 
-       if (conf.daemon_given)
+       if (OPT_GIVEN(DAEMON))
                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;
@@ -1423,7 +1545,12 @@ int main(int argc, char *argv[])
        ret = schedule(&sched);
        audiod_cleanup();
        sched_shutdown(&sched);
+       signal_shutdown(signal_task);
 
+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;