====
-------------------------------------------
-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
- 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)
#include "para.h"
#include "error.h"
+#include "lsu.h"
#include "net.h"
#include "string.h"
#include "fd.h"
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,
#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;
*/
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))
} 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);
#include "recv_cmd.lsg.h"
#include "para.h"
#include "error.h"
+#include "lsu.h"
#include "crypt.h"
#include "list.h"
#include "sched.h"
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;
}
}
#include "audiod.lsg.h"
#include "para.h"
+#include "lsu.h"
#include "audiod_cmd.lsg.h"
#include "list.h"
#include "sched.h"
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)
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,
#include "para.h"
#include "error.h"
#include "list.h"
+#include "lsu.h"
#include "sched.h"
#include "crypt.h"
#include "net.h"
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)
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)
}
}
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));
ret = lls_num_inputs(lpr);
out:
free(home);
- free(cf);
if (ret < 0) {
if (errctx)
PARA_ERROR_LOG("%s\n", errctx);
#include "server.lsg.h"
#include "para.h"
#include "error.h"
+#include "lsu.h"
#include "crypt.h"
#include "sideband.h"
#include "command.h"
}
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);
wma_common
sideband
version
+ lsu
"
if test "$CRYPTOLIB" = openssl; then
server_errlist_objs="$server_errlist_objs openssl"
net
string
fd
+ lsu
sched
stdin
stdout
wmadec_filter
buffer_tree
sync_filter
+ lsu
"
if test "$CRYPTOLIB" = openssl; then
audiod_errlist_objs="$audiod_errlist_objs openssl"
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
fd
gui
gui_theme
+ lsu
time
sched
version
amp_filter
fecdec_filter
fec
+ lsu
version
prebuffer_filter
time
file_write
version
sync_filter
+ lsu
"
NEED_OGG_OBJECTS && play_errlist_objs="$play_errlist_objs ogg_afh_common"
NEED_VORBIS_OBJECTS && {
audioc_errlist_objs="
audioc
string
+ lsu
net
fd
version
#include "filter_cmd.lsg.h"
#include "para.h"
#include "list.h"
+#include "lsu.h"
#include "sched.h"
#include "buffer_tree.h"
#include "filter.h"
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);
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;
#include "gui.lsg.h"
#include "para.h"
#include "gui.h"
+#include "lsu.h"
#include "string.h"
#include "ringbuffer.h"
#include "fd.h"
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. */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
[/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
--- /dev/null
+[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]
+
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
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
#include "mixer.lsg.h"
#include "para.h"
+#include "lsu.h"
#include "fd.h"
#include "string.h"
#include "mix.h"
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;
}
/**
#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"
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)
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[] = {
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);
}
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);
#include "server.lsg.h"
#include "para.h"
#include "error.h"
+#include "lsu.h"
#include "crypt.h"
#include "afh.h"
#include "string.h"
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));
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;
}
/*
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"