Merge branch 'refs/heads/t/long-help'
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 10 Jun 2018 16:59:36 +0000 (18:59 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 10 Jun 2018 17:03:23 +0000 (19:03 +0200)
This series introduces lsu.c and lsu.h which contain helpers related to
the lopsub library. These helpers are designed to be shared between
the executables. The series starts by implementing a generic help
command for lopsub suites and converts para_server. Subsequent patches
convert audiod and para_play.

The second part of the series adds another lopsub related helper which
merges command line options and config file options. Each executable
is modified to make use of the new helper, getting rid of quite some
code duplication.

The conflict resolution for server.c has been tested for a while.

Cooking for five weeks.

* refs/heads/t/long-help:
  play: Use lsu_merge_config_file_options().
  mixer: Use lsu_merge_config_file_options().
  gui: Use lsu_merge_config_file_options().
  filter: Use lsu_merge_config_file_options().
  audioc: Use lsu_merge_config_file_options().
  audiod: Use lsu_merge_config_file_options().
  client: Use lsu_merge_config_file_options().
  lsu: Add helper to merge config file options, convert server.
  Trivial: Rename completion_result variables.
  play: Implement help --long.
  audiod: Implement help --long.
  Introduce lsu.{c,h}, implement help --long for para_server.

20 files changed:
NEWS.md
audioc.c
audiod.c
audiod_command.c
client.c
client_common.c
command.c
configure.ac
filter.c
gui.c
lsu.c [new file with mode: 0644]
lsu.h [new file with mode: 0644]
m4/lls/audiod_cmd.suite.m4
m4/lls/include/long-help.m4 [new file with mode: 0644]
m4/lls/play_cmd.suite.m4
m4/lls/server_cmd.suite.m4
mixer.c
play.c
server.c
t/t0004-server.sh

diff --git a/NEWS.md b/NEWS.md
index 4a837ed..29a64ff 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -2,7 +2,7 @@ NEWS
 ====
 
 -------------------------------------------
-0.6.2 (to be accounced) "elastic diversity"
+0.6.2 (to be announced) "elastic diversity"
 -------------------------------------------
 
 - para_gui no longer waits up to one second to update the screen when
@@ -18,6 +18,10 @@ NEWS
 - New server options: --listen--address, --http-listen-address and
   --dccp-listen-address. These options restrict the set of listening
   addresses of the TCP and DCCP sockets of the server process.
+- The help commands of server, audiod, play now support --long. By default,
+  the short help is shown.
+- The code which merges the command line options with the config file
+  options has been consolidated.
 
 Download: [tarball](./releases/paraslash-git.tar.xz)
 
index 77a0539..af67063 100644 (file)
--- a/audioc.c
+++ b/audioc.c
@@ -17,6 +17,7 @@
 
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "net.h"
 #include "string.h"
 #include "fd.h"
@@ -95,9 +96,15 @@ I9E_DUMMY_COMPLETER(tasks);
 I9E_DUMMY_COMPLETER(term);
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, audiod_completers);
+       char *opts[] = {LSG_AUDIOD_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, audiod_completers);
 }
 
 static void version_completer(struct i9e_completion_info *ci,
@@ -284,19 +291,6 @@ __noreturn static void print_completions(void)
 
 #endif /* HAVE_READLINE */
 
-static char *configfile_exists(void)
-{
-       char *config_file;
-       char *home = para_homedir();
-
-       config_file = make_message("%s/.paraslash/audioc.conf", home);
-       free(home);
-       if (file_exists(config_file))
-               return config_file;
-       free(config_file);
-       return NULL;
-}
-
 static void handle_help_flag(void)
 {
        char *help;
@@ -330,54 +324,21 @@ static void handle_help_flag(void)
  */
 int main(int argc, char *argv[])
 {
-       const struct lls_command *cmd = CMD_PTR;
        int ret, fd;
-       char *cf = NULL, *buf, *args, *errctx = NULL;
+       char *buf, *args, *errctx = NULL;
        size_t bufsize;
-       struct lls_parse_result *lpr1, *lpr2, *lpr3;
        unsigned num_inputs;
 
-       ret = lls(lls_parse(argc, argv, cmd, &lpr1, &errctx));
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
        if (ret < 0)
                goto fail;
-       lpr = lpr1;
-       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        version_handle_flag("audioc", OPT_GIVEN(VERSION));
        handle_help_flag();
-       cf = configfile_exists();
-       if (cf) {
-               void *map;
-               size_t sz;
-               int cf_argc;
-               char **cf_argv;
-               ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-               if (ret != -E_EMPTY) {
-                       if (ret < 0)
-                               goto out;
-                       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv,
-                               &errctx));
-                       para_munmap(map, sz);
-                       if (ret < 0) {
-                               PARA_ERROR_LOG("syntax error in %s\n", cf);
-                               goto out;
-                       }
-                       cf_argc = ret;
-                       ret = lls(lls_parse(cf_argc, cf_argv, cmd, &lpr2,
-                               &errctx));
-                       lls_free_argv(cf_argv);
-                       if (ret < 0) {
-                               PARA_ERROR_LOG("parse error in %s\n", cf);
-                               goto out;
-                       }
-                       ret = lls(lls_merge(lpr1, lpr2, cmd, &lpr3, &errctx));
-                       lls_free_parse_result(lpr2, cmd);
-                       if (ret < 0)
-                               goto out;
-                       lls_free_parse_result(lpr1, cmd);
-                       lpr = lpr3;
-                       loglevel = OPT_UINT32_VAL(LOGLEVEL);
-               }
-       }
+       ret = lsu_merge_config_file_options(NULL, "audioc.conf",
+               &lpr, CMD_PTR, audioc_suite, 0 /* default flags */);
+       if (ret < 0)
+               goto fail;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        if (OPT_GIVEN(COMPLETE))
                print_completions();
        if (OPT_GIVEN(SOCKET))
@@ -412,8 +373,7 @@ int main(int argc, char *argv[])
        } while (ret >= 0);
        free(buf);
 out:
