]> git.tuebingen.mpg.de Git - paraslash.git/blobdiff - audiod_command.c
audiod: Fix error checking in init_default_filters().
[paraslash.git] / audiod_command.c
index fe26b663256aaa8083268bee75ac2f2c5fbb5c68..bb54dfab87f7965a18f6cccbe6bc4077c147fa29 100644 (file)
-/*
- * Copyright (C) 2005-2006 Andre Noll <noll@mathematik.tu-darmstadt.de>
- *
- *     This program is free software; you can redistribute it and/or modify
- *     it under the terms of the GNU General Public License as published by
- *     the Free Software Foundation; either version 2 of the License, or
- *     (at your option) any later version.
- *
- *     This program is distributed in the hope that it will be useful,
- *     but WITHOUT ANY WARRANTY; without even the implied warranty of
- *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *     GNU General Public License for more details.
- *
- *     You should have received a copy of the GNU General Public License
- *     along with this program; if not, write to the Free Software
- *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
- */
+/* Copyright (C) 2005 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
 
-/** \file audiod_command.c commands for para_audiod */
+/** \file audiod_command.c Commands for para_audiod. */
 
-#include "para.h"
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
+#include <lopsub.h>
 
-#include "audiod.cmdline.h"
+#include "audiod.lsg.h"
+#include "para.h"
+#include "lsu.h"
+#include "audiod_cmd.lsg.h"
 #include "list.h"
-#include "close_on_fork.h"
 #include "sched.h"
+#include "buffer_tree.h"
 #include "filter.h"
-#include "grab_client.cmdline.h"
 #include "grab_client.h"
-
 #include "error.h"
 #include "audiod.h"
 #include "net.h"
 #include "daemon.h"
 #include "string.h"
+#include "write.h"
 #include "fd.h"
+#include "version.h"
 
