Convert audiod commands to lopsub.
authorAndre Noll <maan@tuebingen.mpg.de>
Sat, 7 May 2016 08:59:01 +0000 (10:59 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 26 Mar 2017 09:02:28 +0000 (11:02 +0200)
The four command lists (server, afs, audiod, play) and all executables
will be converted to the long option parser library (lopsub). This
first patch converts the audiod commands (on, off, cycle...) and adds
the necessary infrastructure to the build system. The option parser
for para_audiod is still generated by gengetopt and will be converted
in a subsequent patch.

The build system is updated to include an autoconf test which
checks for the lopsub library and the lopsubgen executable. If the
check fails, it prints instructions on how to download the lopsub
package. Moreover, a section on lopsub is added to the INSTALL file
and the library is listed as a required tool in the manual.

The options and help texts of all audiod commands are moved from
audiod.cmd to the new file audiod_cmd.suite.m4. Until all command
lists are converted, man_util.bash needs an ugly hack to deal with
the two kinds of files.

The help texts have been reworked slightly, but no syntactical
changes were performed. However, one side effect of the change is
that options to audiod commands now accept short and long options,
and that short options may be combined in the usual way.

The error subsystem of paraslash is extended to treat lopsub errors
analogous to errors from the osl libary: we reserve a new bit for
error codes returned from lopsub library functions and a lls() wrapper
function that must be used for all lopsub functions which return a
lopsub error code on failure. The E_INVALID_AUDIOD_CMD error code
can be removed since invalid commands are now detected by the lopsub
library, which returns its own error code in this case.

As a result of the conversion, struct audiod_command can be removed.
Command handlers now take a pointer to a lopsub parse result instead
of the (argc, argv) pair.

The patch also changes the completers for audiod commands in
audioc.c. to use the information in the generated audioc_cmd.lsg.h
header file instead of duplicating this information.

With the patch applied, para_audiod and para_audioc need to be linked
with -llopsub.

We still need to include ggo.h from audiod_command.c until receivers,
filters and writers have been converted as well.

15 files changed:
INSTALL
Makefile.in
Makefile.real
audioc.c
audiod.c
audiod.cmd
audiod_command.c
configure.ac
error.h
grab_client.c
grab_client.h
m4/lls/audiod_cmd.suite.m4 [new file with mode: 0644]
m4/lls/makefile [new file with mode: 0644]
man_util.bash
web/manual.md

diff --git a/INSTALL b/INSTALL
index 85b4fab..d0e8a79 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -1,5 +1,11 @@
 Any knowledge of how to work with mouse and icons is not required.
 
+Installing lopsub
+~~~~~~~~~~~~~~~~~
+       git clone git://git.tuebingen.mpg.de/lopsub
+       cd lopsub && make && sudo make install
+       (see http://people.tuebingen.mpg.de/maan/lopsub/)
+
 Installing osl
 ~~~~~~~~~~~~~~
        git clone git://git.tuebingen.mpg.de/osl
index ec55c8e..986877b 100644 (file)
@@ -39,6 +39,7 @@ osl_cppflags := @osl_cppflags@
 id3tag_cppflags := @id3tag_cppflags@
 openssl_cppflags := @openssl_cppflags@
 gcrypt_cppflags := @gcrypt_cppflags@
+lopsub_cppflags := @lopsub_cppflags@
 ogg_cppflags := @ogg_cppflags@
 mad_cppflags := @mad_cppflags@
 faad_cppflags := @faad_cppflags@
@@ -61,6 +62,7 @@ opus_ldflags := @opus_ldflags@
 faad_ldflags := @faad_ldflags@
 mad_ldflags := @mad_ldflags@
 flac_ldflags := @flac_ldflags@
+lopsub_ldflags := @lopsub_ldflags@
 oss_ldflags := @oss_ldflags@
 alsa_ldflags := @alsa_ldflags@
 pthread_ldflags := @pthread_ldflags@
index 8ededf6..6ff5abf 100644 (file)
@@ -33,6 +33,8 @@ cmdline_dir := $(build_dir)/cmdline
 cmdlist_dir := $(build_dir)/cmdlist
 m4depdir := $(build_dir)/m4deps
 help2man_dir := $(build_dir)/help2man
+lls_suite_dir := $(build_dir)/lls
+lls_m4_dir := m4/lls
 m4_ggo_dir := m4/gengetopt
 test_dir := t
 
@@ -43,6 +45,8 @@ all_objs := $(sort $(recv_objs) $(filter_objs) $(client_objs) $(gui_objs) \
 deps := $(addprefix $(dep_dir)/, $(filter-out %.cmdline.d, $(all_objs:.o=.d)))
 m4_deps := $(addprefix $(m4depdir)/, $(addsuffix .m4d, $(executables)))
 
+audiod_objs += audiod_cmd.lsg.o
+
 # now prefix all objects with object dir
 recv_objs := $(addprefix $(object_dir)/, $(recv_objs))
 filter_objs := $(addprefix $(object_dir)/, $(filter_objs))
@@ -68,6 +72,7 @@ all: $(prefixed_executables) $(man_pages)
 man: $(man_pages)
 tarball: $(tarball)
 
+include $(lls_m4_dir)/makefile
 include $(m4_ggo_dir)/makefile
 include $(test_dir)/makefile.test
 ifeq ($(findstring clean, $(MAKECMDGOALS)),)
@@ -76,7 +81,7 @@ ifeq ($(findstring clean, $(MAKECMDGOALS)),)
 endif
 
 $(object_dir) $(man_dir) $(ggo_dir) $(cmdline_dir) $(dep_dir) $(m4depdir) \
-               $(help2man_dir) $(cmdlist_dir):
+               $(help2man_dir) $(cmdlist_dir) $(lls_suite_dir):
        $(Q) $(MKDIR_P) $@
 
 # When in doubt, use brute force (Ken Thompson)
@@ -96,6 +101,8 @@ CPPFLAGS += -DCC_VERSION='"$(cc_version)"'
 CPPFLAGS += -I/usr/local/include
 CPPFLAGS += -I$(cmdline_dir)
 CPPFLAGS += -I$(cmdlist_dir)
+CPPFLAGS += -I$(lls_suite_dir)
+CPPFLAGS += $(lopsub_cppflags)
 
 CFLAGS += -Os
 CFLAGS += -Wuninitialized
@@ -168,7 +175,7 @@ $(cmdlist_dir)/audiod.completion.h \
 
 server_command_lists := $(cmdlist_dir)/server.command_list.man \
        $(cmdlist_dir)/afs.command_list.man
-audiod_command_lists := $(cmdlist_dir)/audiod.command_list.man
+audiod_command_lists := $(lls_suite_dir)/audiod_cmd.lsg.man
 play_command_lists := $(cmdlist_dir)/play.command_list.man
 
 $(man_dir)/para_server.1: $(server_command_lists)
@@ -179,8 +186,9 @@ $(man_dir)/para_server.1: man_util_command_lists := $(server_command_lists)
 $(man_dir)/para_audiod.1: man_util_command_lists := $(audiod_command_lists)
 $(man_dir)/para_play.1: man_util_command_lists := $(play_command_lists)
 
-$(man_dir)/para_%.1: $(ggo_dir)/%.ggo man_util.bash \
-               git-version.h | $(man_dir) $(help2man_dir)
+$(man_dir)/para_%.1: $(man_util_command_lists) git-version.h \
+               $(ggo_dir)/%.ggo man_util.bash \
+               | $(man_dir) $(help2man_dir)
        @[ -z "$(Q)" ] || echo 'MAN $<'
        $(Q) \
                COMMAND_LISTS="$(man_util_command_lists)" \
@@ -278,6 +286,7 @@ $(dep_dir)/%.d: %.c | $(dep_dir)
        $(Q) $(CC) $(CPPFLAGS) -MM -MG -MP -MT $@ -MT $(object_dir)/$(*F).o $< \
                | sed -e "s@ \([a-zA-Z0-9_]\{1,\}\.cmdline.h\)@ $(cmdline_dir)/\1@g" \
                -e "s@ \([a-zA-Z0-9_]\{1,\}\.command_list.h\)@ $(cmdlist_dir)/\1@g" \
+               -e "s@ \([a-zA-Z0-9_]\{1,\}.lsg.h\)@ $(lls_suite_dir)/\1@g" \
                -e "s@ \([a-zA-Z0-9_]\{1,\}\.completion.h\)@ $(cmdlist_dir)/\1@g" > $@
 
 para_recv para_afh para_play para_server: LDFLAGS += $(id3tag_ldflags)
@@ -309,6 +318,10 @@ para_fade \
        $(oss_ldflags) \
        $(alsa_ldflags)
 
+para_audioc \
+para_audiod \
+: LDFLAGS += $(lopsub_ldflags)
+
 para_server \
 para_filter \
 para_audiod \
index f8fd80f..dabc2f7 100644 (file)
--- a/audioc.c
+++ b/audioc.c
@@ -16,6 +16,8 @@
 #include <signal.h>
 
 #include "audioc.cmdline.h"
+#include "audiod_cmd.lsg.h"
+
 #include "para.h"
 #include "error.h"
 #include "net.h"
@@ -72,7 +74,6 @@ fail:
 #include "sched.h"
 #include "buffer_tree.h"
 #include "interactive.h"
-#include "audiod.completion.h"
 
 static struct sched sched;
 
@@ -100,7 +101,7 @@ static void help_completer(struct i9e_completion_info *ci,
 static void version_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-v", NULL};
+       char *opts[] = {LSG_AUDIOD_CMD_VERSION_OPTS, NULL};
 
        if (ci->word_num <= 2 && ci->word && ci->word[0] == '-')
                i9e_complete_option(opts, ci, cr);
@@ -110,7 +111,7 @@ static void stat_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
        char *sia[] = {STATUS_ITEM_ARRAY NULL};
-       char *opts[] = {"-p", NULL};
+       char *opts[] = {LSG_AUDIOD_CMD_STAT_OPTS, NULL};
 
        if (ci->word_num <= 2 && ci->word && ci->word[0] == '-')
                i9e_complete_option(opts, ci, cr);
@@ -121,12 +122,16 @@ static void stat_completer(struct i9e_completion_info *ci,
 static void grab_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
-       char *opts[] = {"-ms", "-ms", "-ma", "-p=", "-n=", "-o", NULL};
+       char *opts[] = {LSG_AUDIOD_CMD_GRAB_OPTS, NULL};
        i9e_complete_option(opts, ci, cr);
 }
 
+I9E_DUMMY_COMPLETER(SUPERCOMMAND_UNAVAILABLE);
 static struct i9e_completer audiod_completers[] = {
-       AUDIOD_COMPLETERS
+#define LSG_AUDIOD_CMD_CMD(_name) {.name = #_name, \
+       .completer = _name ## _completer}
+       LSG_AUDIOD_CMD_SUBCOMMANDS
+#undef LSG_AUDIOD_CMD_CMD
        {.name = NULL}
 };
 
index db69cf1..fa40191 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -15,6 +15,7 @@
 #include <netdb.h>
 #include <signal.h>
 #include <pwd.h>
+#include <lopsub.h>
 
 #include "para.h"
 #include "error.h"
index 18c802d..6922497 100644 (file)
@@ -2,78 +2,3 @@ BN: audiod
 SF: audiod_command.c
 SN: list of audiod commands
 ---
-N: cycle
-D: switch to next mode
-U: cycle
-H: on -> standby -> off -> on
----
-N: grab
-D: grab the audio stream
-L:
-U: -- grab [-m[{s|p|a}]] [-p=PARENT] [-n=NAME] [-o]
-H:
-H: grab ('splice') the audio stream at any position in the buffer
-H: tree and send that data back to the client.
-H:
-H: Options:
-H:
-H: -m  Change grab mode. Defaults to sloppy grab if not given.
-H:
-H:    -ms: sloppy grab
-H:    -mp: pedantic grab
-H:    -ma: aggressive grab
-H:
-H: The various grab modes only differ in what happens if an attempt to
-H: write the grabbed audio data would block. Sloppy mode ignores the
-H: write, pedantic mode aborts and aggressive mode tries to write anyway.
-H:
-H: -p  Grab output of node PARENT of the buffer tree.
-H:
-H: -n  Name of the new buffer tree node. Defaults to 'grab'.
-H:
-H: -o  One-shot mode: Stop grabbing if audio file changes.
----
-N: help
-D: display command list or help for given command
-U: help [command]
-H: When I was younger, so much younger than today, I never needed anybody's help
-H: in any way. But now these days are gone, I'm not so self assured. Now I find
-H: I've changed my mind and opened up the doors.
-H:                                                              -- Beatles: Help
----
-N: off
-D: deactivate para_audiod
-U: off
-H: Close connection to para_server and stop all decoders.
----
-N: on
-D: activate para_audiod
-U: on
-H: Establish connection to para_server, retrieve para_server's current status. If
-H: playing, start corresponding decoder. Otherwise stop all decoders.
----
-N: sb
-D: enter standby mode
-U: sb
-H: Stop all decoders but leave connection to para_server open.
----
-N: stat
-D: print status information
-U: stat [-p] [item1 ...]
-H: Dump given status items (all if none given) to stdout. If -p is given, use
-H: parser-friendly mode.
----
-N: tasks
-D: list current tasks
-U: tasks
-H: Print the list of task ids together with the status of each task.
----
-N: term
-D: terminate audiod
-U: term
-H: Stop all decoders, shut down connection to para_server and exit.
----
-N: version
-D: print the version of para_audiod
-U: version [-v]
-H: If the -v option is given, a more detailed version text is printed.
index 3a39027..8a87d91 100644 (file)
 #include <arpa/inet.h>
 #include <sys/un.h>
 #include <netdb.h>
+#include <lopsub.h>
 
 #include "para.h"
+#include "audiod_cmd.lsg.h"
 #include "audiod.cmdline.h"
-#include "audiod.command_list.h"
 #include "list.h"
 #include "sched.h"
 #include "ggo.h"
 extern struct sched sched;
 extern char *stat_item_values[NUM_STAT_ITEMS];
 
-typedef int audiod_command_handler_t(int, int, char **);
-static audiod_command_handler_t AUDIOD_COMMAND_HANDLERS;
-
-/* Defines one command of para_audiod. */
-struct audiod_command {
-       const char *name;
-       /* Pointer to the function that handles the command. */
-       /*
-        * Command handlers must never never close their file descriptor. A
-        * positive return value tells audiod that the status items have
-        * changed. In this case audiod will send an updated version of all
-        * status items to to each connected stat client.
-        */
-       audiod_command_handler_t *handler;
-       /* One-line description. */
-       const char *description;
-       /* Summary of the command line options. */
-       const char *usage;
-       /* The long help text. */
-       const char *help;
-};
+/** The maximal number of simultaneous connections. */
+#define MAX_STAT_CLIENTS 50
 
-static struct audiod_command audiod_cmds[] = {DEFINE_AUDIOD_CMD_ARRAY};
+/** Pointer to a command handler function. */
+typedef int (*audiod_cmd_handler_t)(int, struct lls_parse_result *);
 
-/** Iterate over the array of all audiod commands. */
-#define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++)
+/** 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. */
+};
 
-/** The maximal number of simultaneous connections. */
-#define MAX_STAT_CLIENTS 50
+/** 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 {
@@ -245,10 +234,11 @@ static int dump_commands(int fd)
        char *buf = para_strdup(""), *tmp = NULL;
        int i;
        ssize_t ret;
+       const struct lls_command *cmd;
 
-       FOR_EACH_COMMAND(i) {
-               tmp = make_message("%s%s\t%s\n", buf, audiod_cmds[i].name,
-                       audiod_cmds[i].description);
+       for (i = 1; (cmd = lls_cmd(i, audiod_cmd_suite)); i++) {
+               tmp = make_message("%s%s\t%s\n", buf, lls_command_name(cmd),
+                       lls_purpose(cmd));
                free(buf);
                buf = tmp;
        }
@@ -257,76 +247,80 @@ static int dump_commands(int fd)
        return ret;
 }
 
-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;
-
-       if (argc < 2) {
-               ret = dump_commands(fd);
-               goto out;
+       int ret;
+       char *buf, *errctx;
+       const struct lls_command *cmd;
+
+       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+       if (ret < 0) {
+               if (errctx) {
+                       buf = make_message("%s\n", errctx);
+                       client_write(fd, buf);
+                       free(buf);
+                       free(errctx);
+               }
+               return ret;
        }
-       FOR_EACH_COMMAND(i) {
-               if (strcmp(audiod_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],
-                       audiod_cmds[i].description,
-                       audiod_cmds[i].usage,
-                       audiod_cmds[i].help
-               );
-               ret = client_write(fd, buf);
+       if (lls_num_inputs(lpr) == 0)
+               return dump_commands(fd);
+       ret = lls(lls_lookup_subcmd(lls_input(0, lpr), audiod_cmd_suite,
+               &errctx));
+       if (ret < 0) {
+               buf = make_message("%s: %s\nAvailable commands:\n", errctx?
+                       errctx : lls_input(0, lpr), para_strerror(-ret));
+               if (client_write(fd, buf) >= 0)
+                       dump_commands(fd);
+               free(errctx);
                free(buf);
                goto out;
        }
-       ret = client_write(fd, "No such command. Available commands:\n");
-       if (ret > 0)
-               ret = dump_commands(fd);
+       cmd = lls_cmd(ret, audiod_cmd_suite);
+       buf = lls_long_help(cmd);
+       assert(buf);
+       ret = client_write(fd, buf);
+       free(buf);
 out:
        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)
 {
+       int ret;
        char *tl = get_task_list(&sched);
-       int ret = 1;
 
-       if (tl)
-               ret = client_write(fd, tl);
+       if (!tl) /* no tasks registered yet */
+               return 0;
+       ret = client_write(fd, tl);
        free(tl);
-       return ret < 0? ret : 0;
+       return ret;
 }
+EXPORT_AUDIOD_CMD_HANDLER(tasks)
 
-static int com_stat(int fd, int argc, char **argv)
+static int com_stat(int fd, struct lls_parse_result *lpr)
 {
        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;
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strncmp(arg, "-p", 2)) {
-                       parser_friendly = 1;
-                       b.flags = PBF_SIZE_PREFIX;
-               }
+       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 (i >= argc)
+       num_inputs = lls_num_inputs(lpr);
+       if (num_inputs == 0)
                mask--; /* set all bits */
-       for (; i < argc; i++) {
-               ret = stat_item_valid(argv[i]);
+       for (i = 0; i < num_inputs; i++) {
+               ret = stat_item_valid(lls_input(i, lpr));
                if (ret < 0)
                        return ret;
                mask |= (one << ret);
@@ -344,58 +338,61 @@ static int com_stat(int fd, int argc, char **argv)
        free(b.buf);
        return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(stat)
 
-static int com_grab(int fd, int argc, char **argv)
+static int com_grab(int fd, struct lls_parse_result *lpr)
 {
-       int ret = grab_client_new(fd, argc, argv, &sched);
+       int ret = grab_client_new(fd, lpr, &sched);
        return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(grab)
 
-static int com_term(__a_unused int fd, __a_unused int argc, __a_unused char **argv)
+static int com_term(__a_unused int fd, __a_unused struct lls_parse_result *lpr)
 {
        return -E_AUDIOD_TERM;
 }
+EXPORT_AUDIOD_CMD_HANDLER(term)
 
-static int com_on(__a_unused 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;
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(on)
 
-static int com_off(__a_unused 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;
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(off)
 
-static int com_sb(__a_unused 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;
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(sb)
 
-static int com_cycle(__a_unused 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;
        }
        return 1;
 }
+EXPORT_AUDIOD_CMD_HANDLER(cycle)
 
-static int com_version(int fd, int argc, char **argv)
+static int com_version(int fd, struct lls_parse_result *lpr)
 {
        int ret;
        char *msg;
+       const struct lls_opt_result *r_v;
 
-       if (argc > 1 && strcmp(argv[1], "-v") == 0)
+       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"));
@@ -403,6 +400,7 @@ static int com_version(int fd, int argc, char **argv)
        free(msg);
        return ret < 0? ret : 0;
 }
+EXPORT_AUDIOD_CMD_HANDLER(version)
 
 /**
  * Handle arriving connections on the local socket.
@@ -423,10 +421,14 @@ static int com_version(int fd, int argc, char **argv)
  * */
 int handle_connect(int accept_fd, fd_set *rfds)
 {
-       int i, argc, ret, clifd;
+       int argc, ret, clifd;
        char buf[MAXLINE], **argv = NULL;
        struct sockaddr_un unix_addr;
        uid_t uid;
+       const struct lls_command *cmd;
+       struct lls_parse_result *lpr;
+       char *errctx;
+       const struct audiod_command_info *aci;
 
        ret = para_accept(accept_fd, rfds, &unix_addr, sizeof(struct sockaddr_un), &clifd);
        if (ret <= 0)
@@ -443,18 +445,27 @@ int handle_connect(int accept_fd, fd_set *rfds)
        if (ret <= 0)
                goto out;
        argc = ret;
-       //PARA_INFO_LOG("argv[0]: %s, argc = %d\n", argv[0], argc);
-       FOR_EACH_COMMAND(i) {
-               if (strcmp(audiod_cmds[i].name, argv[0]))
-                       continue;
-               ret = audiod_cmds[i].handler(clifd, argc, argv);
+       ret = lls(lls_lookup_subcmd(argv[0], audiod_cmd_suite, &errctx));
+       if (ret < 0)
                goto out;
-       }
-       ret = -E_INVALID_AUDIOD_CMD;
+       cmd = lls_cmd(ret, audiod_cmd_suite);
+       ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
+       if (ret < 0)
+               goto out;
+       aci = lls_user_data(cmd);
+       ret = aci->handler(clifd, lpr);
+       lls_free_parse_result(lpr, cmd);
 out:
        free_argv(argv);
        if (ret < 0 && ret != -E_CLIENT_WRITE) {
-               char *tmp = make_message("%s\n", para_strerror(-ret));
+               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);
        }
index 0b00cc2..36a1f4b 100644 (file)
@@ -67,6 +67,10 @@ AC_PATH_PROG([INSTALL], [install])
 test -z "$INSTALL" && AC_MSG_ERROR(
        [The install program is required to build this package])
 
+AC_PATH_PROG([lopsubgen], [lopsubgen])
+test -z "$lopsubgen" && AC_MSG_ERROR(
+       [lopsubgen is required to build this package])
+
 AC_PROG_CC
 AC_PROG_CPP
 
@@ -92,6 +96,22 @@ AC_CHECK_HEADER(osl.h, [], [HAVE_OSL=no])
 AC_CHECK_LIB([osl], [osl_open_table], [], [HAVE_OSL=no])
 LIB_SUBST_FLAGS(osl)
 UNSTASH_FLAGS
+######################################################################## lopsub
+STASH_FLAGS
+LIB_ARG_WITH([lopsub], [-llopsub])
+HAVE_LOPSUB=yes
+AC_CHECK_HEADER(lopsub.h, [], [HAVE_LOPSUB=no])
+AC_CHECK_LIB([lopsub], [lls_merge], [], [HAVE_LOPSUB=yes])
+if test $HAVE_LOPSUB = no; then AC_MSG_ERROR([
+       The lopsub library is required to build this software, but
+       the above checks indicate it is not installed on your system.
+       Run the following command to download a copy.
+               git clone git://git.tuebingen.mpg.de/lopsub.git
+       Install the library, then run this configure script again.
+])
+fi
+LIB_SUBST_FLAGS([lopsub])
+UNSTASH_FLAGS
 ######################################################################## openssl
 STASH_FLAGS
 HAVE_OPENSSL=yes
diff --git a/error.h b/error.h
index 899c574..0af0dec 100644 (file)
--- a/error.h
+++ b/error.h
        PARA_ERROR(ID3_SETENCODING, "could not set id3 text encoding field"), \
        PARA_ERROR(ID3_SETSTRING, "could not set id3 string field"), \
        PARA_ERROR(INCOHERENT_BLOCK_LEN, "incoherent block length"), \
-       PARA_ERROR(INVALID_AUDIOD_CMD, "invalid command"), \
        PARA_ERROR(KEY_MARKER, "invalid/missing key header or footer"), \
        PARA_ERROR(KEY_PERM, "unprotected private key"), \
        PARA_ERROR(LIBSAMPLERATE, "secret rabbit code error"), \
@@ -287,19 +286,28 @@ extern const char * const para_errlist[];
  */
 #define OSL_ERROR_BIT 29
 
+#define LLS_ERROR_BIT 28
+
 /** Check whether the system error bit is set. */
 #define IS_SYSTEM_ERROR(num) (!!((num) & (1 << SYSTEM_ERROR_BIT)))
 
 /** Check whether the osl error bit is set. */
 #define IS_OSL_ERROR(num) (!!((num) & (1 << OSL_ERROR_BIT)))
 
+/** Check whether the lopsub error bit is set. */
+#define IS_LLS_ERROR(num) (!!((num) & (1 << LLS_ERROR_BIT)))
+
 /** Set the system error bit for the given number. */
 #define ERRNO_TO_PARA_ERROR(num) ((num) | (1 << SYSTEM_ERROR_BIT))
 
 /** Set the osl error bit for the given number. */
 #define OSL_ERRNO_TO_PARA_ERROR(num) ((num) | (1 << OSL_ERROR_BIT))
 
+/** Set the lopsub error bit for the error code. */
+#define LLS_ERRNO_TO_PARA_ERROR(num) ((num) | (1 << LLS_ERROR_BIT))
+
 static const char *weak_osl_strerror(int) __attribute__ ((weakref("osl_strerror")));
+static const char *weak_lls_strerror(int) __attribute__ ((weakref("lls_strerror")));
 /**
  * Paraslash's version of strerror(3).
  *
@@ -314,6 +322,10 @@ _static_inline_ const char *para_strerror(int num)
                assert(weak_osl_strerror);
                return weak_osl_strerror(num & ~(1U << OSL_ERROR_BIT));
        }
+       if (IS_LLS_ERROR(num)) {
+               assert(weak_lls_strerror);
+               return weak_lls_strerror(num & ~(1U << LLS_ERROR_BIT));
+       }
        if (IS_SYSTEM_ERROR(num))
                return strerror(num & ~(1U << SYSTEM_ERROR_BIT));
        return para_errlist[num];
@@ -336,3 +348,16 @@ _static_inline_ int osl(int ret)
                return ret;
        return -OSL_ERRNO_TO_PARA_ERROR(-ret);
 }
+
+/**
+ * Wrapper for lopsub library calls.
+ *
+ * \param ret See osl().
+ * \return See osl().
+ */
+_static_inline_ int lls(int ret)
+{
+       if (ret >= 0)
+               return ret;
+       return -LLS_ERRNO_TO_PARA_ERROR(-ret);
+}
index 926f479..11fff4c 100644 (file)
@@ -8,11 +8,14 @@
 
 #include <regex.h>
 #include <sys/types.h>
+#include <inttypes.h>
+#include <lopsub.h>
+
+#include "audiod_cmd.lsg.h"
 
 #include "para.h"
 #include "list.h"
 #include "sched.h"
-#include "ggo.h"
 #include "buffer_tree.h"
 #include "grab_client.h"
 #include "audiod.h"
@@ -210,77 +213,62 @@ err:
        return ret;
 }
 
-static int gc_check_args(int argc, char **argv, struct grab_client *gc)
+static int gc_check_args(struct lls_parse_result *lpr, struct grab_client *gc)
 {
-       int i;
+       const struct lls_opt_result *r;
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strncmp(arg, "-m", 2)) {
-                       if (*(arg + 3))
-                               return -E_GC_SYNTAX;
-                       switch(*(arg + 2)) {
-                       case 's':
-                               gc->mode = GM_SLOPPY;
-                               continue;
-                       case 'a':
-                               gc->mode = GM_AGGRESSIVE;
-                               continue;
-                       case 'p':
-                               gc->mode = GM_PEDANTIC;
-                               continue;
-                       default:
-                               return -E_GC_SYNTAX;
-                       }
-               }
-               if (!strcmp(arg, "-o")) {
-                       gc->flags |= GF_ONE_SHOT;
-                       continue;
-               }
-               if (!strncmp(arg, "-p=", 3)) {
-                       gc->parent = para_strdup(arg + 3);
-                       continue;
-               }
-               if (!strncmp(arg, "-n=", 3)) {
-                       gc->name = para_strdup(arg + 3);
-                       continue;
-               }
-               return -E_GC_SYNTAX;
+       r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_MODE, lpr);
+       if (lls_opt_given(r) > 0) {
+               const char *arg = lls_string_val(0, r);
+               if (strcmp(arg, "s") == 0)
+                       gc->mode = GM_SLOPPY;
+               else if (strcmp(arg, "a") == 0)
+                       gc->mode = GM_AGGRESSIVE;
+               else if (strcmp(arg, "p") == 0)
+                       gc->mode = GM_PEDANTIC;
+               else
+                       return -E_GC_SYNTAX;
+       }
+
+       r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_ONE_SHOT, lpr);
+       if (lls_opt_given(r) > 0)
+               gc->flags |= GF_ONE_SHOT;
+
+       r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_PARENT, lpr);
+       if (lls_opt_given(r) > 0) {
+               const char *arg = lls_string_val(0, r);
+               gc->parent = para_strdup(arg);
+       }
+
+       r = lls_opt_result(LSG_AUDIOD_CMD_GRAB_OPT_NAME, lpr);
+       if (lls_opt_given(r) > 0) {
+               const char *arg = lls_string_val(0, r);
+               gc->name = para_strdup(arg);
        }
-       if (i != argc)
-               return -E_GC_SYNTAX;
        return 1;
 }
 
 /**
- * Check the command line options and allocate a grab_client structure.
+ * Create and activate a grab client.
  *
  * \param fd The file descriptor of the client.
- * \param argc Argument count.
- * \param argv Argument vector.
+ * \param lpr The parsed command line of the grab command.
  * \param s The scheduler to register the grab client task to.
  *
- * If the command line options given by \a argc and \a argv are valid.
- * allocate a struct grab_client and initialize it with this valid
- * configuration.
- *
- * If the new grab client can be added to an existing buffer tree, activate it.
- * Otherwise, add it to the inactive list for later activation.
+ * This function semantically parses the arguments given as options to the grab
+ * command. On success it allocates a struct grab_client, associates it with
+ * the given file descriptor and activates it. If the new grab client can not
+ * be attached to an existing buffer tree node it is put into the inactive list
+ * for later activation.
  *
  * \return Standard.
  */
-int grab_client_new(int fd, int argc, char **argv, struct sched *s)
+int grab_client_new(int fd, struct lls_parse_result *lpr, struct sched *s)
 {
        int ret;
        struct grab_client *gc = para_calloc(sizeof(struct grab_client));
 
-       ret = gc_check_args(argc, argv, gc);
+       ret = gc_check_args(lpr, gc);
        if (ret < 0)
                goto err_out;
        ret = dup(fd);
index 7a752ce..3f3a0c0 100644 (file)
@@ -6,5 +6,5 @@
 
 /** \file grab_client.h exported symbols from grab_client.c */
 
-int grab_client_new(int fd, int argc, char **argv, struct sched *s);
+int grab_client_new(int fd, struct lls_parse_result *lpr, struct sched *s);
 void activate_grab_clients(struct sched *s);
diff --git a/m4/lls/audiod_cmd.suite.m4 b/m4/lls/audiod_cmd.suite.m4
new file mode 100644 (file)
index 0000000..11de987
--- /dev/null
@@ -0,0 +1,100 @@
+[suite audiod_cmd]
+caption = list of audiod commands
+[subcommand cycle]
+       purpose = switch to next operating mode
+       [description]
+               Cycle through the three operation modes (on, standby, off).
+       [/description]
+
+[subcommand help]
+       purpose = display command list or help for given command
+       non-opts-name = [subcommand]
+       [description]
+               When I was younger, so much younger than today, I never needed
+               anybody's help in any way. But now these days are gone, I'm not so self
+               assured. Now I find I've changed my mind and opened up the doors. --
+               Beatles: Help
+       [/description]
+
+[subcommand grab]
+       purpose = grab the audio stream
+       [description]
+               grab ('splice') the audio stream at any position in the buffer tree
+               and send that data back to the client.
+       [/description]
+       [option mode]
+               short_opt = m
+               summary = change grab mode
+               arg_info = required_arg
+               arg_type = string
+               typestr = mode
+               default_val = s
+               [help]
+                       The various grab modes only differ in what happens if an attempt to
+                       write the grabbed audio data would block. Sloppy mode ("s") ignores
+                       the write, pedantic mode ("p") aborts and aggressive mode ("a")
+                       tries to write anyway.
+               [/help]
+       [option parent]
+               short_opt = p
+               summary = Grab output of the given node of the buffer tree
+               arg_info = required_arg
+               arg_type = string
+               typestr = node
+       [option name]
+               short_opt = n
+               summary = Name of the new buffer tree node. Defaults to 'grab'
+               arg_info = optional_arg
+               arg_type = string
+               typestr = string
+       [option one-shot]
+               short_opt = o
+               summary = One-shot mode: Stop grabbing if audio file changes
+
+[subcommand off]
+       purpose = deactivate para_audiod
+       [description]
+               Close connection to para_server and stop all decoders.
+       [/description]
+
+[subcommand on]
+       purpose = activate para_audiod
+       [description]
+               Establish connection to para_server, retrieve para_server's current
+               status. If playing, start corresponding decoder. Otherwise stop
+               all decoders.
+       [/description]
+
+[subcommand sb]
+       purpose = enter standby mode
+       [description]
+               Stop all decoders but leave connection to para_server open.
+       [/description]
+
+[subcommand stat]
+       purpose = print status information
+       non-opts-name = [item...]
+       [description]
+               Dump given status items (all if none given) to stdout.
+       [/description]
+       [option parser-friendly]
+               short_opt = p
+               summary = use parser-friendly output format
+
+[subcommand version]
+       purpose = print the version of para_audiod
+       [option verbose]
+               short_opt = v
+               summary = print detailed (multi-line) version text
+
+[subcommand tasks]
+       purpose = list current tasks
+       [description]
+               Print the list of task ids together with the status of each task.
+       [/description]
+
+[subcommand term]
+       purpose = terminate para_audiod
+       [description]
+               Stop all decoders, shut down connection to para_server and exit.
+       [/description]
diff --git a/m4/lls/makefile b/m4/lls/makefile
new file mode 100644 (file)
index 0000000..bd36add
--- /dev/null
@@ -0,0 +1,21 @@
+.PRECIOUS: $(lls_suite_dir)/%.suite
+
+$(lls_suite_dir)/%.suite: $(lls_m4_dir)/%.suite.m4 | $(lls_suite_dir)
+       @[ -z "$(Q)" ] || echo 'M4 $<'
+       $(Q) $(M4) -Pg $< > $@
+
+$(lls_suite_dir)/%.lsg.c: $(lls_suite_dir)/%.suite
+       @[ -z "$(Q)" ] || echo 'LSGC $<'
+       $(Q) lopsubgen --gen-c --output-dir $(lls_suite_dir) < $<
+
+$(lls_suite_dir)/%.lsg.h: $(lls_suite_dir)/%.suite
+       @[ -z "$(Q)" ] || echo 'LSGH $<'
+       $(Q) lopsubgen --gen-header --output-dir $(lls_suite_dir) < $<
+
+$(lls_suite_dir)/%.lsg.man: $(lls_suite_dir)/%.suite
+       @[ -z "$(Q)" ] || echo 'LSGM $<'
+       $(Q) lopsubgen --gen-man --output-dir $(lls_suite_dir) < $<
+
+$(object_dir)/%.o: $(lls_suite_dir)/%.c | $(object_dir)
+       @[ -z "$(Q)" ] || echo 'CC $<'
+       $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(STRICT_CFLAGS) $<
index cb7519c..687acaa 100755 (executable)
@@ -90,8 +90,15 @@ target="${target%.*}" # server, audiod, filter, ...
 link="$HELP2MAN_DIR/para_$target"
 
 cl_opts=
+tempfiles=
 for cl in $COMMAND_LISTS; do
-       cl_opts+=" --include $cl"
+       if [[ "$cl" =~ lsg ]]; then
+               tempfiles+=" $cl.man_util.$$"
+               sed -e '/^\.SH / s/$/]/1' -e '/^\.SH / s/^\.SH /[/1' "$cl" > "$cl.man_util.$$"
+               cl_opts+=" --include $cl.man_util.$$"
+       else
+               cl_opts+=" --include $cl"
+       fi
 done
 
 # Create a symlink para_$target, pointing to this script. This hack is
@@ -102,7 +109,9 @@ ln -sf "$PWD/$0" "$link"
 # This will call us again twice, with either --help-$target or --version given.
 $HELP2MAN --no-info --help-option "--help-$target" $cl_opts \
        "$link" > "$output_file"
-if (($? != 0)); then
+ret=$?
+rm -f $tempfiles
+if (($ret != 0)); then
        rm -f "$output_file"
        exit 1
 fi
index 12454ee..f8d35e5 100644 (file)
@@ -203,6 +203,13 @@ code repository, execute
 
                git clone git://git.tuebingen.mpg.de/osl
 
+- [lopsub](http://people.tuebingen.mpg.de/maan/lopsub/). The long
+option parser for subcommands generates the command line and config
+file parsers for all paraslash executables. Clone the source code
+repository with
+
+               git clone git://git.tuebingen.mpg.de/lopsub
+
 - [gcc](ftp://ftp.gnu.org/pub/gnu/gcc) or
 [clang](http://clang.llvm.org). All gcc versions >= 4.2 are currently
 supported. Clang version 1.1 or newer should work as well.