-       lls_free_parse_result(lpr, cmd);
-       free(cf);
+       lls_free_parse_result(lpr, CMD_PTR);
 fail:
        if (errctx)
                PARA_ERROR_LOG("%s\n", errctx);
index 083c2a7..b93f29d 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -17,6 +17,7 @@
 #include "recv_cmd.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
 #include "list.h"
 #include "sched.h"
@@ -378,81 +379,36 @@ empty:
 
 static void parse_config_or_die(void)
 {
-       int ret;
-       char *cf, *errctx = NULL;
-       void *map;
-       size_t sz;
+       int i, ret;
+       uint32_t n;
 
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/audiod.conf", home);
-               free(home);
-       }
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "audiod.conf", &lpr, CMD_PTR, audiod_suite, 0U /* flags */);
        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;
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
        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;
+       n = OPT_GIVEN(USER_ALLOW);
+       if (n == 0)
+               return;
+       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;
                }
-       }
-       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);
+               pw = getpwnam(arg);
+               if (!pw) {
+                       PARA_EMERG_LOG("invalid username: %s\n", arg);
+                       exit(EXIT_FAILURE);
+               }
+               uid_whitelist[i] = pw->pw_uid;
        }
 }
 
index cb4363b..bb54dfa 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "audiod.lsg.h"
 #include "para.h"
+#include "lsu.h"
 #include "audiod_cmd.lsg.h"
 #include "list.h"
 #include "sched.h"
@@ -224,59 +225,17 @@ __malloc static char *audiod_status_string(void)
        return para_strdup(status);
 }
 
-static int dump_commands(int fd)
-{
-       char *buf = para_strdup(""), *tmp = NULL;
-       int i;
-       ssize_t ret;
-       const struct lls_command *cmd;
-
-       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;
-       }
-       ret = client_write(fd, buf);
-       free(buf);
-       return ret;
-}
-
 static int com_help(int fd, struct lls_parse_result *lpr)
 {
+       char *buf;
        int ret;
-       char *buf, *errctx;
-       const struct lls_command *cmd;
+       const struct lls_opt_result *r =
+               lls_opt_result(LSG_AUDIOD_CMD_HELP_OPT_LONG, lpr);
+       bool long_help = lls_opt_given(r);
 
-       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;
-       }
-       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;
-       }
-       cmd = lls_cmd(ret, audiod_cmd_suite);
-       buf = lls_long_help(cmd);
-       assert(buf);
+       lsu_com_help(long_help, lpr, audiod_cmd_suite, NULL, &buf, NULL);
        ret = client_write(fd, buf);
        free(buf);
-out:
        return ret < 0? ret : 0;
 }
 EXPORT_AUDIOD_CMD_HANDLER(help)
index c45826a..f72719f 100644 (file)
--- a/client.c
+++ b/client.c
@@ -248,9 +248,15 @@ I9E_DUMMY_COMPLETER(tasks);
 static struct i9e_completer completers[];
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, completers);
+       char *opts[] = {LSG_SERVER_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, completers);
 }
 
 static void stat_completer(struct i9e_completion_info *ci,
index b28e895..a7eee85 100644 (file)
@@ -15,6 +15,7 @@
 #include "para.h"
 #include "error.h"
 #include "list.h"
+#include "lsu.h"
 #include "sched.h"
 #include "crypt.h"
 #include "net.h"
@@ -525,12 +526,10 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
                int *loglevel)
 {
        const struct lls_command *cmd = CLIENT_CMD_PTR;
-       void *map;
-       size_t sz;
        struct lls_parse_result *lpr;
        int ret, ll;
        struct client_task *ct;
-       char *cf = NULL, *kf = NULL, *user, *errctx, *home = para_homedir();
+       char *kf = NULL, *user, *errctx, *home = para_homedir();
 
        ret = lls(lls_parse(argc, argv, cmd, &lpr, &errctx));
        if (ret < 0)
@@ -538,38 +537,10 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
        version_handle_flag("client", CLIENT_OPT_GIVEN(VERSION, lpr));
        handle_help_flag(lpr);
 
-       if (CLIENT_OPT_GIVEN(CONFIG_FILE, lpr))
-               cf = para_strdup(CLIENT_OPT_STRING_VAL(CONFIG_FILE, lpr));
-       else
-               cf = make_message("%s/.paraslash/client.conf", home);
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-       if (ret < 0) {
-               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
-                       goto out;
-               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) &&
-                               CLIENT_OPT_GIVEN(CONFIG_FILE, lpr))
-                       goto out;
-       } 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 out;
-               cf_argc = ret;
-               ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
-               lls_free_argv(cf_argv);
-               if (ret < 0)
-                       goto out;
-               ret = lls(lls_merge(lpr, cf_lpr, cmd, &merged_lpr,
-                       &errctx));
-               lls_free_parse_result(cf_lpr, cmd);
-               if (ret < 0)
-                       goto out;
-               lls_free_parse_result(lpr, cmd);
-               lpr = merged_lpr;
-       }
+       ret = lsu_merge_config_file_options(CLIENT_OPT_STRING_VAL(CONFIG_FILE, lpr),
+               "client.conf", &lpr, cmd, client_suite, 0U /* default flags */);
+       if (ret < 0)
+               goto out;
        /* success */
        ll = CLIENT_OPT_UINT32_VAL(LOGLEVEL, lpr);
        if (loglevel)
@@ -587,7 +558,6 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
                }
        }
        PARA_INFO_LOG("user: %s\n", user);
-       PARA_INFO_LOG("config file: %s\n", cf);
        PARA_INFO_LOG("key file: %s\n", kf);
        PARA_INFO_LOG("loglevel: %d\n", ll);
        ct = para_calloc(sizeof(*ct));