-/** defines one command of para_audiod */
-struct audiod_command {
-       /** the name of the command */
-       const char *name;
-       /** pointer to the function that handles the command */
-       int (*handler)(int, int, char**);
-       /**
-        * if the command prefers to handle the full line (rather than the usual
-        * argv[] array), it stores a pointer to the corresponding line handling
-        * function here. In this case, the above \a handler pointer must be NULL.
-        */
-       int (*line_handler)(int, char*);
-       /** one-line description of the command */
-       const char *description;
-       /** summary of the command line options */
-       const char *synopsis;
-       /** the long help text */
-       const char *help;
-};
-static int com_grab(int, char *);
-static int com_cycle(int, int, char **);
-static int com_help(int, int, char **);
-static int com_kill(int, int, char **);
-static int com_off(int, int, char **);
-static int com_on(int, int, char **);
-static int com_sb(int, int, char **);
-static int com_stat(int, int, char **);
-static int com_tasks(int, int, char **);
-static int com_term(int, int, char **);
-static struct audiod_command cmds[] = {
-{
-.name = "cycle",
-.handler = com_cycle,
-.description = "switch to next mode",
-.synopsis = "cycle",
-.help =
-
-"on -> standby -> off -> on\n"
+extern struct sched sched;
+extern char *stat_item_values[NUM_STAT_ITEMS];
 
-},
-{
-.name = "grab",
-.line_handler = com_grab,
-.description = "grab the audio stream",
-.synopsis = "-- grab [grab_options]",
-.help =
-
-"grab ('splice') the audio stream at any position in the filter      \n"
-"chain and send that data back to the client. Try\n"
-"\t para_audioc -- grab -h\n"
-"for the list of available options.\n"
-},
-
-{
-.name = "help",
-.handler = com_help,
-.description = "display command list or help for given command",
-.synopsis = "help [command]",
-.help =
-
-"When I was younger, so much younger than today, I never needed\n"
-"anybody's help in any way. But now these days are gone, I'm not so\n"
-"self assured. Now I find I've changed my mind and opened up the doors.\n"
-"\n"
-"                              -- Beatles: Help\n"
-
-},
-{
-.name = "kill",
-.handler = com_kill,
-.description = "kill an active audiod task",
-.synopsis = "kill task_id [task_id ...]",
-.help =
-
-"call sched_unregister() and the event_handler of the given task(s)\n"
+/** The maximal number of simultaneous connections. */
+#define MAX_STAT_CLIENTS 50
 
-},
-{
-.name = "off",
-.handler = com_off,
-.description = "deactivate para_audiod",
-.synopsis = "off",
-.help =
-
-"Close connection to para_server and stop all decoders.\n"
+/** Pointer to a command handler function. */
+typedef int (*audiod_cmd_handler_t)(int, struct lls_parse_result *);
 
-},
-{
-.name = "on",
-.handler = com_on,
-.description = "activate para_audiod",
-.synopsis = "on",
-.help =
-
-"Establish connection to para_server, retrieve para_server's current\n"
-"status. If playing, start corresponding decoder. Otherwise stop\n"
-"all decoders.\n"
+/** The lopsub user_data pointer. Only the command handler at the moment. */
+struct audiod_command_info {
+       audiod_cmd_handler_t handler; /**< Implementation of the command. */
+};
 
-},
-{
-.name = "sb",
-.handler = com_sb,
-.description = "enter standby mode",
-.synopsis = "sb",
-.help =
+/** Define the user_data pointer as expected by lopsub. */
+#define EXPORT_AUDIOD_CMD_HANDLER(_cmd) \
+       /** Implementation of _cmd. */ \
+       const struct audiod_command_info lsg_audiod_cmd_com_ ## _cmd ## _user_data = { \
+               .handler = com_ ## _cmd \
+       };
+
+/** Flags used for the stat command of para_audiod. */
+enum stat_client_flags {
+       /** Enable parser-friendly output. */
+       SCF_PARSER_FRIENDLY = 1,
+};
 
-"Stop all decoders but leave connection to para_server open.\n"
+/**
+ * Describes a status client of para_audiod.
+ *
+ * There's one such structure per audiod client that sent the 'stat' command.
+ *
+ * A status client is identified by its file descriptor.  para_audiod
+ * keeps a list of connected status clients.
+ */
+struct stat_client {
+       /** The stat client's file descriptor. */
+       int fd;
+       /** Bitmask of those status items the client is interested in. */
+       uint64_t item_mask;
+       /** See \ref stat_client flags. */
+       unsigned flags;
+       /** Its entry in the list of stat clients. */
+       struct list_head node;
+};
 
-},
-{
-.name = "stat",
-.handler = com_stat,
-.description = "print status information",
-.synopsis = "stat [item1 ...]",
-.help =
+static INITIALIZED_LIST_HEAD(client_list);
+static int num_clients;
 
-"Dump given status items (all if none given) to stdout.\n"
+/** The list of all status items used by para_{server,audiod,gui}. */
+const char *status_item_list[] = {STATUS_ITEMS};
 
-},
+static void dump_stat_client_list(void)
 {
-.name = "tasks",
-.handler = com_tasks,
-.description = "list current tasks",
-.synopsis = "tasks",
-.help =
-
-"print the list of task ids together with the status of each task\n"
+       struct stat_client *sc;
 
-},
+       list_for_each_entry(sc, &client_list, node)
+               PARA_INFO_LOG("stat client on fd %d\n", sc->fd);
+}
+/**
+ * Add a status client to the list.
+ *
+ * \param fd The file descriptor of the client.
+ * \param mask Bitfield of status items for this client.
+ * \param parser_friendly Enable parser-friendly output mode.
+ *
+ * Only those status items having the bit set in \a mask will be
+ * sent to the client.
+ *
+ * \return Positive value on success, or -E_TOO_MANY_CLIENTS if
+ * the number of connected clients exceeds #MAX_STAT_CLIENTS.
+ */
+static int stat_client_add(int fd, uint64_t mask, int parser_friendly)
 {
-.name = "term",
-.handler = com_term,
-.description = "terminate audiod",
-.synopsis = "term",
-.help =
+       struct stat_client *new_client;
+       int ret;
 
-"Stop all decoders, shut down connection to para_server and exit.\n"
-
-},
-{
-.name = NULL,
+       if (num_clients >= MAX_STAT_CLIENTS) {
+               PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n",
+                       MAX_STAT_CLIENTS);
+               return -E_TOO_MANY_CLIENTS;
+       }
+       ret = dup(fd);
+       if (ret < 0)
+               return -ERRNO_TO_PARA_ERROR(errno);
+       new_client = para_calloc(sizeof(*new_client));
+       new_client->fd = ret;
+       PARA_INFO_LOG("adding client on fd %d\n", new_client->fd);
+       new_client->item_mask = mask;
+       if (parser_friendly)
+               new_client->flags = SCF_PARSER_FRIENDLY;
+       para_list_add(&new_client->node, &client_list);
+       dump_stat_client_list();
+       num_clients++;
+       return 1;
 }
