From b96a74cbfd0e8cfc755d36cfa5ee8c4d95e89dda Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 12 Mar 2021 17:13:28 +0100 Subject: [PATCH] MINOR: cli: filter the list of commands to the matching part The error message on the CLI has become unreadable due to the long list and it's not even sorted, making it even harder to figure the right command. This patch starts by looking if some of the words match something known, and if so, will limit the listing only to those commands that start like the current one. The "help", "prompt" and "quit" commands are always shown to help the user try something else. Now thanks to this, typing "add" or "del" will only list "add acl", "add map" and not 50 lines anymore. As a small bonus, we won't print "Unknown command" anymore in response to the "help" command. --- src/cli.c | 82 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/src/cli.c b/src/cli.c index f4aefaf4e..027272611 100644 --- a/src/cli.c +++ b/src/cli.c @@ -68,13 +68,6 @@ static struct applet cli_applet; static struct applet mcli_applet; -static const char stats_sock_usage_msg[] = - "Unknown command. Please enter one of the following commands only :\n" - " help : this message\n" - " prompt : toggle interactive mode with prompt\n" - " quit : disconnect\n" - ""; - static const char stats_permission_denied_msg[] = "Permission denied\n" ""; @@ -91,20 +84,54 @@ extern const char *stat_status_codes[]; static struct proxy *mworker_proxy; /* CLI proxy of the master */ -static char *cli_gen_usage_msg(struct appctx *appctx) +/* This will show the help message and list the commands supported at the + * current level that match all of the first words of if args is not + * NULL, or all args if none matches or if args is null. + */ +static char *cli_gen_usage_msg(struct appctx *appctx, char * const *args) { struct cli_kw_list *kw_list; struct cli_kw *kw; struct buffer *tmp = get_trash_chunk(); struct buffer out; + int idx; + int length = 0; ha_free(&dynamic_usage_msg); - if (LIST_ISEMPTY(&cli_keywords.list)) - goto end; + /* first, let's measure the longest match */ + list_for_each_entry(kw_list, &cli_keywords.list, list) { + for (kw = &kw_list->kw[0]; kw->str_kw[0]; kw++) { + if (kw->level & ~appctx->cli_level & (ACCESS_MASTER_ONLY|ACCESS_EXPERT)) + continue; + if ((appctx->cli_level & ~kw->level & (ACCESS_MASTER_ONLY|ACCESS_MASTER)) == + (ACCESS_MASTER_ONLY|ACCESS_MASTER)) + continue; + + /* OK this command is visible */ + for (idx = 0; idx < CLI_PREFIX_KW_NB; idx++) { + if (!kw->str_kw[idx]) + break; // end of keyword + if (!args || !args[idx] || !*args[idx]) + break; // end of command line + if (strcmp(kw->str_kw[idx], args[idx]) != 0) + break; + if (idx + 1 > length) + length = idx + 1; + } + } + } + + /* now equals the number of matching words */ chunk_reset(tmp); - chunk_strcat(tmp, stats_sock_usage_msg); + if (!args) // this is the help message. + chunk_strcat(tmp, "The following commands are valid at this level:\n"); + else if (!length) // no match + chunk_strcat(tmp, "Unknown command. Please enter one of the following commands only:\n"); + else // partial match + chunk_strcat(tmp, "Unknown command, but maybe one of the following ones is a better match:\n"); + list_for_each_entry(kw_list, &cli_keywords.list, list) { for (kw = &kw_list->kw[0]; kw->str_kw[0]; kw++) { @@ -121,23 +148,32 @@ static char *cli_gen_usage_msg(struct appctx *appctx) (ACCESS_MASTER_ONLY|ACCESS_MASTER)) continue; - if (kw->usage) + for (idx = 0; idx < length; idx++) { + if (!kw->str_kw[idx]) + break; // end of keyword + if (!args || !args[idx] || !*args[idx]) + break; // end of command line + if (strcmp(kw->str_kw[idx], args[idx]) != 0) + break; + } + + if (kw->usage && idx == length) chunk_appendf(tmp, " %s\n", kw->usage); } } + + /* always show the prompt/help/quit commands */ + chunk_strcat(tmp, + " help : full commands list\n" + " prompt : toggle interactive mode with prompt\n" + " quit : disconnect\n"); + chunk_init(&out, NULL, 0); chunk_dup(&out, tmp); dynamic_usage_msg = out.area; -end: - if (dynamic_usage_msg) { - appctx->ctx.cli.severity = LOG_INFO; - appctx->ctx.cli.msg = dynamic_usage_msg; - } - else { - appctx->ctx.cli.severity = LOG_INFO; - appctx->ctx.cli.msg = stats_sock_usage_msg; - } + appctx->ctx.cli.severity = LOG_INFO; + appctx->ctx.cli.msg = dynamic_usage_msg; appctx->st0 = CLI_ST_PRINT; return dynamic_usage_msg; @@ -594,7 +630,7 @@ static int cli_parse_request(struct appctx *appctx) (kw->level & ~appctx->cli_level & ACCESS_MASTER_ONLY) || (appctx->cli_level & ~kw->level & (ACCESS_MASTER_ONLY|ACCESS_MASTER)) == (ACCESS_MASTER_ONLY|ACCESS_MASTER)) { /* keyword not found in this mode */ - cli_gen_usage_msg(appctx); + cli_gen_usage_msg(appctx, args); return 0; } @@ -1879,7 +1915,7 @@ static int cli_parse_simple(char **args, char *payload, struct appctx *appctx, v { if (*args[0] == 'h') /* help */ - cli_gen_usage_msg(appctx); + cli_gen_usage_msg(appctx, NULL); else if (*args[0] == 'p') /* prompt */ appctx->st1 ^= APPCTX_CLI_ST1_PROMPT;