@@ -599,7 +569,6 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr,
        ret = lls_num_inputs(lpr);
 out:
        free(home);
-       free(cf);
        if (ret < 0) {
                if (errctx)
                        PARA_ERROR_LOG("%s\n", errctx);
index 25b9271..6adb945 100644 (file)
--- a/command.c
+++ b/command.c
@@ -16,6 +16,7 @@
 #include "server.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
 #include "sideband.h"
 #include "command.h"
@@ -552,63 +553,35 @@ out:
 }
 EXPORT_SERVER_CMD_HANDLER(stat);
 
-/* fixed-length, human readable permission string */
-const char *server_cmd_perms_str(unsigned int perms)
+const char *aux_info_cb(unsigned cmd_num, bool verbose)
 {
-       static char result[5];
+       static char result[80];
+       unsigned perms = server_command_perms[cmd_num];
 
-       result[0] = perms & AFS_READ? 'a' : '-';
-       result[1] = perms & AFS_WRITE? 'A' : '-';
-       result[2] = perms & VSS_READ? 'v' : '-';
-       result[3] = perms & VSS_WRITE? 'V' : '-';
-       result[4] = '\0';
-       return result;
-}
-
-static int send_list_of_commands(struct command_context *cc)
-{
-       int i;
-       const struct lls_command *cmd;
-       char *msg = para_strdup("");
-
-       for (i = 1; (cmd = lls_cmd(i, server_cmd_suite)); i++) {
-               const char *perms = server_cmd_perms_str(server_command_perms[i]);
-               char *tmp = make_message("%s%s\t%s\t%s\n", msg,
-                       lls_command_name(cmd), perms, lls_purpose(cmd));
-               free(msg);
-               msg = tmp;
+       if (verbose) {
+               /* permissions: VSS_READ | VSS_WRITE */
+               sprintf(result, "permissions: %s",
+                       server_command_perms_txt[cmd_num]);
+       } else {
+               result[0] = perms & AFS_READ? 'a' : '-';
+               result[1] = perms & AFS_WRITE? 'A' : '-';
+               result[2] = perms & VSS_READ? 'v' : '-';
+               result[3] = perms & VSS_WRITE? 'V' : '-';
+               result[4] = '\0';
        }
-       return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false);
+       return result;
 }
 
 static int com_help(struct command_context *cc, struct lls_parse_result *lpr)
 {
-       const char *perms;
-       char *long_help, *buf, *errctx;
+       char *buf;
        int ret;
-       const struct lls_command *cmd;
+       unsigned n;
+       bool long_help = SERVER_CMD_OPT_GIVEN(HELP, LONG, lpr);
 
-       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
-       if (ret < 0) {
-               send_errctx(cc, errctx);
-               return ret;
-       }
-       if (lls_num_inputs(lpr) == 0)
-               return send_list_of_commands(cc);
-       /* argument given for help */
-       ret = lls(lls_lookup_subcmd(lls_input(0, lpr), server_cmd_suite,
-               &errctx));
-       if (ret < 0) {
-               send_errctx(cc, errctx);
-               return ret;
-       }
-       cmd = lls_cmd(ret, server_cmd_suite);
-       perms = server_command_perms_txt[ret];
-       long_help = lls_long_help(cmd);
-       assert(long_help);
-       ret = xasprintf(&buf, "%spermissions: %s\n", long_help, perms);
-       free(long_help);
-       return send_sb(&cc->scc, buf, ret, SBD_OUTPUT, false);
+       lsu_com_help(long_help, lpr, server_cmd_suite, aux_info_cb, &buf, &n);
+       ret = send_sb(&cc->scc, buf, n, SBD_OUTPUT, false);
+       return ret;
 }
 EXPORT_SERVER_CMD_HANDLER(help);
 
index 9493f8e..053996d 100644 (file)
@@ -392,6 +392,7 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes && test -n "$BISON" && \
                wma_common
                sideband
                version
+               lsu
        "
        if test "$CRYPTOLIB" = openssl; then
                server_errlist_objs="$server_errlist_objs openssl"
@@ -420,6 +421,7 @@ if test -n "$CRYPTOLIB"; then
                net
                string
                fd
+               lsu
                sched
                stdin
                stdout
@@ -486,6 +488,7 @@ if test -n "$CRYPTOLIB"; then
                wmadec_filter
                buffer_tree
                sync_filter
+               lsu
        "
        if test "$CRYPTOLIB" = openssl; then
                audiod_errlist_objs="$audiod_errlist_objs openssl"
@@ -543,7 +546,7 @@ fi
 if test $HAVE_OSS = yes -o $HAVE_ALSA = yes; then
        build_mixer="yes"
        executables="$executables mixer"
-       mixer_errlist_objs="mixer exec string fd version"
+       mixer_errlist_objs="mixer exec string fd lsu version"
        if test $HAVE_OSS = yes; then
                mixer_errlist_objs="$mixer_errlist_objs oss_mix"
        fi
@@ -569,6 +572,7 @@ if test $HAVE_CURSES = yes; then
                fd
                gui
                gui_theme
+               lsu
                time
                sched
                version
@@ -593,6 +597,7 @@ filter_errlist_objs="
        amp_filter
        fecdec_filter
        fec
+       lsu
        version
        prebuffer_filter
        time
@@ -722,6 +727,7 @@ play_errlist_objs="
        file_write
        version
        sync_filter