-};
-
-/** iterate over the array of all audiod commands */
-#define FOR_EACH_COMMAND(c) for (c = 0; cmds[c].name; c++)
 
-static int client_write(int fd, const char *buf)
+static void close_stat_client(struct stat_client *sc)
 {
-       size_t len = strlen(buf);
-       return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1;
+       PARA_INFO_LOG("closing client fd %d\n", sc->fd);
+       close(sc->fd);
+       list_del(&sc->node);
+       free(sc);
+       num_clients--;
 }
 
-static char *get_time_string(struct timeval *newest_stime)
+/**
+ * Empty the status clients list.
+ *
+ * This iterates over the list of connected status clients, closes each client
+ * file descriptor and frees the resources.
+ */
+void close_stat_clients(void)
 {
-       struct timeval diff, adj_stream_start, tmp;
-       int total = 0, use_server_time = 1,
-               length_seconds = stat_task->length_seconds;
-
-       if (!stat_task->playing) {
-               if (length_seconds)
-                       return NULL;
-               return make_message("%s:\n", status_item_list[SI_PLAY_TIME]);
-       }
-       if (audiod_status == AUDIOD_OFF)
-               goto out;
-       if (stat_task->sa_time_diff_sign > 0)
-               tv_diff(&stat_task->server_stream_start, &stat_task->sa_time_diff,
-                       &adj_stream_start);
-       else
-               tv_add(&stat_task->server_stream_start, &stat_task->sa_time_diff,
-                       &adj_stream_start);
-       tmp = adj_stream_start;
-       if (newest_stime && audiod_status == AUDIOD_ON) {
-               tv_diff(newest_stime, &adj_stream_start, &diff);
-               if (tv2ms(&diff) < 5000) {
-                       tmp = *newest_stime;
-                       use_server_time = 0;
-               }
-       }
-       tv_diff(now, &tmp, &diff);
-       total = diff.tv_sec + stat_task->offset_seconds;
-       if (total > length_seconds)
-               total = length_seconds;
-       if (total < 0)
-               total = 0;
-out:
-       return make_message(
-               "%s:%s%d:%02d [%d:%02d] (%d%%/%d:%02d)\n",
-               status_item_list[SI_PLAY_TIME],
-               use_server_time? "~" : "",
-               total / 60,
-               total % 60,
-               (length_seconds - total) / 60,
-               (length_seconds - total) % 60,
-               length_seconds? (total * 100 + length_seconds / 2) /
-                       length_seconds : 0,
-               length_seconds / 60,
-               length_seconds % 60
-       );
-}
+       struct stat_client *sc, *tmp;
 
-__malloc static char *audiod_status_string(void)
-{
-       const char *status = (audiod_status == AUDIOD_ON)?
-               "on" : (audiod_status == AUDIOD_OFF)? "off": "sb";
-       return make_message("%s:%s\n", status_item_list[SI_AUDIOD_STATUS], status);
+       list_for_each_entry_safe(sc, tmp, &client_list, node)
+               close_stat_client(sc);
+       assert(num_clients == 0);
 }
 
