From 1709cc8f8a54429441f8f83c38146e667133c727 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 12 Jun 2016 15:36:21 +0200 Subject: [PATCH] server: Convert non-afs commands to lopsub. Currently the server commands are divided into two group: those commands which are handled by the server process and those which communicate with the afs process. This commit converts the commands of the former group and the corresponding completers for para_client to the lopsub suite format while the afs commands will be converted in subsequent commits. After this change para_server needs to be linked with -llopsub. To this aim the options and help texts of of the server commands are transferred from server.cmd to the new server_cmd.suite.m4, enabling long-style options in the progress. Moreover, an introduction is added at the beginning of the list of server commands which describes how server commands are executed. Command permissions are now handled by making use of the aux_info feature of lopsub. To keep those commands working which do not have any permission bit set, we need to add a new identifier NO_PERMISSION_REQUIRED to enum server_command_permissions of user_list.h. The value of this identifier is zero of course. Naturally the bulk of the change takes place in command.c where all server commands are implemented. The command handlers are modified to take a pointer to a struct lls_parse result as an additional argument. A new helper, send_errctx(), is introduced to avoid code duplication. Since command.h now refers to a lopsub parse result, all files which include command.h, including those which implement only afs commands, need to include the system header lopsub.h. To keep afs commands working, some compatibility code in run_command() is added. This will go away after all commands have been converted. A couple of macros in command.h ease the handling of the long symbolic constants exposed by the generated lopsub header file. Although only the non-afs commands are converted, the change allows for a couple of cleanups: * The E_BAD_COMMAND error code is no longer needed and has been removed. * cmd_perms_itohuman() has become unused and is removed. * The server_cmds[] array is empty and can be removed, along with the loop in send_list_of_commands() which iterated over the array. The patch also adjusts tests t0004 and t0005 since the help output format changed slightly, breaking the expectations of these tests. --- Makefile.real | 5 +- afs.c | 3 +- aft.c | 1 + attribute.c | 1 + blob.c | 1 + client.c | 8 +- command.c | 447 ++++++++++++++++++++----------------- command.h | 40 ++++ error.h | 1 - m4/lls/server_cmd.suite.m4 | 177 +++++++++++++++ server.cmd | 126 ----------- t/t0004-server.sh | 2 +- t/t0005-man.sh | 2 +- user_list.h | 1 + 14 files changed, 472 insertions(+), 343 deletions(-) create mode 100644 m4/lls/server_cmd.suite.m4 diff --git a/Makefile.real b/Makefile.real index 6ff5abf0..8aa10a6e 100644 --- a/Makefile.real +++ b/Makefile.real @@ -46,6 +46,7 @@ deps := $(addprefix $(dep_dir)/, $(filter-out %.cmdline.d, $(all_objs:.o=.d))) m4_deps := $(addprefix $(m4depdir)/, $(addsuffix .m4d, $(executables))) audiod_objs += audiod_cmd.lsg.o +server_objs += server_cmd.lsg.o # now prefix all objects with object dir recv_objs := $(addprefix $(object_dir)/, $(recv_objs)) @@ -173,8 +174,7 @@ $(cmdlist_dir)/audiod.command_list.man \ $(cmdlist_dir)/audiod.completion.h \ : audiod_command.c -server_command_lists := $(cmdlist_dir)/server.command_list.man \ - $(cmdlist_dir)/afs.command_list.man +server_command_lists := $(lls_suite_dir)/server_cmd.lsg.man audiod_command_lists := $(lls_suite_dir)/audiod_cmd.lsg.man play_command_lists := $(cmdlist_dir)/play.command_list.man @@ -320,6 +320,7 @@ para_fade \ para_audioc \ para_audiod \ +para_server \ : LDFLAGS += $(lopsub_ldflags) para_server \ diff --git a/afs.c b/afs.c index 0946b6df..57ec1338 100644 --- a/afs.c +++ b/afs.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -888,7 +889,7 @@ static int execute_server_command(fd_set *rfds) return ret; buf[n] = '\0'; if (strcmp(buf, "new")) - return -E_BAD_CMD; + return -ERRNO_TO_PARA_ERROR(EINVAL); return open_next_audio_file(); } diff --git a/aft.c b/aft.c index bfcd1fb0..38f47fd8 100644 --- a/aft.c +++ b/aft.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "para.h" #include "error.h" diff --git a/attribute.c b/attribute.c index 4cb29828..d5f48846 100644 --- a/attribute.c +++ b/attribute.c @@ -8,6 +8,7 @@ #include #include +#include #include "para.h" #include "error.h" diff --git a/blob.c b/blob.c index ed684428..339897cb 100644 --- a/blob.c +++ b/blob.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "para.h" #include "error.h" diff --git a/client.c b/client.c index f1100df4..0582edb3 100644 --- a/client.c +++ b/client.c @@ -36,8 +36,8 @@ __printf_2_3 void (*para_log)(int, const char*, ...) = stderr_log; #ifdef HAVE_READLINE #include "interactive.h" -#include "server.completion.h" #include "afs.completion.h" +#include "server_cmd.lsg.h" struct exec_task { struct task *task; @@ -459,9 +459,13 @@ static int client_i9e_line_handler(char *line) return 1; } +I9E_DUMMY_COMPLETER(SUPERCOMMAND_UNAVAILABLE); static struct i9e_completer completers[] = { - SERVER_COMPLETERS AFS_COMPLETERS +#define LSG_SERVER_CMD_CMD(_name) {.name = #_name, \ + .completer = _name ## _completer} + LSG_SERVER_CMD_COMMANDS +#undef LSG_SERVER_CMD_CMD {.name = NULL} }; diff --git a/command.c b/command.c index fe4b9232..491ce394 100644 --- a/command.c +++ b/command.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "para.h" #include "error.h" @@ -34,14 +35,20 @@ #include "daemon.h" #include "fd.h" #include "ipc.h" +#include "server_cmd.lsg.h" #include "user_list.h" -#include "server.command_list.h" #include "afs.command_list.h" #include "signal.h" #include "version.h" +#define SERVER_CMD_AUX_INFO(_arg) _arg, +static const unsigned server_command_perms[] = {LSG_SERVER_CMD_AUX_INFOS}; +#undef SERVER_CMD_AUX_INFO +#define SERVER_CMD_AUX_INFO(_arg) #_arg, +static const char * const server_command_perms_txt[] = {LSG_SERVER_CMD_AUX_INFOS}; +#undef SERVER_CMD_AUX_INFO + typedef int server_command_handler_t(struct command_context *); -static server_command_handler_t SERVER_COMMAND_HANDLERS; server_command_handler_t AFS_COMMAND_HANDLERS; /* Defines one command of para_server. */ @@ -61,7 +68,6 @@ struct server_command { }; static struct server_command afs_cmds[] = {DEFINE_AFS_CMD_ARRAY}; -static struct server_command server_cmds[] = {DEFINE_SERVER_CMD_ARRAY}; /** Commands including options must be shorter than this. */ #define MAX_COMMAND_LEN 32768 @@ -93,21 +99,6 @@ static char *vss_status_tohuman(unsigned int flags) return para_strdup("paused"); } -/* - * return human readable permission string. Never returns NULL. - */ -static char *cmd_perms_itohuman(unsigned int perms) -{ - char *msg = para_malloc(5 * sizeof(char)); - - msg[0] = perms & AFS_READ? 'a' : '-'; - msg[1] = perms & AFS_WRITE? 'A' : '-'; - msg[2] = perms & VSS_READ? 'v' : '-'; - msg[3] = perms & VSS_WRITE? 'V' : '-'; - msg[4] = '\0'; - return msg; -} - /* * Never returns NULL. */ @@ -123,7 +114,7 @@ static char *vss_get_status_flags(unsigned int flags) return msg; } -static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly, +static unsigned get_status(struct misc_meta_data *nmmd, bool parser_friendly, char **result) { char *status, *flags; /* vss status info */ @@ -158,52 +149,6 @@ static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly, return b.offset; } -static int check_sender_args(int argc, char * const * argv, struct sender_command_data *scd) -{ - int i; - - const char *subcmds[] = {SENDER_SUBCOMMANDS NULL}; - scd->sender_num = -1; - if (argc < 3) - return -E_COMMAND_SYNTAX; - for (i = 0; senders[i].name; i++) - if (!strcmp(senders[i].name, argv[1])) - break; - PARA_DEBUG_LOG("%d:%s\n", argc, argv[1]); - if (!senders[i].name) - return -E_COMMAND_SYNTAX; - scd->sender_num = i; - for (i = 0; subcmds[i]; i++) - if (!strcmp(subcmds[i], argv[2])) - break; - if (!subcmds[i]) - return -E_COMMAND_SYNTAX; - scd->cmd_num = i; - if (!senders[scd->sender_num].client_cmds[scd->cmd_num]) - return -E_SENDER_CMD; - switch (scd->cmd_num) { - case SENDER_on: - case SENDER_off: - if (argc != 3) - return -E_COMMAND_SYNTAX; - break; - case SENDER_deny: - case SENDER_allow: - if (argc != 4 || parse_cidr(argv[3], scd->host, - sizeof(scd->host), &scd->netmask) == NULL) - return -E_COMMAND_SYNTAX; - break; - case SENDER_add: - case SENDER_delete: - if (argc != 4) - return -E_COMMAND_SYNTAX; - return parse_fec_url(argv[3], scd); - default: - return -E_COMMAND_SYNTAX; - } - return 1; -} - /** * Send a sideband packet through a blocking file descriptor. * @@ -279,6 +224,81 @@ int send_strerror(struct command_context *cc, int err) return send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s\n", para_strerror(err)); } +/** + * Send an error context to a client, + * + * \param cc Client info. + * \param errctx The error context string. + * + * \return The return value of the underlying call to send_sb_va(). + * + * This function frees the error context string after it was sent. + */ +int send_errctx(struct command_context *cc, char *errctx) +{ + int ret; + + if (!errctx) + return 0; + ret = send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s\n", errctx); + free(errctx); + return ret; +} + +static int check_sender_args(struct command_context *cc, + struct lls_parse_result *lpr, struct sender_command_data *scd) +{ + int i, ret; + const char *subcmds[] = {SENDER_SUBCOMMANDS}; + const char *arg; + char *errctx; + unsigned num_inputs = lls_num_inputs(lpr); + + scd->sender_num = -1; + ret = lls(lls_check_arg_count(lpr, 2, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + arg = lls_input(0, lpr); + for (i = 0; senders[i].name; i++) + if (!strcmp(senders[i].name, arg)) + break; + if (!senders[i].name) + return -E_COMMAND_SYNTAX; + scd->sender_num = i; + arg = lls_input(1, lpr); + for (i = 0; subcmds[i]; i++) + if (!strcmp(subcmds[i], arg)) + break; + if (!subcmds[i]) + return -E_COMMAND_SYNTAX; + scd->cmd_num = i; + if (!senders[scd->sender_num].client_cmds[scd->cmd_num]) + return -E_SENDER_CMD; + switch (scd->cmd_num) { + case SENDER_on: + case SENDER_off: + if (num_inputs != 2) + return -E_COMMAND_SYNTAX; + break; + case SENDER_deny: + case SENDER_allow: + if (num_inputs != 3 || parse_cidr(lls_input(2, lpr), scd->host, + sizeof(scd->host), &scd->netmask) == NULL) + return -E_COMMAND_SYNTAX; + break; + case SENDER_add: + case SENDER_delete: + if (num_inputs != 3) + return -E_COMMAND_SYNTAX; + return parse_fec_url(lls_input(2, lpr), scd); + default: + return -E_COMMAND_SYNTAX; + } + return 1; +} + /** * Send a sideband packet through a blocking file descriptor. * @@ -329,13 +349,13 @@ fail: return ret; } -static int com_sender(struct command_context *cc) +static int com_sender(struct command_context *cc, struct lls_parse_result *lpr) { int i, ret = 0; char *msg = NULL; struct sender_command_data scd; - if (cc->argc < 2) { + if (lls_num_inputs(lpr) == 0) { for (i = 0; senders[i].name; i++) { char *tmp; ret = xasprintf(&tmp, "%s%s\n", msg? msg : "", @@ -345,11 +365,11 @@ static int com_sender(struct command_context *cc) } return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false); } - ret = check_sender_args(cc->argc, cc->argv, &scd); + ret = check_sender_args(cc, lpr, &scd); if (ret < 0) { if (scd.sender_num < 0) return ret; - if (strcmp(cc->argv[2], "status") == 0) + if (strcmp(lls_input(1, lpr), "status") == 0) msg = senders[scd.sender_num].status(); else msg = senders[scd.sender_num].help(); @@ -360,7 +380,8 @@ static int com_sender(struct command_context *cc) case SENDER_add: case SENDER_delete: assert(senders[scd.sender_num].resolve_target); - ret = senders[scd.sender_num].resolve_target(cc->argv[3], &scd); + ret = senders[scd.sender_num].resolve_target(lls_input(2, lpr), + &scd); if (ret < 0) return ret; } @@ -380,17 +401,16 @@ static int com_sender(struct command_context *cc) } return (i < 10)? 1 : -E_LOCK; } +EXPORT_SERVER_CMD_HANDLER(sender); -/* server info */ -static int com_si(struct command_context *cc) +static int com_si(struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - int ret; char *msg, *ut; + int ret; - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; - mutex_lock(mmd_mutex); ut = daemon_get_uptime_str(now); + mutex_lock(mmd_mutex); ret = xasprintf(&msg, "up: %s\nplayed: %u\n" "server_pid: %d\n" @@ -411,19 +431,20 @@ static int com_si(struct command_context *cc) free(ut); return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false); } +EXPORT_SERVER_CMD_HANDLER(si); -/* version */ -static int com_version(struct command_context *cc) +static int com_version(struct command_context *cc, struct lls_parse_result *lpr) { char *msg; size_t len; - if (cc->argc > 1 && strcmp(cc->argv[1], "-v") == 0) + if (SERVER_CMD_OPT_GIVEN(VERSION, VERBOSE, lpr)) len = xasprintf(&msg, "%s", version_text("server")); else len = xasprintf(&msg, "%s\n", version_single_line("server")); return send_sb(&cc->scc, msg, len, SBD_OUTPUT, false); } +EXPORT_SERVER_CMD_HANDLER(version); /** These status items are cleared if no audio file is currently open. */ #define EMPTY_STATUS_ITEMS \ @@ -463,7 +484,7 @@ static int com_version(struct command_context *cc) * * This is used by vss when currently no audio file is open. */ -static unsigned empty_status_items(int parser_friendly, char **result) +static unsigned empty_status_items(bool parser_friendly, char **result) { char *esi; unsigned len; @@ -491,39 +512,16 @@ static unsigned empty_status_items(int parser_friendly, char **result) } #undef EMPTY_STATUS_ITEMS -/* stat */ -static int com_stat(struct command_context *cc) +static int com_stat(struct command_context *cc, struct lls_parse_result *lpr) { - int i, ret; + int ret; struct misc_meta_data tmp, *nmmd = &tmp; char *s; - int32_t num = 0; - int parser_friendly = 0; + bool parser_friendly = SERVER_CMD_OPT_GIVEN(STAT, PARSER_FRIENDLY, + lpr) > 0; + uint32_t num = SERVER_CMD_UINT32_VAL(STAT, NUM, lpr); para_sigaction(SIGUSR1, dummy); - - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strncmp(arg, "-n=", 3)) { - ret = para_atoi32(arg + 3, &num); - if (ret < 0) - return ret; - continue; - } - if (!strcmp(arg, "-p")) { - parser_friendly = 1; - continue; - } - return -E_COMMAND_SYNTAX; - } - if (i != cc->argc) - return -E_COMMAND_SYNTAX; for (;;) { /* * Copy the mmd structure to minimize the time we hold the mmd @@ -555,21 +553,34 @@ static int com_stat(struct command_context *cc) out: return ret; } +EXPORT_SERVER_CMD_HANDLER(stat); -static int send_list_of_commands(struct command_context *cc, struct server_command *cmd, - const char *handler) +/* fixed-length, human readable permission string */ +const char *server_cmd_perms_str(unsigned int perms) { - char *msg = NULL; + static char result[5]; + + 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; +} - for (; cmd->name; cmd++) { - char *tmp, *perms = cmd_perms_itohuman(cmd->perms); - tmp = make_message("%s\t%s\t%s\t%s\n", cmd->name, handler, - perms, cmd->description); - free(perms); - msg = para_strcat(msg, tmp); - free(tmp); +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; } - assert(msg); return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false); } @@ -578,12 +589,6 @@ static struct server_command *get_cmd_ptr(const char *name, char **handler) { struct server_command *cmd; - for (cmd = server_cmds; cmd->name; cmd++) - if (!strcmp(cmd->name, name)) { - if (handler) - *handler = para_strdup("server"); /* server commands */ - return cmd; - } /* not found, look for commands supported by afs */ for (cmd = afs_cmds; cmd->name; cmd++) if (!strcmp(cmd->name, name)) { @@ -594,75 +599,67 @@ static struct server_command *get_cmd_ptr(const char *name, char **handler) return NULL; } -/* help */ -static int com_help(struct command_context *cc) +static int com_help(struct command_context *cc, struct lls_parse_result *lpr) { - struct server_command *cmd; - char *perms, *handler, *buf; + const char *perms; + char *long_help, *buf, *errctx; int ret; + const struct lls_command *cmd; - if (cc->argc < 2) { - /* no argument given, print list of commands */ - if ((ret = send_list_of_commands(cc, server_cmds, "server")) < 0) - return ret; - return send_list_of_commands(cc, afs_cmds, "afs"); + 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 */ - cmd = get_cmd_ptr(cc->argv[1], &handler); - if (!cmd) - return -E_BAD_CMD; - perms = cmd_perms_itohuman(cmd->perms); - ret = xasprintf(&buf, "%s - %s\n\n" - "handler: %s\n" - "permissions: %s\n" - "usage: %s\n\n" - "%s\n", - cc->argv[1], - cmd->description, - handler, - perms, - cmd->usage, - cmd->help - ); - free(perms); - free(handler); + 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); } +EXPORT_SERVER_CMD_HANDLER(help); -/* hup */ -static int com_hup(struct command_context *cc) +static int com_hup(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; kill(getppid(), SIGHUP); return 1; } +EXPORT_SERVER_CMD_HANDLER(hup); -/* term */ -static int com_term(struct command_context *cc) +static int com_term(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; kill(getppid(), SIGTERM); return 1; } +EXPORT_SERVER_CMD_HANDLER(term); -static int com_play(struct command_context *cc) +static int com_play(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; mutex_lock(mmd_mutex); mmd->new_vss_status_flags |= VSS_PLAYING; mmd->new_vss_status_flags &= ~VSS_NOMORE; mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(play); -/* stop */ -static int com_stop(struct command_context *cc) +static int com_stop(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; mutex_lock(mmd_mutex); mmd->new_vss_status_flags &= ~VSS_PLAYING; mmd->new_vss_status_flags &= ~VSS_REPOS; @@ -670,12 +667,11 @@ static int com_stop(struct command_context *cc) mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(stop); -/* pause */ -static int com_pause(struct command_context *cc) +static int com_pause(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; mutex_lock(mmd_mutex); if (!vss_paused() && !vss_stopped()) { mmd->events++; @@ -685,42 +681,44 @@ static int com_pause(struct command_context *cc) mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(pause); -/* next */ -static int com_next(struct command_context *cc) +static int com_next(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; mutex_lock(mmd_mutex); mmd->events++; mmd->new_vss_status_flags |= VSS_NEXT; mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(next); -/* nomore */ -static int com_nomore(struct command_context *cc) +static int com_nomore(__a_unused struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { - if (cc->argc != 1) - return -E_COMMAND_SYNTAX; mutex_lock(mmd_mutex); if (vss_playing() || vss_paused()) mmd->new_vss_status_flags |= VSS_NOMORE; mutex_unlock(mmd_mutex); return 1; } +EXPORT_SERVER_CMD_HANDLER(nomore); -/* ff */ -static int com_ff(struct command_context *cc) +static int com_ff(__a_unused struct command_context *cc, + struct lls_parse_result *lpr) { long promille; int ret, backwards = 0; unsigned i; - char c; + char c, *errctx; - if (cc->argc != 2) - return -E_COMMAND_SYNTAX; - if (!(ret = sscanf(cc->argv[1], "%u%c", &i, &c))) + ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + if (!(ret = sscanf(lls_input(0, lpr), "%u%c", &i, &c))) return -E_COMMAND_SYNTAX; if (ret > 1 && c == '-') backwards = 1; /* jmp backwards */ @@ -748,17 +746,22 @@ out: mutex_unlock(mmd_mutex); return ret; } +EXPORT_SERVER_CMD_HANDLER(ff); -/* jmp */ -static int com_jmp(struct command_context *cc) +static int com_jmp(__a_unused struct command_context *cc, + struct lls_parse_result *lpr) { long unsigned int i; int ret; + char *errctx; - if (cc->argc != 2) - return -E_COMMAND_SYNTAX; - if (sscanf(cc->argv[1], "%lu", &i) <= 0) - return -E_COMMAND_SYNTAX; + ret = lls(lls_check_arg_count(lpr, 1, 1, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + if (sscanf(lls_input(0, lpr), "%lu", &i) <= 0) + return -ERRNO_TO_PARA_ERROR(EINVAL); mutex_lock(mmd_mutex); ret = -E_NO_AUDIO_FILE; if (!mmd->afd.afhi.chunks_total) @@ -777,16 +780,16 @@ out: mutex_unlock(mmd_mutex); return ret; } +EXPORT_SERVER_CMD_HANDLER(jmp); -static int com_tasks(struct command_context *cc) +static int com_tasks(struct command_context *cc, + __a_unused struct lls_parse_result *lpr) { char *tl = server_get_tasks(); - int ret = 1; - - if (tl) - ret = send_sb(&cc->scc, tl, strlen(tl), SBD_OUTPUT, false); - return ret; + assert(tl); + return send_sb(&cc->scc, tl, strlen(tl), SBD_OUTPUT, false); } +EXPORT_SERVER_CMD_HANDLER(tasks); /* * check if perms are sufficient to exec a command having perms cmd_perms. @@ -858,18 +861,34 @@ static int run_command(struct command_context *cc, struct iovec *iov, { int ret, i; char *p, *end; - struct server_command *cmd; + struct server_command *cmd = NULL; + const struct lls_command *lcmd = NULL; + unsigned perms; + struct lls_parse_result *lpr; + char *errctx; if (iov->iov_base == NULL || iov->iov_len == 0) - return -E_BAD_CMD; + return -ERRNO_TO_PARA_ERROR(EINVAL); p = iov->iov_base; p[iov->iov_len - 1] = '\0'; /* just to be sure */ - cmd = get_cmd_ptr(p, NULL); - if (!cmd) - return -E_BAD_CMD; - ret = check_perms(cc->u->perms, cmd); - if (ret < 0) - return ret; + + ret = lls(lls_lookup_subcmd(p, server_cmd_suite, &errctx)); + if (ret >= 0) { + perms = server_command_perms[ret]; + if ((perms & cc->u->perms) != perms) + return -E_PERM; + lcmd = lls_cmd(ret, server_cmd_suite); + } else { + cmd = get_cmd_ptr(p, NULL); + if (!cmd) { + send_errctx(cc, errctx); + return ret; + } + perms = cmd->perms; + ret = check_perms(cc->u->perms, cmd); + if (ret < 0) + return ret; + } end = iov->iov_base + iov->iov_len; for (i = 0; p < end; i++) p += strlen(p) + 1; @@ -880,13 +899,23 @@ static int run_command(struct command_context *cc, struct iovec *iov, p += strlen(p) + 1; } cc->argv[cc->argc] = NULL; - PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cmd->name, - cc->u->name, peername); - ret = cmd->handler(cc); + PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", lcmd? + lls_command_name(lcmd) : cmd->name, cc->u->name, peername); + if (lcmd) { + ret = lls(lls_parse(cc->argc, cc->argv, lcmd, &lpr, &errctx)); + if (ret >= 0) { + const struct server_cmd_user_data *ud = lls_user_data(lcmd); + ret = ud->handler(cc, lpr); + lls_free_parse_result(lpr, lcmd); + } else + send_errctx(cc, errctx); + } else { + ret = cmd->handler(cc); + } free_argv(cc->argv); mutex_lock(mmd_mutex); mmd->num_commands++; - if (ret >= 0 && (cmd->perms & AFS_WRITE)) + if (ret >= 0 && (perms & AFS_WRITE)) mmd->events++; mutex_unlock(mmd_mutex); return ret; diff --git a/command.h b/command.h index 5e1a4ce3..8a117b9c 100644 --- a/command.h +++ b/command.h @@ -14,11 +14,51 @@ struct command_context { struct stream_cipher_context scc; }; +/** Prototype of a server command handler. */ +typedef int (*server_cmd_handler_t)(struct command_context *, + struct lls_parse_result *); + +/** + * The lopsub user data structure for server commands. + * + * One such structure exists for each server command. Lopsub maintains + * references to the user data structures and provides lls_user_data() for + * applications to fetch the user data pointer of a given command. This + * mechanism is used by the dispatcher of command.c to run the command handler. + */ +struct server_cmd_user_data { + /** Pointer to the command handler. */ + server_cmd_handler_t handler; +}; + +/** Define the user data structure for one command. */ +#define EXPORT_SERVER_CMD_HANDLER(_cmd) \ + const struct server_cmd_user_data lsg_server_cmd_com_ ## _cmd ## _user_data = { \ + .handler = com_ ## _cmd \ + }; + +/** Get the lopsub command pointer by command name. */ +#define SERVER_CMD_CMD_PTR(_cmd) \ + (lls_cmd(LSG_SERVER_CMD_CMD_ ## _cmd, server_cmd_suite)) + +/** Get the lopsub parse result of an option. */ +#define SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr) \ + (lls_opt_result(LSG_SERVER_CMD_ ## _cmd ## _OPT_ ## _opt, _lpr)) + +/** How many times an option was given. */ +#define SERVER_CMD_OPT_GIVEN(_cmd, _opt, _lpr) \ + (lls_opt_given(SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr))) + +/** Fetch the (first) argument given to an option of type uint32. */ +#define SERVER_CMD_UINT32_VAL(_cmd, _opt, _lpr) \ + (lls_uint32_val(0, SERVER_CMD_OPT_RESULT(_cmd, _opt, _lpr))) + int send_sb(struct stream_cipher_context *scc, void *buf, size_t numbytes, int band, bool dont_free); __printf_3_4 int send_sb_va(struct stream_cipher_context *scc, int band, const char *fmt, ...); int send_strerror(struct command_context *cc, int err); +int send_errctx(struct command_context *cc, char *errctx); int recv_sb(struct stream_cipher_context *scc, enum sb_designator expected_band, size_t max_size, struct iovec *result); diff --git a/error.h b/error.h index 0af0dec9..346ef50f 100644 --- a/error.h +++ b/error.h @@ -56,7 +56,6 @@ PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \ PARA_ERROR(BAD_CHANNEL_COUNT, "channel count not supported"), \ PARA_ERROR(BAD_CHANNEL, "invalid channel"), \ - PARA_ERROR(BAD_CMD, "invalid command"), \ PARA_ERROR(BAD_CONFIG, "syntax error in config file"), \ PARA_ERROR(BAD_CT, "invalid chunk table or bad FEC configuration"), \ PARA_ERROR(BAD_FEATURE, "invalid feature request"), \ diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4 new file mode 100644 index 00000000..0585b5e2 --- /dev/null +++ b/m4/lls/server_cmd.suite.m4 @@ -0,0 +1,177 @@ +[suite server_cmd] +caption = list of server commands +aux_info_prefix = Permissions: + +[introduction] + The server process listens on a network socket and accepts connections + from para_client or para_audiod. For the connection to succeed the + connecting peer must authenticate as one of the users stored in the + user table of para_server. Each entry of the user table contains the + set of permission bits that are granted to the user. Authenticated + users may execute one of the commands below if the set of permission + bits of the command is a subset of the permission bits that are + granted to the user. +[/introduction] + +[subcommand ff] + purpose = jump N seconds forward or backward + synopsis = n[-] + aux_info = VSS_READ | VSS_WRITE + [description] + This sets the 'R' (reposition request) bit of the vss status flags + which enqueues a request to jump n seconds forwards or backwards. + + Example: + + para_client ff 30- + + jumps 30 seconds backwards. + + [/description] + +[subcommand help] + purpose = list available commands or print command-specific help + 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. + [/description] + +[subcommand hup] + purpose = reload config file, log file and user list + aux_info = VSS_WRITE + [description] + Reread the config file and the user list file, close and reopen the log + file, and ask the afs process to do the same. Sending the HUP signal + to the server process has the same effect as running this command. + [/description] + +[subcommand jmp] + purpose = reposition the current stream + non-opts-name = n + aux_info = VSS_READ | VSS_WRITE + [description] + Set the 'R' (reposition request) bit of the vss status flags and + enqueue a request to jump to n% of the current audio file, where 0 <= + n <= 100. + [/description] + +[subcommand next] + purpose = close the stream and start to stream the next audio file + aux_info = VSS_READ | VSS_WRITE + [description] + Set the 'N' (next audio file) bit of the vss status flags. This + instructs the server to close the current stream, if any. The 'P' + (playing) bit is not modified by this command. If it is on, playing + continues with the next audio file. + + This command is equivalent to stop if paused, and has no effect + if stopped. + [/description] + +[subcommand nomore] + purpose = stop playing after current audio file + aux_info = VSS_READ | VSS_WRITE + [description] + Set the 'O' (no more) bit of the vss status flags which asks + para_server to clear the 'P' (playing) bit after the 'N' (next audio + file) bit transitions from off to on (because the end of the current + audio file is reached). Use this command instead of stop if you don't + like sudden endings. + [/description] + +[subcommand pause] + purpose = suspend the current stream + aux_info = VSS_READ | VSS_WRITE + [description] + Clear the 'P' (playing) bit of the vss status flags. + [/description] + +[subcommand play] + purpose = start or resume playback + aux_info = VSS_READ | VSS_WRITE + [description] + Set the 'P' (playing) bit of the vss status flags. + [/description] + +[subcommand sender] + purpose = control paraslash senders + synopsis = [sender cmd [arguments]] + aux_info = VSS_READ | VSS_WRITE + [description] + Send a command to a specific sender. The following commands are + available, but not all senders support every command. + + help, on, off, add, delete, allow, deny, status. + + The help command prints the help text of the given sender. If no + command is given the list of available senders is shown. + + Example: + + para_client sender http help + + [/description] + +[subcommand si] + purpose = print server info + aux_info = NO_PERMISSION_REQUIRED + [description] + Show server and afs PID, number of connections, uptime and more. + [/description] + +[subcommand stat] + purpose = print information about the current audio file + aux_info = VSS_READ + [option num] + short_opt = n + summary = number of times to show the status info + arg_info = required_arg + arg_type = uint32 + typestr = num + [help] + Exit after the status information has been shown num times. If this + option is not given, the command runs in an endless loop. + [/help] + [option parser-friendly] + short_opt = p + summary = enable parser-friendly output + [help] + Show status item identifiers as numerical values and prefix each + status item with its size in bytes. + [/help] + +[subcommand stop] + purpose = stop playback + aux_info = VSS_READ | VSS_WRITE + [description] + Clear the 'P' (playing) bit and set the 'N' (next audio file) bit of + the vss status flags, effectively stopping playback. + [/description] + +[subcommand tasks] + purpose = list active server tasks + aux_info = NO_PERMISSION_REQUIRED + [description] + For each task, print ID, status and name. This is mostly useful + for debugging. + [/description] + +[subcommand term] + purpose = ask the server to terminate + aux_info = VSS_READ | VSS_WRITE + [description] + Shut down the server. Instead of this command, you can also send + SIGINT or SIGTERM to the para_server process. It should never be + necessary to send SIGKILL. + [/description] + +[subcommand version] + purpose = print the git version string of para_server + aux_info = NO_PERMISSION_REQUIRED + [option verbose] + short_opt = v + summary = print detailed (multi-line) version text + diff --git a/server.cmd b/server.cmd index 5f46ba1e..b7d0af1a 100644 --- a/server.cmd +++ b/server.cmd @@ -1,129 +1,3 @@ BN: server SF: command.c SN: list of server commands ---- -N: ff -P: VSS_READ | VSS_WRITE -D: Jump N seconds forward or backward. -U: ff n[-] -H: This sets the 'R' (reposition request) bit of the vss status flags -H: which enqueues a request to jump n seconds forwards or backwards. -H: -H: Example: -H: -H: para_client ff 30- -H: -H: jumps 30 seconds backwards. ---- -N: help -P: 0 -D: Print online help. -U: help [command] -H: Without any arguments, help prints a list of available commands. When -H: called with a command name as first argument, it prints the description -H: of that command. ---- -N: hup -P: VSS_WRITE -D: Reload config file, log file and user list. -U: hup -H: Reread the config file and the user list file, close and reopen the log -H: file, and ask the afs process to do the same. Sending the HUP signal to -H: the server process has the same effect. ---- -N: jmp -P: VSS_READ | VSS_WRITE -D: Jump to the given position. -U: jmp n -H: Set the 'R' (reposition request) bit of the vss status flags and enqueue a -H: request to jump to n% of the current audio file, where 0 <= n <= 100. ---- -N: next -P: VSS_READ | VSS_WRITE -D: Close the current audio file. -U: next -H: Set the 'N' (next audio file) bit of the vss status flags which instructs the -H: server to close its current audio file if necessary. If the 'P' bit (playing) -H: is on, playing continues with the next audio file. This command is equivalent -H: to stop if paused, and has no effect if stopped. ---- -N: nomore -P: VSS_READ | VSS_WRITE -D: Stop playing after current audio file. -U: nomore -H: Set the 'O' (no more) bit of the vss status flags which asks para_server to -H: clear the 'P' (playing) bit after the 'N' (next audio file) bit transitions -H: from off to on (because the end of the current audio file is reached). Use this -H: command instead of stop if you don't like sudden endings. ---- -N: pause -P: VSS_READ | VSS_WRITE -D: Pause current audio file. -U: pause -H: Clear the 'P' (playing) bit of the vss status flags. ---- -N: play -P: VSS_READ | VSS_WRITE -D: Start or resume playing. -U: play -H: Set the 'P' (playing) bit of the vss status flags. ---- -N: sender -P: VSS_READ | VSS_WRITE -D: Control paraslash senders. -U: sender [s cmd [arguments]] -H: Send a command to a specific sender. The following commands are available, but -H: not all senders support every command. -H: -H: help, on, off, add, delete, allow, deny, status. -H: -H: The help command prints the help text of the given sender. If no command is -H: given the list of compiled in senders is shown. -H: -H: Example: -H: -H: para_client sender http help ---- -N: si -P: 0 -D: Print server info. -U: si -H: Show server and afs PID, number of connections, uptime and more. ---- -N: stat -P: VSS_READ -D: Print information about the current audio file. -U: stat [-n=num] [-p] -H: If -n is given, exit after the status information has been shown n times. -H: Otherwise, the command runs in an endless loop. -H: -H: The -p option activates parser-friendly output: Each status item is -H: prefixed with its size in bytes and the status item identifiers are -H: printed as numerical values. ---- -N: stop -P: VSS_READ | VSS_WRITE -D: Stop playing. -U: stop -H: Clear the 'P' (playing) bit and set the 'N' (next audio file) bit of the vss -H: status flags, effectively stopping playback. ---- -N: tasks -P: 0 -D: List server tasks. -U: tasks -H: For each task, print ID, status and name. ---- -N: term -P: VSS_READ | VSS_WRITE -D: Ask the server to terminate. -U: term -H: Shut down the server. Instead of this command, you can also send SIGINT or -H: SIGTERM to the para_server process. It should never be necessary to send -H: SIGKILL. ---- -N: version -P: 0 -D: Print the git version string of para_server. -U: version -H: Show version and other info. diff --git a/t/t0004-server.sh b/t/t0004-server.sh index 1963748f..5329ef0f 100755 --- a/t/t0004-server.sh +++ b/t/t0004-server.sh @@ -28,7 +28,7 @@ declare -a commands=() cmdline=() required_objects=() good=() bad=() i=0 commands[$i]="help" cmdline[$i]="help" -good[$i]='help server ----' +good[$i]='help ----' let i++ commands[$i]="init" diff --git a/t/t0005-man.sh b/t/t0005-man.sh index fa32d411..8f966cd5 100755 --- a/t/t0005-man.sh +++ b/t/t0005-man.sh @@ -56,7 +56,7 @@ missing_objects="$result" if [[ -n "$missing_objects" ]]; then test_skip "para_server" "missing object(s): $missing_objects" else - regex='LIST OF SERVER COMMANDS.\{100,\}LIST OF AFS COMMANDS' + regex='LIST OF SERVER COMMANDS.\{100,\}' test_expect_success 'para_server: server/afs commands' \ "grep_man '$regex' server" fi diff --git a/user_list.h b/user_list.h index 3a77e98f..948ed503 100644 --- a/user_list.h +++ b/user_list.h @@ -15,6 +15,7 @@ * is read at server startup. */ enum server_command_permissions { + NO_PERMISSION_REQUIRED = 0, /** None of the below. */ AFS_READ = 1, /** Read-only operation on the AFS database. */ AFS_WRITE = 2, /** Read-write operation on the AFS database. */ VSS_READ = 4, /** Read-only operation on the virtual streaming system. */ -- 2.30.2