+       lsu
 "
 NEED_OGG_OBJECTS && play_errlist_objs="$play_errlist_objs ogg_afh_common"
 NEED_VORBIS_OBJECTS && {
@@ -793,6 +799,7 @@ AC_SUBST(write_objs, add_dot_o($write_objs))
 audioc_errlist_objs="
        audioc
        string
+       lsu
        net
        fd
        version
index dfdadab..d4a2423 100644 (file)
--- a/filter.c
+++ b/filter.c
@@ -9,6 +9,7 @@
 #include "filter_cmd.lsg.h"
 #include "para.h"
 #include "list.h"
+#include "lsu.h"
 #include "sched.h"
 #include "buffer_tree.h"
 #include "filter.h"
@@ -71,50 +72,15 @@ static void handle_help_flag(void)
 
 static int parse_config(void)
 {
-       char *home, *cf; /* config file */
-       struct stat statbuf;
        int ret;
 
        version_handle_flag("filter", OPT_GIVEN(VERSION));
        handle_help_flag();
-       home = para_homedir();
-       cf = make_message("%s/.paraslash/filter.conf", home);
-       free(home);
-       if (!stat(cf, &statbuf)) {
-               void *map;
-               size_t sz;
-               int cf_argc;
-               char **cf_argv, *errctx;
-               struct lls_parse_result *cf_lpr, *merged_lpr;
-
-               ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
-               if (ret != -E_EMPTY) {
-                       if (ret < 0)
-                               return ret;
-                       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv,
-                               &errctx));
-                       para_munmap(map, sz);
-                       if (ret < 0) {
-                               PARA_ERROR_LOG("syntax error in %s\n", cf);
-                               return ret;
-                       }
-                       cf_argc = ret;
-                       ret = lls(lls_parse(cf_argc, cf_argv, CMD_PTR, &cf_lpr,
-                               &errctx));
-                       lls_free_argv(cf_argv);
-                       if (ret < 0) {
-                               PARA_ERROR_LOG("parse error in %s\n", cf);
-                               return ret;
-                       }
-                       ret = lls(lls_merge(lpr, cf_lpr, CMD_PTR, &merged_lpr, &errctx));
-                       lls_free_parse_result(cf_lpr, CMD_PTR);
-                       if (ret < 0)
-                               return ret;
-                       lls_free_parse_result(lpr, CMD_PTR);
-                       lpr = merged_lpr;
-                       loglevel = OPT_UINT32_VAL(LOGLEVEL);
-               }
-       }
+       ret = lsu_merge_config_file_options(NULL, "filter.conf",
+               &lpr, CMD_PTR, filter_suite, 0 /* default flags */);
+       if (ret < 0)
+               return ret;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        if (!OPT_GIVEN(FILTER)) {
                print_filter_list();
                exit(EXIT_SUCCESS);
@@ -147,7 +113,6 @@ int main(int argc, char *argv[])
        ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
        if (ret < 0)
                goto out;
-       loglevel = OPT_UINT32_VAL(LOGLEVEL);
        ret = parse_config();
        if (ret < 0)
                goto free_lpr;
diff --git a/gui.c b/gui.c
index 69a9243..a4f1717 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -13,6 +13,7 @@
 #include "gui.lsg.h"
 #include "para.h"
 #include "gui.h"
+#include "lsu.h"
 #include "string.h"
 #include "ringbuffer.h"
 #include "fd.h"
@@ -857,64 +858,23 @@ static void check_key_map_args_or_die(void)
 static void parse_config_file_or_die(bool reload)
 {
        int ret;
-       char *cf = NULL, *errctx = NULL;
-       void *map;
-       size_t sz;
-       int cf_argc;
-       char **cf_argv;
-       struct lls_parse_result *cf_lpr, *merged_lpr;
-
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/gui.conf", home);
-               free(home);
-       }
-       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;
-               ret = 0;
-               lpr = cmdline_lpr;
-               goto success;
-       }
-       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;
-       if (reload) /* config file overrides command line */
-               ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       else /* command line options override config file options */
-               ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       lls_free_parse_result(cf_lpr, CMD_PTR);
-       if (ret < 0)
-               goto free_cf;
+       unsigned flags = MCF_DONT_FREE;
+
        if (lpr != cmdline_lpr)
                lls_free_parse_result(lpr, CMD_PTR);
-       lpr = merged_lpr;
-success:
-       loglevel = OPT_UINT32_VAL(LOGLEVEL);
-       check_key_map_args_or_die();
-       theme_init(OPT_STRING_VAL(THEME), &theme);
-free_cf:
-       free(cf);
+       lpr = cmdline_lpr;
+       if (reload)
+               flags |= MCF_OVERRIDE;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "gui.conf", &lpr, CMD_PTR, gui_suite, flags);
        if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
                exit(EXIT_FAILURE);
        }
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       check_key_map_args_or_die();
+       theme_init(OPT_STRING_VAL(THEME), &theme);
 }
 
 /* Reread configuration, terminate on errors. */