-static struct timeval *wstime(void)
+/**
+ * Write a message to all connected status clients.
+ *
+ * \param item_num The number of the status item of \a msg.
+ *
+ * On write errors, remove the status client from the client list and close its
+ * file descriptor.
+ */
+void stat_client_write_item(int item_num)
 {
-       int i;
-       struct timeval *max = NULL;
-       FOR_EACH_SLOT(i) {
-               struct slot_info *s = &slot[i];
-               if (!s->wng)
+       struct stat_client *sc, *tmp;
+       struct para_buffer pb = {.flags = 0};
+       struct para_buffer pfpb = {.flags = PBF_SIZE_PREFIX};
+       const uint64_t one = 1;
+       char *msg = stat_item_values[item_num];
+       struct para_buffer *b;
+
+       list_for_each_entry_safe(sc, tmp, &client_list, node) {
+               int ret;
+
+               if (!((one << item_num) & sc->item_mask))
                        continue;
-               if (max && tv_diff(&s->wstime, max, NULL) <= 0)
+               b = (sc->flags & SCF_PARSER_FRIENDLY)? &pfpb : &pb;
+               if (!b->buf)
+                       WRITE_STATUS_ITEM(b, item_num, "%s\n", msg? msg : "");
+               ret = write(sc->fd, b->buf, b->offset);
+               if (ret == b->offset)
                        continue;
-               max = &s->wstime;
+               /* write error or short write */
+               close_stat_client(sc);
+               dump_stat_client_list();
        }
-       return max;
+       free(pb.buf);
+       free(pfpb.buf);
 }
-__malloc static char *decoder_flags(void)
+
+/**
+ * Check if string is a known status item.
+ *
+ * \param item Buffer containing the text to check.
+ *
+ * \return If \a item is a valid status item, the number of that status item is
+ * returned. Otherwise, this function returns \p -E_UNKNOWN_STAT_ITEM.
+ */
+static int stat_item_valid(const char *item)
 {
        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->wng)
-                       flag += 2;
-               flags[i] = flag;
+       if (!item || !*item) {
+               PARA_ERROR_LOG("%s\n", "no item");
+               return -E_UNKNOWN_STAT_ITEM;
        }
-       flags[MAX_STREAM_SLOTS] = '\0';
-       return make_message("%s:%s\n", status_item_list[SI_DECODER_FLAGS],
-               flags);
+       FOR_EACH_STATUS_ITEM(i)
+               if (!strcmp(status_item_list[i], item))
+                       return i;
+       PARA_ERROR_LOG("invalid stat item: %s\n", item);
+       return -E_UNKNOWN_STAT_ITEM;
 }
 
-static int dump_commands(int fd)
+static int client_write(int fd, const char *buf)
 {
-       char *buf = para_strdup(""), *tmp = NULL;
-       int i;
-       ssize_t ret;
+       size_t len;
 
-       FOR_EACH_COMMAND(i) {
-               tmp = make_message("%s%s\t%s\n", buf, cmds[i].name,
-                       cmds[i].description);
-               free(buf);
-               buf = tmp;
-       }
-       ret = client_write(fd, buf);
-       free(buf);
-       return ret;
+       if (!buf)
+               return 0;
+       len = strlen(buf);
+       return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1;
 }
 
-/*
- * command handlers don't close their fd on errors (ret < 0) so that
- * its caller can send an error message. Otherwise (ret >= 0) it's up
- * to each individual command to close the fd if necessary.
- */
+__malloc static char *audiod_status_string(void)
+{
+       const char *status = (audiod_status == AUDIOD_ON)?
+               "on" : (audiod_status == AUDIOD_OFF)? "off": "sb";
+       return para_strdup(status);
+}
 
