diff options
Diffstat (limited to 'ctdb/common/cmdline.c')
-rw-r--r-- | ctdb/common/cmdline.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/ctdb/common/cmdline.c b/ctdb/common/cmdline.c new file mode 100644 index 0000000..ce368a9 --- /dev/null +++ b/ctdb/common/cmdline.c @@ -0,0 +1,598 @@ +/* + Command line processing + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/debug.h" + +#include "common/cmdline.h" + +#define CMDLINE_MAX_LEN 80 + +struct cmdline_section { + const char *name; + struct cmdline_command *commands; +}; + +struct cmdline_context { + const char *prog; + struct poptOption *options; + struct cmdline_section *section; + int num_sections; + size_t max_len; + poptContext pc; + int argc, arg0; + const char **argv; + struct cmdline_command *match_cmd; +}; + +static bool cmdline_show_help = false; + +static void cmdline_popt_help(poptContext pc, + enum poptCallbackReason reason, + struct poptOption *key, + const char *arg, + void *data) +{ + if (key->shortName == 'h') { + cmdline_show_help = true; + } +} + +struct poptOption cmdline_help_options[] = { + { NULL, '\0', POPT_ARG_CALLBACK, cmdline_popt_help, 0, NULL, NULL }, + { "help", 'h', 0, NULL, 'h', "Show this help message", NULL }, + POPT_TABLEEND +}; + +#define CMDLINE_HELP_OPTIONS \ + { NULL, '\0', POPT_ARG_INCLUDE_TABLE, cmdline_help_options, \ + 0, "Help Options:", NULL } + +static bool cmdline_option_check(struct poptOption *option) +{ + if (option->longName == NULL) { + D_ERR("Option has no long name\n"); + return false; + } + + if (option->argInfo != POPT_ARG_STRING && + option->argInfo != POPT_ARG_INT && + option->argInfo != POPT_ARG_LONG && + option->argInfo != POPT_ARG_VAL && + option->argInfo != POPT_ARG_FLOAT && + option->argInfo != POPT_ARG_DOUBLE) { + D_ERR("Option '%s' has unsupported type\n", option->longName); + return false; + } + + if (option->arg == NULL) { + D_ERR("Option '%s' has invalid arg\n", option->longName); + return false; + } + + if (option->descrip == NULL) { + D_ERR("Option '%s' has no help msg\n", option->longName); + return false; + } + + return true; +} + +static bool cmdline_options_check(struct poptOption *options) +{ + int i; + bool ok; + + if (options == NULL) { + return true; + } + + i = 0; + while (options[i].longName != NULL || options[i].shortName != '\0') { + ok = cmdline_option_check(&options[i]); + if (!ok) { + return false; + } + i++; + } + + return true; +} + +static int cmdline_options_define(TALLOC_CTX *mem_ctx, + struct poptOption *user_options, + struct poptOption **result) +{ + struct poptOption *options; + int count, i; + + count = (user_options == NULL ? 2 : 3); + + options = talloc_array(mem_ctx, struct poptOption, count); + if (options == NULL) { + return ENOMEM; + } + + i = 0; + options[i++] = (struct poptOption) CMDLINE_HELP_OPTIONS; + if (user_options != NULL) { + options[i++] = (struct poptOption) { + .argInfo = POPT_ARG_INCLUDE_TABLE, + .arg = user_options, + .descrip = "Options:", + }; + } + options[i++] = (struct poptOption) POPT_TABLEEND; + + *result = options; + return 0; +} + +static bool cmdline_command_check(struct cmdline_command *cmd, size_t *max_len) +{ + size_t len; + + if (cmd->name == NULL) { + return false; + } + + if (cmd->fn == NULL) { + D_ERR("Command '%s' has no implementation function\n", + cmd->name); + return false; + } + + if (cmd->msg_help == NULL) { + D_ERR("Command '%s' has no help msg\n", cmd->name); + return false; + } + + len = strlen(cmd->name); + if (cmd->msg_args != NULL) { + len += strlen(cmd->msg_args); + } + if (len > CMDLINE_MAX_LEN) { + D_ERR("Command '%s' is too long (%zu)\n", cmd->name, len); + return false; + } + + if (len > *max_len) { + *max_len = len; + } + + len = strlen(cmd->msg_help); + if (len > CMDLINE_MAX_LEN) { + D_ERR("Command '%s' help too long (%zu)\n", cmd->name, len); + return false; + } + + return true; +} + +static bool cmdline_commands_check(struct cmdline_command *commands, + size_t *max_len) +{ + int i; + bool ok; + + if (commands == NULL) { + return false; + } + + for (i=0; commands[i].name != NULL; i++) { + ok = cmdline_command_check(&commands[i], max_len); + if (!ok) { + return false; + } + } + + return true; +} + +static int cmdline_context_destructor(struct cmdline_context *cmdline); + +static int cmdline_section_add(struct cmdline_context *cmdline, + const char *name, + struct cmdline_command *commands) +{ + struct cmdline_section *section; + size_t max_len = 0; + bool ok; + + ok = cmdline_commands_check(commands, &max_len); + if (!ok) { + return EINVAL; + } + + section = talloc_realloc(cmdline, + cmdline->section, + struct cmdline_section, + cmdline->num_sections + 1); + if (section == NULL) { + return ENOMEM; + } + + section[cmdline->num_sections] = (struct cmdline_section) { + .name = name, + .commands = commands, + }; + + if (max_len > cmdline->max_len) { + cmdline->max_len = max_len; + } + + cmdline->section = section; + cmdline->num_sections += 1; + + return 0; +} + +int cmdline_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + const char *name, + struct cmdline_command *commands, + struct cmdline_context **result) +{ + struct cmdline_context *cmdline; + int ret; + bool ok; + + if (prog == NULL) { + return EINVAL; + } + + ok = cmdline_options_check(options); + if (!ok) { + return EINVAL; + } + + cmdline = talloc_zero(mem_ctx, struct cmdline_context); + if (cmdline == NULL) { + return ENOMEM; + } + + cmdline->prog = talloc_strdup(cmdline, prog); + if (cmdline->prog == NULL) { + talloc_free(cmdline); + return ENOMEM; + } + + ret = cmdline_options_define(cmdline, options, &cmdline->options); + if (ret != 0) { + talloc_free(cmdline); + return ret; + } + + ret = cmdline_section_add(cmdline, name, commands); + if (ret != 0) { + talloc_free(cmdline); + return ret; + } + + cmdline->argc = 1; + cmdline->argv = talloc_array(cmdline, const char *, 2); + if (cmdline->argv == NULL) { + talloc_free(cmdline); + return ENOMEM; + } + cmdline->argv[0] = cmdline->prog; + cmdline->argv[1] = NULL; + + /* Dummy popt context for generating help */ + cmdline->pc = poptGetContext(cmdline->prog, + cmdline->argc, + cmdline->argv, + cmdline->options, + 0); + if (cmdline->pc == NULL) { + talloc_free(cmdline); + return ENOMEM; + } + + talloc_set_destructor(cmdline, cmdline_context_destructor); + + *result = cmdline; + return 0; +} + +static int cmdline_context_destructor(struct cmdline_context *cmdline) +{ + if (cmdline->pc != NULL) { + poptFreeContext(cmdline->pc); + } + + return 0; +} + +int cmdline_add(struct cmdline_context *cmdline, + const char *name, + struct cmdline_command *commands) +{ + return cmdline_section_add(cmdline, name, commands); +} + +static int cmdline_parse_options(struct cmdline_context *cmdline, + int argc, + const char **argv) +{ + int opt; + + if (cmdline->pc != NULL) { + poptFreeContext(cmdline->pc); + } + + cmdline->pc = poptGetContext(cmdline->prog, + argc, + argv, + cmdline->options, + 0); + if (cmdline->pc == NULL) { + return ENOMEM; + } + + while ((opt = poptGetNextOpt(cmdline->pc)) != -1) { + D_ERR("Invalid option %s: %s\n", + poptBadOption(cmdline->pc, 0), + poptStrerror(opt)); + return EINVAL; + } + + /* Set up remaining arguments for commands */ + cmdline->argc = 0; + cmdline->argv = poptGetArgs(cmdline->pc); + if (cmdline->argv != NULL) { + while (cmdline->argv[cmdline->argc] != NULL) { + cmdline->argc++; + } + } + + return 0; +} + +static int cmdline_match_section(struct cmdline_context *cmdline, + struct cmdline_section *section) +{ + int i; + + for (i=0; section->commands[i].name != NULL; i++) { + struct cmdline_command *cmd; + char name[CMDLINE_MAX_LEN+1]; + size_t len; + char *t, *str; + int n = 0; + bool match = false; + + cmd = §ion->commands[i]; + len = strlcpy(name, cmd->name, sizeof(name)); + if (len >= sizeof(name)) { + D_ERR("Skipping long command '%s'\n", cmd->name); + continue; + } + + str = name; + while ((t = strtok(str, " ")) != NULL) { + if (n >= cmdline->argc) { + match = false; + break; + } + if (cmdline->argv[n] == NULL) { + match = false; + break; + } + if (strcmp(cmdline->argv[n], t) == 0) { + match = true; + cmdline->arg0 = n+1; + } else { + match = false; + break; + } + + n += 1; + str = NULL; + } + + if (match) { + cmdline->match_cmd = cmd; + return 0; + } + } + + cmdline->match_cmd = NULL; + return ENOENT; +} + +static int cmdline_match(struct cmdline_context *cmdline) +{ + int i, ret = ENOENT; + + if (cmdline->argc == 0 || cmdline->argv == NULL) { + cmdline->match_cmd = NULL; + return EINVAL; + } + + for (i=0; i<cmdline->num_sections; i++) { + ret = cmdline_match_section(cmdline, &cmdline->section[i]); + if (ret == 0) { + break; + } + } + + return ret; +} + +int cmdline_parse(struct cmdline_context *cmdline, + int argc, + const char **argv, + bool parse_options) +{ + int ret; + + if (argc < 2) { + cmdline_usage(cmdline, NULL); + return EINVAL; + } + + cmdline_show_help = false; + + if (parse_options) { + ret = cmdline_parse_options(cmdline, argc, argv); + if (ret != 0) { + cmdline_usage(cmdline, NULL); + return ret; + } + } else { + cmdline->argc = argc; + cmdline->argv = argv; + } + + ret = cmdline_match(cmdline); + + if (ret != 0 || cmdline_show_help) { + const char *name = NULL; + + if (cmdline->match_cmd != NULL) { + name = cmdline->match_cmd->name; + } + + cmdline_usage(cmdline, name); + + if (cmdline_show_help) { + ret = EAGAIN; + } + } + + return ret; +} + +static void cmdline_usage_command(struct cmdline_context *cmdline, + struct cmdline_command *cmd, + bool print_all) +{ + size_t len; + + len = strlen(cmd->name); + + printf(" %s ", cmd->name); + if (print_all) { + printf("%-*s", + (int)(cmdline->max_len-len), + cmd->msg_args == NULL ? "" : cmd->msg_args); + } else { + printf("%s", cmd->msg_args == NULL ? "" : cmd->msg_args); + } + printf(" %s\n", cmd->msg_help); +} + +static void cmdline_usage_section(struct cmdline_context *cmdline, + struct cmdline_section *section) +{ + int i; + + printf("\n"); + + if (section->name != NULL) { + printf("%s ", section->name); + } + printf("Commands:\n"); + for (i=0; section->commands[i].name != NULL; i++) { + cmdline_usage_command(cmdline, §ion->commands[i], true); + + } +} + +static void cmdline_usage_full(struct cmdline_context *cmdline) +{ + int i; + + poptSetOtherOptionHelp(cmdline->pc, "[<options>] <command> [<args>]"); + poptPrintHelp(cmdline->pc, stdout, 0); + + for (i=0; i<cmdline->num_sections; i++) { + cmdline_usage_section(cmdline, &cmdline->section[i]); + } +} + +void cmdline_usage(struct cmdline_context *cmdline, const char *cmd_name) +{ + struct cmdline_command *cmd = NULL; + int i, j; + + if (cmd_name == NULL) { + cmdline_usage_full(cmdline); + return; + } + + for (j=0; j<cmdline->num_sections; j++) { + struct cmdline_section *section = &cmdline->section[j]; + + for (i=0; section->commands[i].name != NULL; i++) { + if (strcmp(section->commands[i].name, cmd_name) == 0) { + cmd = §ion->commands[i]; + break; + } + } + } + + if (cmd == NULL) { + cmdline_usage_full(cmdline); + return; + } + + poptSetOtherOptionHelp(cmdline->pc, "<command> [<args>]"); + poptPrintUsage(cmdline->pc, stdout, 0); + + printf("\n"); + cmdline_usage_command(cmdline, cmd, false); +} + +int cmdline_run(struct cmdline_context *cmdline, + void *private_data, + int *result) +{ + struct cmdline_command *cmd = cmdline->match_cmd; + TALLOC_CTX *tmp_ctx; + int ret; + + if (cmd == NULL) { + return ENOENT; + } + + tmp_ctx = talloc_new(cmdline); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = cmd->fn(tmp_ctx, + cmdline->argc - cmdline->arg0, + &cmdline->argv[cmdline->arg0], + private_data); + + talloc_free(tmp_ctx); + + if (result != NULL) { + *result = ret; + } + return 0; +} |