diff --git a/lsu.c b/lsu.c
new file mode 100644 (file)
index 0000000..8ccf80d
--- /dev/null
+++ b/lsu.c
@@ -0,0 +1,235 @@
+/* Copyright (C) 2018 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file lsu.c Utilities related to the lopsub library. */
+
+#include <lopsub.h>
+#include <regex.h>
+
+#include "para.h"
+#include "error.h"
+#include "string.h"
+#include "lsu.h"
+#include "fd.h"
+
+static int lsu_lopsub_error(int ret, char **errctx, char **result, unsigned *num_chars)
+{
+       const char *se = para_strerror(-ret);
+       unsigned n;
+
+       if (*errctx)
+               n = xasprintf(result, "%s: %s\n", *errctx, se);
+       else
+               n = xasprintf(result, "lopsub error: %s\n", se);
+       free(*errctx);
+       *errctx = NULL;
+       if (num_chars)
+               *num_chars = n;
+       return ret;
+}
+
+static void lsu_get_subcommand_summary(bool long_summary,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars)
+{
+       int i;
+       const struct lls_command *cmd;
+       const char *name, *aux_info = NULL;
+       struct para_buffer pb = {.max_size = 0 /* unlimited */};
+
+       para_printf(&pb, "Available subcommands:\n");
+       if (long_summary) {
+               int maxname = 0, max_aux_info = 0;
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       maxname = PARA_MAX(maxname,
+                               (int)strlen(lls_command_name(cmd)));
+                       if (aux_info_cb) {
+                               aux_info = aux_info_cb(i, false);
+                               if (!aux_info)
+                                       continue;
+                               max_aux_info = PARA_MAX(max_aux_info,
+                                       (int)strlen(aux_info));
+                       }
+               }
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       if (aux_info_cb)
+                               aux_info = aux_info_cb(i, false);
+                       para_printf(&pb, "%-*s %-*s %s\n", maxname,
+                               lls_command_name(cmd), max_aux_info, aux_info?
+                               aux_info : "", lls_purpose(cmd));
+               }
+       } else {
+               unsigned n = 8;
+               para_printf(&pb, "\t");
+               for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
+                       name = lls_command_name(cmd);
+                       if (i > 1)
+                               n += para_printf(&pb, ", ");
+                       if (n > 70) {
+                               para_printf(&pb, "\n\t");
+                               n = 8;
+                       }
+                       n += para_printf(&pb, "%s", name);
+               }
+               para_printf(&pb, "\n");
+       }
+       *result = pb.buf;
+       if (num_chars)
+               *num_chars = pb.offset;
+}
+
+/**
+ * A generic implementation of the help subcommand.
+ *
+ * This function returns the help text for the given subcommand, or the list of
+ * all subcommands if no non-option argument is given. The function is generic
+ * in that it works for arbitrary lopsub suites.
+ *
+ * \param long_help Applies to both command list and command help.
+ * \param suite The supercommand, if any, is omitted.
+ * \param lpr Used to determine whether a non-option argument is given.
+ * \param aux_info_cb Optional callback, may return NULL, static memory.
+ * \param result Must be freed by the caller.
+ * \param num_chars Initialized to the length of the returned string, optional.
+ *
+ * If the optional aux_info_cb function pointer is not NULL, the callback
+ * function must return the string representation of the aux_info structure of
+ * the given command, or NULL to indicate that this command has no aux info
+ * structure.
+ *
+ * The function fails if lpr has more than one non-option argument, or if there
+ * is exactly one non-option argument, but this argument is not the name of a
+ * subcommand in the given lopsub suite.
+ *
+ * \return Standard. In the failure case a suitable error message is returned
+ * via the result pointer and num_chars is set accordingly.
+ */
+int lsu_com_help(bool long_help, const struct lls_parse_result *lpr,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars)
+{
+       int ret;
+       unsigned n;
+       char *errctx, *tmp;
+       const char *arg, *aux_info = NULL;
+       const struct lls_command *cmd;
+
+       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
+       if (ret < 0)
+               return lsu_lopsub_error(ret, &errctx, result, num_chars);
+       if (lls_num_inputs(lpr) == 0) {
+               lsu_get_subcommand_summary(long_help, suite,
+                       aux_info_cb, result, num_chars);
+               return 0;
+       }
+       arg = lls_input(0, lpr);
+       ret = lls(lls_lookup_subcmd(arg, suite, &errctx));
+       if (ret < 0)
+               return lsu_lopsub_error(ret, &errctx, result, num_chars);
+       cmd = lls_cmd(ret, suite);
+       tmp = long_help? lls_long_help(cmd) : lls_short_help(cmd);
+       if (aux_info_cb)
+               aux_info = aux_info_cb(ret, true);
+       n = xasprintf(result, "%s%s%s", tmp, aux_info? aux_info : "",
+               aux_info? "\n" : "");
+       free(tmp);
+       if (num_chars)
+               *num_chars = n;
+       return 1;
+}
+
+/**
+ * Merge command line options and config file options.
+ *
+ * This function parses the options stored in the configuration file and merges
+ * them with the currently effective options. If the application supports
+ * config files, it is supposed to call this after the command line options
+ * have been parsed. If the application also supports config file reloading,
+ * the function will be called for that purpose.
+ *
+ * \param path Config file path, usually the argument to --config-file.
+ * \param dflt Relative to ~/.paraslash, ignored if path is not NULL.
+ * \param lpr Value-result pointer.
+ * \param cmd Passed to lls_parse() and lls_merge().
+ * \param suite Needed to tell whether cmd is the supercommand.
+ * \param flags See enum \ref lsu_merge_cf_flags.
+ *
+ * The function does nothing if path is NULL and the default config file does
+ * not exist, or if path is an empty file. Otherwise, the options of the config
+ * file are parsed, the parse result is merged with lpr, and the merged parse
+ * result is returned via lpr.
+ *
+ * By default, lpr is freed if the merge was done, but this can be changed by
+ * including MCF_DONT_FREE flags.
+ *
+ * \return Zero if there was nothing to do, one if the config file options were
+ * merged successfully, negative error code on failure. It is considered an error
+ * if path is given, but the file does not exist.
+ */
+int lsu_merge_config_file_options(const char *path, const char *dflt,
+               struct lls_parse_result **lpr, const struct lls_command *cmd,
+               const struct lls_suite *suite, unsigned flags)
+{
+       int ret;
+       void *map;
+       size_t sz;
+       int cf_argc;
+       char *cf, **cf_argv, *errctx = NULL;
+       struct lls_parse_result *old_lpr = *lpr, *cf_lpr, *merged_lpr;
+       const char *subcmd_name;
+
+       if (path)
+               cf = para_strdup(path);
+       else {
+               char *home = para_homedir();
+               cf = make_message("%s/.paraslash/%s", home, dflt);
+               free(home);
+       }
+       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       if (ret < 0) {
+               if (ret == -E_EMPTY)
+                       ret = 0;
+               else if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && !path)
+                       ret = 0;
+               else
+                       PARA_ERROR_LOG("failed to mmap config file %s\n", cf);
+               goto free_cf;
+       }
+       subcmd_name = (lls_cmd(0, suite) == cmd)? NULL : lls_command_name(cmd);
+       ret = lls(lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx));
+       para_munmap(map, sz);
+       if (ret < 0) {
+               PARA_ERROR_LOG("failed to convert config file %s\n", cf);
+               goto lopsub_error;
+       }
+       cf_argc = ret;
+       ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
+       lls_free_argv(cf_argv);
+       if (ret < 0) {
+               PARA_ERROR_LOG("failed to parse config file %s\n", cf);
+               goto lopsub_error;
+       }
+       if (flags & MCF_OVERRIDE)
+               ret = lls(lls_merge(cf_lpr, old_lpr, cmd, &merged_lpr, &errctx));
+       else
+               ret = lls(lls_merge(old_lpr, cf_lpr, cmd, &merged_lpr, &errctx));
+       lls_free_parse_result(cf_lpr, cmd);
+       if (ret < 0) {
+               PARA_ERROR_LOG("could not merge options in %s\n", cf);
+               goto lopsub_error;
+       }
+       if (!(flags & MCF_DONT_FREE))
+               lls_free_parse_result(old_lpr, cmd);
+       *lpr = merged_lpr;
+       ret = 1;
+       goto free_cf;
+lopsub_error:
+       assert(ret < 0);
+       if (errctx)
+               PARA_ERROR_LOG("lopsub error: %s\n", errctx);
+       free(errctx);
+free_cf:
+       free(cf);
+       return ret;
+}
diff --git a/lsu.h b/lsu.h
new file mode 100644 (file)
index 0000000..6141dfc
--- /dev/null
+++ b/lsu.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2018 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
+
+/** \file lsu.h Lopsub Utilities, enums and public functions. */
+int lsu_com_help(bool long_help, const struct lls_parse_result *lpr,
+               const struct lls_suite *suite,
+               const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
+               char **result, unsigned *num_chars);
+
+/** Flags for \ref lsu_merge_config_file_options(). */
+enum lsu_merge_cf_flags {
+       /**
+        * Whether the options specified in the configuration file should
+        * override the currently effective options. At application startup
+        * this is usually unset so that command line options take precedence
+        * over config file options. However, if the application supports
+        * re-reading the configuration, it can make sense to enable this flag.
+        */
+       MCF_OVERRIDE = 1,
+       /**
+        * After the two lopsub parse results have been merged, the merged
+        * parse result usually becomes the effective configuration and the
+        * parse result which corresponds to the former effective options is no
+        * longer needed. Therefore \ref lsu_merge_config_file_options() frees
+        * this former parse result by default. This flag instructs the
+        * function to keep it. This is mostly useful if the application
+        * supports re-reading the config file so that the parse result which
+        * corresponds to the command line options is kept for future calls to
+        * \ref lsu_merge_config_file_options().
+        */
+       MCF_DONT_FREE = 2,
+};
+
+int lsu_merge_config_file_options(const char *path, const char *dflt,
+               struct lls_parse_result **lpr, const struct lls_command *cmd,
+               const struct lls_suite *suite, unsigned flags);
index 11de987..80ae2e4 100644 (file)
@@ -7,14 +7,14 @@ caption = list of audiod commands
        [/description]
 
 [subcommand help]