-static int com_help(int fd, int argc, char **argv)
+static int com_help(int fd, struct lls_parse_result *lpr)
 {
-       int i, ret;
        char *buf;
-       const char *dflt = "No such command. Available commands:\n";
+       int ret;
+       const struct lls_opt_result *r =
+               lls_opt_result(LSG_AUDIOD_CMD_HELP_OPT_LONG, lpr);
+       bool long_help = lls_opt_given(r);
 
-       if (argc < 2) {
-               ret = dump_commands(fd);
-               goto out;
-       }
-       FOR_EACH_COMMAND(i) {
-               if (strcmp(cmds[i].name, argv[1]))
-                       continue;
-               buf = make_message(
-                       "NAME\n\t%s -- %s\n"
-                       "SYNOPSIS\n\tpara_audioc %s\n"
-                       "DESCRIPTION\n%s\n",
-                       argv[1],
-                       cmds[i].description,
-                       cmds[i].synopsis,
-                       cmds[i].help
-               );
-               ret = client_write(fd, buf);
-               free(buf);
-               goto out;
-       }
-       ret = client_write(fd, dflt);
-       if (ret > 0)
-               ret = dump_commands(fd);
-out:
-       if (ret >= 0)
-               close(fd);
-       return ret;
+       lsu_com_help(long_help, lpr, audiod_cmd_suite, NULL, &buf, NULL);
+       ret = client_write(fd, buf);
+       free(buf);
+       return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(help)
 
-static int com_tasks(int fd, __a_unused int argc, __a_unused char **argv)
+static int com_tasks(int fd, __a_unused struct lls_parse_result *lpr)
 {
-       char *tl = get_task_list();
-       int ret = 1;
-       if (tl)
-               ret = client_write(fd, tl);
-       free(tl);
-       if (ret > 0)
-               close(fd);
-       return ret;
-}
+       int ret;
+       char *tl = get_task_list(&sched);
 
-static int com_kill(int fd, int argc, char **argv)
-{
-       int i, ret = 1;
-       if (argc < 2)
-               return -E_AUDIOD_SYNTAX;
-       for (i = 1; i < argc; i++) {
-               ret = kill_task(argv[i]);
-               if (ret < 0)
-                       break;
-       }
-       if (ret > 0)
-               close(fd);
+       if (!tl) /* no tasks registered yet */
+               return 0;
+       ret = client_write(fd, tl);
+       free(tl);
        return ret;
 }
+EXPORT_AUDIOD_CMD_HANDLER(tasks)
 
-static int com_stat(int fd, __a_unused int argc, __a_unused char **argv)
+static int com_stat(int fd, struct lls_parse_result *lpr)
 {
-       int i, ret;
-       char *buf = NULL;
-       long unsigned mask = ~0LU;
-
-       if (argc > 1) {
-               mask = 0;
-               for (i = 1; i < argc; i++) {
-                       ret = stat_item_valid(argv[i]);
-                       if (ret < 0)
-                               return ret;
-                       mask |= (1 << ret);
-               }
-       }
-       PARA_INFO_LOG("mask: 0x%lx\n", mask);
-       if (mask & (1 << SI_PLAY_TIME)) {
-               struct timeval *t = wstime();
-               char *ts = get_time_string(t);
-               if (ts) {
-                       ret = client_write(fd, ts);
-                       if (ret < 0)
-                               goto out;
-                       free(ts);
-               }
-       }
-       if (mask & (1 << SI_AUDIOD_UPTIME)) {
-               char *tmp, *us = uptime_str();
-               tmp = make_message("%s:%s\n",
-                       status_item_list[SI_AUDIOD_UPTIME], us);
-               free(us);
-               ret = client_write(fd, tmp);
-               if (ret < 0)
-                       goto out;
-               free(tmp);
-       }
-       if (mask & (1 << SI_AUDIOD_STATUS)) {
-               char *s = audiod_status_string();
-               ret = client_write(fd, s);
-               if (ret < 0)
-                       goto out;
-               free(s);
+       int i, ret, parser_friendly = 0;
+       uint64_t mask = 0;
+       const uint64_t one = 1;
+       struct para_buffer b = {.flags = 0};
+       const struct lls_opt_result *r;
+       unsigned num_inputs;
+
+       ret = mark_fd_nonblocking(fd);
+       if (ret < 0)
+               return ret;
+       r = lls_opt_result(LSG_AUDIOD_CMD_STAT_OPT_PARSER_FRIENDLY, lpr);
+       if (lls_opt_given(r) > 0) {
+               parser_friendly = 1;
+               b.flags = PBF_SIZE_PREFIX;
        }
-       if (mask & (1 << SI_DECODER_FLAGS)) {
-               char *df =decoder_flags();
-               ret = client_write(fd, df);
+       num_inputs = lls_num_inputs(lpr);
+       if (num_inputs == 0)
+               mask--; /* set all bits */
+       for (i = 0; i < num_inputs; i++) {
+               ret = stat_item_valid(lls_input(i, lpr));
                if (ret < 0)
-                       goto out;
-               free(df);
+                       return ret;
+               mask |= (one << ret);
        }
-
-       for (i = 0; i < NUM_STAT_ITEMS; i++) {
-               char *tmp, *v;
-               if (!((1 << i) & mask))
+       PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask);
+       FOR_EACH_STATUS_ITEM(i) {
+               char *item = stat_item_values[i];
+               if (!((one << i) & mask))
                        continue;
-               v = stat_task->stat_item_values[i];
-               tmp = make_message("%s%s%s", buf? buf: "",
-                       v? v : "", v? "\n" : "");
-               free(buf);
-               buf = tmp;
+               WRITE_STATUS_ITEM(&b, i, "%s\n", item? item : "");
        }
-       ret = client_write(fd, buf);
-out:
-       if (ret > 0)
-               ret = stat_client_add(fd, mask);
-       free(buf);
-       return ret;
+       ret = client_write(fd, b.buf);
+       if (ret >= 0)
+               ret = stat_client_add(fd, mask, parser_friendly);
+       free(b.buf);
+       return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(stat)
 
-static struct filter_node *find_filter_node(int slot_num, int format, int filternum)
+static int com_grab(int fd, struct lls_parse_result *lpr)
 {
-       struct filter_node *fn;
-       int i, j;
-
-       FOR_EACH_SLOT(i) {
-               struct slot_info *s = &slot[i];
-               if (s->format < 0 || !s->fc)
-                       continue;
-               if (slot_num >= 0 && slot_num != i)
-                       continue;
-               if (format >= 0 && s->format != format)
-                       continue;
-               if (num_filters(i) < filternum)
-                       continue;
-               /* success */
-               j = 1;
-               list_for_each_entry(fn, &s->fc->filters, node)
-                       if (filternum <= 0 || j++ == filternum)
-                               break;
-               return fn;
-       }
-       return NULL;
+       int ret = grab_client_new(fd, lpr, &sched);
+       return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(grab)
 
-static int com_grab(int fd, char *cmdline)
+static int com_term(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
-       struct grab_client *gc;
-       struct filter_node *fn;
-       int i, err;
-       char *msg;
-
-       gc = grab_client_new(fd, cmdline, &err);
-       if (!gc)
-               goto err_out;
-       fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg);
-       if (fn)
-               activate_grab_client(gc, fn);
-       return 1;
-err_out:
-       if (err != -E_GC_HELP_GIVEN && err != -E_GC_VERSION_GIVEN)
-               return err;
-       if (err == -E_GC_HELP_GIVEN) {
-               msg = make_message("%s\n\n", grab_client_args_info_usage);
-               for (i = 0; grab_client_args_info_help[i]; i++) {
-                       char *tmp = make_message("%s%s\n", msg,
-                               grab_client_args_info_help[i]);
-                       free(msg);
-                       msg = tmp;
-               }
-       } else
-               msg = make_message("%s %s\n",
-                       GRAB_CLIENT_CMDLINE_PARSER_PACKAGE,
-                       GRAB_CLIENT_CMDLINE_PARSER_VERSION);
-       err = client_write(fd, msg);
-       free(msg);
-       if (err < 0)
-               return err;
-       close(fd);
-       return 1;
-}
-
-static int __noreturn com_term(int fd, __a_unused int argc, __a_unused char **argv)
-{
-       close(fd);
-       clean_exit(EXIT_SUCCESS, "terminating on user request");
+       return -E_AUDIOD_TERM;
 }
+EXPORT_AUDIOD_CMD_HANDLER(term)
 
-static int com_on(int fd, __a_unused int argc, __a_unused char **argv)
+static int com_on(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        audiod_status = AUDIOD_ON;
-       close(fd);
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(on)
 
-static int com_off(int fd, __a_unused int argc, __a_unused char **argv)
+static int com_off(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        audiod_status = AUDIOD_OFF;
-       close(fd);
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(off)
 
-static int com_sb(int fd, __a_unused int argc, __a_unused char **argv)
+static int com_sb(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        audiod_status = AUDIOD_STANDBY;
-       close(fd);
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(sb)
 
-static int com_cycle(int fd, int argc, char **argv)
+static int com_cycle(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        switch (audiod_status) {
-               case  AUDIOD_ON:
-                       return com_sb(fd, argc, argv);
-                       break;
-               case  AUDIOD_OFF:
-                       return com_on(fd, argc, argv);
-                       break;
-               case  AUDIOD_STANDBY:
-                       return com_off(fd, argc, argv);
-                       break;
+               case  AUDIOD_ON: audiod_status = AUDIOD_STANDBY; break;
+               case  AUDIOD_OFF: audiod_status = AUDIOD_ON; break;
+               case  AUDIOD_STANDBY: audiod_status = AUDIOD_OFF; break;
        }
-       close(fd);
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(cycle)
 
-static int check_perms(uid_t uid)
+static int com_version(int fd, struct lls_parse_result *lpr)
 {
-       int i;
+       int ret;
+       char *msg;
+       const struct lls_opt_result *r_v;
 
-       if (!conf.user_allow_given)
-               return 1;
-       for (i = 0; i < conf.user_allow_given; i++)
-               if (uid == conf.user_allow_arg[i])
-                       return 1;
-       return -E_UCRED_PERM;
+       r_v = lls_opt_result(LSG_AUDIOD_CMD_VERSION_OPT_VERBOSE, lpr);
+       if (lls_opt_given(r_v))
+               msg = make_message("%s", version_text("audiod"));
+       else
+               msg = make_message("%s\n", version_single_line("audiod"));
+       ret = client_write(fd, msg);
+       free(msg);
+       return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(version)
 
-int handle_connect(int accept_fd)
+/**
+ * Handle arriving connections on the local socket.
+ *
+ * \param accept_fd The fd to accept connections on.
+ * \param rfds If \a accept_fd is not set in \a rfds, do nothing.
+ *
+ * This is called in each iteration of the select loop. If there is an incoming
+ * connection on \a accept_fd, this function reads the command sent by the peer,
+ * checks the connecting user's permissions by using unix socket credentials
+ * (if supported by the OS) and calls the corresponding command handler if
+ * permissions are OK.
+ *
+ * \return Positive on success, negative on errors, zero if there was no
+ * connection to accept.
+ *
+ * \sa \ref para_accept(), \ref recv_cred_buffer().
+ * */
+int handle_connect(int accept_fd, fd_set *rfds)
 {
-       int i, argc, ret, clifd = -1;
-       char *cmd = NULL, *p, *buf = para_calloc(MAXLINE), **argv = NULL;
+       int argc, ret, clifd;
+       char buf[MAXLINE], **argv = NULL;
        struct sockaddr_un unix_addr;
-
-       ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un));
+       uid_t uid;
+       const struct lls_command *cmd;
+       struct lls_parse_result *lpr;
+       char *errctx = NULL;
+       const struct audiod_command_info *aci;
+
+       ret = para_accept(accept_fd, rfds, &unix_addr, sizeof(struct sockaddr_un), &clifd);
+       if (ret <= 0)
+               return ret;
+       ret = recv_cred_buffer(clifd, buf, sizeof(buf) - 1);
        if (ret < 0)
                goto out;
-       clifd = ret;
-       ret = recv_cred_buffer(clifd, buf, MAXLINE - 1);
-       if (ret < 0)
+       uid = ret;
+       PARA_INFO_LOG("connection from UID %d, buf: %s\n", ret, buf);
+       ret = -E_UCRED_PERM;
+       if (!uid_is_whitelisted(uid))
+               goto out;
+       ret = create_argv(buf, "\n", &argv);
+       if (ret <= 0)
                goto out;
-       PARA_INFO_LOG("connection from user %i, buf: %s\n",  ret, buf);
-       ret = check_perms(ret);
+       argc = ret;
+       ret = lls(lls_lookup_subcmd(argv[0], audiod_cmd_suite, &errctx));
        if (ret < 0)
                goto out;
-       ret = -E_INVALID_AUDIOD_CMD;
-       cmd = para_strdup(buf);
-       p = strchr(cmd, '\n');
-       if (!p)
-               p = "";
-       else {
-               *p = '\0';
-               p++;
-       }
-       for (i = 0; cmds[i].name; i++) {
-               int j;
-               if (strcmp(cmds[i].name, cmd))
-                       continue;
-               if (cmds[i].handler) {
-                       argc = split_args(buf, &argv, "\n");
-                       PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc);
-                       ret = cmds[i].handler(clifd, argc, argv);
-                       goto out;
-               }
-               for (j = 0; p[j]; j++)
-                       if (p[j] == '\n')
-                               p[j] = ' ';
-               PARA_INFO_LOG("cmd: %s, options: %s\n", cmd, p);
-               ret = cmds[i].line_handler(clifd, p);
+       cmd = lls_cmd(ret, audiod_cmd_suite);
+       ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
+       if (ret < 0)
                goto out;
-       }
-       ret = -E_INVALID_AUDIOD_CMD;
+       aci = lls_user_data(cmd);
+       ret = aci->handler(clifd, lpr);
+       lls_free_parse_result(lpr, cmd);
 out:
-       free(cmd);
-       free(buf);
-       free(argv);
-       if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) {
-               char *tmp = make_message("%s\n", PARA_STRERROR(-ret));
+       free_argv(argv);
+       if (ret < 0 && ret != -E_CLIENT_WRITE) {
+               char *tmp;
+               if (errctx) {
+                       tmp = make_message("%s\n", errctx);
+                       free(errctx);
+                       client_write(clifd, tmp);
+                       free(tmp);
+               }
+               tmp = make_message("%s\n", para_strerror(-ret));
                client_write(clifd, tmp);
                free(tmp);
-               close(clifd);
        }
+       close(clifd);
        return ret;
 }
 
-void audiod_status_dump(void)
+/**
+ * Send the current audiod status to all connected stat clients.
+ *
+ * \param force Whether to write unchanged items.
+ */
+void audiod_status_dump(bool force)
 {
-       static char *p_ts, *p_us, *p_as, *p_df;
-       struct timeval *t = wstime();
-       char *us, *tmp = get_time_string(t);
-
-       if (tmp && (!p_ts || strcmp(tmp, p_ts)))
-               stat_client_write(tmp, SI_PLAY_TIME);
-       free(p_ts);
-       p_ts = tmp;
-
-       us = uptime_str();
-       tmp = make_message("%s:%s\n", status_item_list[SI_AUDIOD_UPTIME], us);
-       free(us);
-       if (!p_us || strcmp(p_us, tmp))
-               stat_client_write(tmp, SI_AUDIOD_UPTIME);
-       free(p_us);
-       p_us = tmp;
-
-       tmp = audiod_status_string();
-       if (!p_as || strcmp(p_as, tmp))
-               stat_client_write(tmp, SI_AUDIOD_STATUS);
-       free(p_as);
-       p_as = tmp;
-
-       tmp = decoder_flags();
-       if (!p_df || strcmp(p_df, tmp))
-               stat_client_write(tmp, SI_DECODER_FLAGS);
-       free(p_df);
-       p_df = tmp;
+       char *old, *new;
+
+       old = stat_item_values[SI_play_time];
+       new = get_time_string();
+       if (new) {
+               if (force || !old || strcmp(old, new)) {
+                       free(old);
+                       stat_item_values[SI_play_time] = new;
+                       stat_client_write_item(SI_play_time);
+               } else
+                       free(new);
+       }
+
+       new = daemon_get_uptime_str(now);
+       old = stat_item_values[SI_audiod_uptime];
+       if (force || !old || strcmp(old, new)) {
+               free(old);
+               stat_item_values[SI_audiod_uptime] = new;
+               stat_client_write_item(SI_audiod_uptime);
+       } else
+               free(new);
+
+       old = stat_item_values[SI_audiod_status];
+       new = audiod_status_string();
+       if (force || !old || strcmp(old, new)) {
+               free(old);
+               stat_item_values[SI_audiod_status] = new;
+               stat_client_write_item(SI_audiod_status);
+       } else
+               free(new);
+
+       old = stat_item_values[SI_decoder_flags];
+       new = audiod_get_decoder_flags();
+       if (force || !old || strcmp(old, new)) {
+               free(old);
+               stat_item_values[SI_decoder_flags] = new;
+               stat_client_write_item(SI_decoder_flags);
+       } else
+               free(new);
 }
 
+/**
+ * Flush and send all status items.
+ *
+ * Send to  each connected client the full status item list
+ * with empty values.
+ */
+void clear_and_dump_items(void)
+{
+       int i;
+
+       FOR_EACH_STATUS_ITEM(i) {
+               free(stat_item_values[i]);
+               stat_item_values[i] = NULL;
+               stat_client_write_item(i);
+       }
+}