-       purpose = display command list or help for given command
+       purpose = list available commands or print command-specific help
        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
+               When executed without any arguments, the available audiod commands
+               are listed. Otherwise, if the first argument is the name of an audiod
+               command, the description of this command is shown.
        [/description]
+       m4_include(`long-help.m4')
 
 [subcommand grab]
        purpose = grab the audio stream
diff --git a/m4/lls/include/long-help.m4 b/m4/lls/include/long-help.m4
new file mode 100644 (file)
index 0000000..408f8e1
--- /dev/null
@@ -0,0 +1,15 @@
+[option long]
+       short_opt = l
+       summary = show the long help text
+[help]
+       If no non-option argument is supplied to the help subcommand and --long
+       is not given, only the names of all subcommands are shown. With --long
+       the purpose of each command is printed as well.
+
+       If the name of a subcommand is supplied and --long is given, the help
+       text for the given subcommand contains the synopsis, the purpose and
+       the description of the specified command, followed by the option list
+       including summary and help text of each option. Without --long the
+       description of the command and the option help are omitted.
+[/help]
+
index 195b47b..36d475d 100644 (file)
@@ -13,6 +13,7 @@ caption = list of commands
                list of available commands. When called with the name of a command
                as first argument, it prints the description of this command.
        [/description]
+       m4_include(`long-help.m4')
 
 [subcommand fg]
        purpose = enter command mode
index 96ce530..079589d 100644 (file)
@@ -144,10 +144,11 @@ aux_info_prefix = Permissions:
        non-opts-name = [command]
        aux_info = NO_PERMISSION_REQUIRED
        [description]
-               Without any arguments, help prints a list of available commands. When
-               called with a command name as first argument, it prints the description
-               of this command.
+               When executed without any arguments, the available server commands
+               are listed. Otherwise, if the first argument is the name of a server
+               command, the description of this command is shown.
        [/description]
+       m4_include(`long-help.m4')
 
 [subcommand hup]
        purpose = reload config file, log file and user list
diff --git a/mixer.c b/mixer.c
index 52af25f..ad674bf 100644 (file)
--- a/mixer.c
+++ b/mixer.c
@@ -8,6 +8,7 @@
 
 #include "mixer.lsg.h"
 #include "para.h"
+#include "lsu.h"
 #include "fd.h"
 #include "string.h"
 #include "mix.h"
@@ -453,60 +454,16 @@ static void handle_help_flags(void)
 static int parse_and_merge_config_file(const struct lls_command *cmd)
 {
        int ret;
-       int cf_argc;
-       char **cf_argv;
-       char *cf, *errctx = NULL;
-       struct lls_parse_result **lprp, *cf_lpr, *merged_lpr;
-       void *map;
-       size_t sz;
-       const char *subcmd_name;
-
-       if (cmd == lls_cmd(0, mixer_suite)) {
-               lprp = &lpr;
-               subcmd_name = NULL;
-       } else {
-               lprp = &sub_lpr;
-               subcmd_name = lls_command_name(cmd);
-       }
-       if (OPT_GIVEN(PARA_MIXER, CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(PARA_MIXER, CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/mixer.conf", home);
-               free(home);
-       }
-       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(PARA_MIXER, CONFIG_FILE))
-                       goto free_cf;
-       } else {
-               ret = lls(lls_convert_config(map, sz, subcmd_name, &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, &cf_lpr, &errctx));
-               lls_free_argv(cf_argv);
-               if (ret < 0)
-                       goto free_cf;
-               ret = lls(lls_merge(*lprp, cf_lpr, cmd, &merged_lpr, &errctx));
-               lls_free_parse_result(cf_lpr, cmd);
-               if (ret < 0)
-                       goto free_cf;
-               lls_free_parse_result(*lprp, cmd);
-               *lprp = merged_lpr;
-               loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
-       }
-       ret = 1;
-free_cf:
-       free(cf);
-       if (errctx)
-               PARA_ERROR_LOG("%s\n", errctx);
-       free(errctx);
-       return ret;
+       struct lls_parse_result **lprp = (cmd == lls_cmd(0, mixer_suite))?
+               &lpr : &sub_lpr;
+
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(PARA_MIXER,
+               CONFIG_FILE), "mixer.conf", lprp, cmd, mixer_suite,
+               0 /* flags */);
+       if (ret < 0)
+               return ret;
+       loglevel = OPT_UINT32_VAL(PARA_MIXER, LOGLEVEL);
+       return 1;
 }
 
 /**
diff --git a/play.c b/play.c
index 78d53fa..647367a 100644 (file)
--- a/play.c
+++ b/play.c
@@ -11,6 +11,7 @@
 #include "write_cmd.lsg.h"
 #include "play.lsg.h"
 #include "para.h"
+#include "lsu.h"
 #include "list.h"
 #include "error.h"
 #include "buffer_tree.h"
@@ -150,73 +151,38 @@ static void handle_help_flags(void)
 
 static void parse_config_or_die(int argc, char *argv[])
 {
-       const struct lls_command *cmd = CMD_PTR;
-       int i, ret, cf_argc;
-       char *cf, *errctx, **cf_argv;
-       struct lls_parse_result *cf_lpr, *merged_lpr;
+       int i, ret;
        unsigned num_kmas;
-       void *map;
-       size_t sz;
+       char *errctx;
 
-       ret = lls(lls_parse(argc, argv, cmd, &play_lpr, &errctx));
-       if (ret < 0)
-               goto fail;
+       ret = lls(lls_parse(argc, argv, CMD_PTR, &play_lpr, &errctx));
+       if (ret < 0) {
+               if (errctx)
+                       PARA_EMERG_LOG("%s\n", errctx);
+               free(errctx);
+               PARA_EMERG_LOG("failed to parse command line options: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
        loglevel = OPT_UINT32_VAL(LOGLEVEL);
        version_handle_flag("play", OPT_GIVEN(VERSION));
        handle_help_flags(); /* also handles the zero-arg case */
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else {
-               char *home = para_homedir();
-               cf = make_message("%s/.paraslash/play.conf", home);
-               free(home);
-       }
-       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "play.conf", &play_lpr, CMD_PTR, play_suite, 0 /* flags */);
        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;
-               ret = 0;
-               goto setup_keymap;
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
        }
-       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, &cf_lpr, &errctx));
-       lls_free_argv(cf_argv);
-       if (ret < 0)
-               goto free_cf;
-       ret = lls(lls_merge(play_lpr, cf_lpr, cmd, &merged_lpr, &errctx));
-       lls_free_parse_result(cf_lpr, cmd);
-       if (ret < 0)
-               goto free_cf;
-       lls_free_parse_result(play_lpr, cmd);
-       play_lpr = merged_lpr;
        loglevel = OPT_UINT32_VAL(LOGLEVEL);
-setup_keymap:
        num_kmas = OPT_GIVEN(KEY_MAP);
        for (i = 0; i < num_kmas; i++) {
                const char *kma = lls_string_val(i, OPT_RESULT(KEY_MAP));
                if (*kma && strchr(kma + 1, ':'))
                        continue;
                PARA_EMERG_LOG("invalid key map arg: %s\n", kma);
-               goto free_cf;
+               exit(EXIT_FAILURE);
        }
-       ret = 1;
-free_cf:
-       free(cf);
-       if (ret >= 0)
-               return;
-       lls_free_parse_result(play_lpr, cmd);
-fail:
-       if (errctx)
-               PARA_EMERG_LOG("%s\n", errctx);
-       free(errctx);
-       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-       exit(EXIT_FAILURE);
 }
 
 static char get_playback_state(void)
@@ -706,9 +672,15 @@ I9E_DUMMY_COMPLETER(quit);
 I9E_DUMMY_COMPLETER(ff);
 
 static void help_completer(struct i9e_completion_info *ci,
-               struct i9e_completion_result *result)
+               struct i9e_completion_result *cr)
 {
-       result->matches = i9e_complete_commands(ci->word, pp_completers);
+       char *opts[] = {LSG_PLAY_CMD_HELP_OPTS, NULL};
+
+       if (ci->word[0] == '-') {
+               i9e_complete_option(opts, ci, cr);
+               return;
+       }
+       cr->matches = i9e_complete_commands(ci->word, pp_completers);
 }
 
 static struct i9e_completer pp_completers[] = {
@@ -742,27 +714,15 @@ EXPORT_PLAY_CMD_HANDLER(quit);
 
 static int com_help(struct lls_parse_result *lpr)
 {
-       int i, ret;
-       char *buf, *errctx;
+       int i;
+       char *buf;
        size_t sz;
-       const struct lls_command *cmd;
+       unsigned n;
+       const struct lls_opt_result *r =
+               lls_opt_result(LSG_PLAY_CMD_HELP_OPT_LONG, lpr);
+       bool long_help = lls_opt_given(r);
 
-       ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               return ret;
-       }
-       if (lls_num_inputs(lpr) == 0) {
-               if (pt->background) {
-                       for (i = 1; (cmd = lls_cmd(i, play_cmd_suite)); i++) {
-                               sz = xasprintf(&buf, "%s\t%s\n",
-                                       lls_command_name(cmd), lls_purpose(cmd));
-                               btr_add_output(buf, sz, pt->btrn);
-                       }
-                       return 0;
-               }
+       if (!pt->background) {
                FOR_EACH_MAPPED_KEY(i) {
                        bool internal = is_internal_key(i);
                        int idx = get_key_map_idx(i);
@@ -777,18 +737,8 @@ static int com_help(struct lls_parse_result *lpr)
                }
                return 0;
        }
-       ret = lls(lls_lookup_subcmd(lls_input(0, lpr), play_cmd_suite,
-               &errctx));
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               return ret;
-       }
-       cmd = lls_cmd(ret, play_cmd_suite);
-       buf = lls_long_help(cmd);
-       assert(buf);
-       btr_add_output(buf, strlen(buf), pt->btrn);
+       lsu_com_help(long_help, lpr, play_cmd_suite, NULL, &buf, &n);
+       btr_add_output(buf, n, pt->btrn);
        return 0;
 }
 EXPORT_PLAY_CMD_HANDLER(help);
index a344b77..eee0a41 100644 (file)
--- a/server.c
+++ b/server.c
@@ -41,6 +41,7 @@
 #include "server.lsg.h"
 #include "para.h"
 #include "error.h"
+#include "lsu.h"
 #include "crypt.h"
 #include "afh.h"
 #include "string.h"
@@ -215,56 +216,20 @@ err_out:
 void parse_config_or_die(bool reload)
 {
        int ret;
-       char *cf = NULL, *errctx = NULL, *user_list_file = NULL;
-       void *map;
-       size_t sz;
-       int cf_argc;
-       char **cf_argv;
-       struct lls_parse_result *cf_lpr, *merged_lpr;
-       char *home = para_homedir();
-
-       if (OPT_GIVEN(CONFIG_FILE))
-               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
-       else
-               cf = make_message("%s/.paraslash/server.conf", home);
-       if (!mmd || getpid() != afs_pid) {
-               if (OPT_GIVEN(USER_LIST))
-                       user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST));
-               else
-                       user_list_file = make_message("%s/.paraslash/server.users", home);
-       }
-       free(home);
-       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;
-               server_lpr = cmdline_lpr;
-               goto success;
-       }
-       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;
-       if (reload) /* config file overrides command line */
-               ret = lls(lls_merge(cf_lpr, cmdline_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       else /* command line options override config file options */
-               ret = lls(lls_merge(cmdline_lpr, cf_lpr, CMD_PTR, &merged_lpr,
-                       &errctx));
-       lls_free_parse_result(cf_lpr, CMD_PTR);
-       if (ret < 0)
-               goto free_cf;
+       unsigned flags = MCF_DONT_FREE;
+
        if (server_lpr != cmdline_lpr)
                lls_free_parse_result(server_lpr, CMD_PTR);
-       server_lpr = merged_lpr;
-success:
+       server_lpr = cmdline_lpr;
+       if (reload)
+               flags |= MCF_OVERRIDE;
+       ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE),
+               "server.conf", &server_lpr, CMD_PTR, server_suite, flags);
+       if (ret < 0) {
+               PARA_EMERG_LOG("failed to parse config file: %s\n",
+                       para_strerror(-ret));
+               exit(EXIT_FAILURE);
+       }
        daemon_set_loglevel(ENUM_STRING_VAL(LOGLEVEL));
        if (OPT_GIVEN(LOGFILE)) {
                daemon_set_logfile(OPT_STRING_VAL(LOGFILE));
@@ -283,19 +248,19 @@ success:
        if (OPT_GIVEN(LOG_TIMING))
                daemon_set_flag(DF_LOG_TIMING);
        daemon_set_priority(OPT_UINT32_VAL(PRIORITY));
-       if (user_list_file)
+       if (!reload || getpid() != afs_pid) {
+               char *user_list_file;
+               if (OPT_GIVEN(USER_LIST))
+                       user_list_file = para_strdup(OPT_STRING_VAL(USER_LIST));
+               else {
+                       char *home = para_homedir();
+                       user_list_file = make_message("%s/.paraslash/server.users", home);
+                       free(home);
+               }
                user_list_init(user_list_file);
-       ret = 1;
-free_cf:
-       free(cf);
-       free(user_list_file);
-       if (ret < 0) {
-               if (errctx)
-                       PARA_ERROR_LOG("%s\n", errctx);
-               free(errctx);
-               PARA_EMERG_LOG("%s\n", para_strerror(-ret));
-               exit(EXIT_FAILURE);
+               free(user_list_file);
        }
+       return;
 }
 
 /*
index 6a8e5bf..7e8ea82 100755 (executable)
@@ -27,8 +27,8 @@ declare -a oggs_base=(${oggs[@]##*/})
 declare -a commands=() cmdline=() required_objects=() good=() bad=()
 i=0
 commands[$i]="help"
-cmdline[$i]="help"
-good[$i]='help ----'
+cmdline[$i]="help -l"
+good[$i]='help \{1,\}----'
 
 let i++
 commands[$i]="init"