The CLI utils lctl and lfs currently use a pseudo option
--non-existent-option to generate a list of completions. However, this
was broken when the help output for an invalid command was changed.
Using --list-commands instead means that the format of the help output
can be kept succinct.
However, currently there are 2 issues that make --list-commands
unsuitable.
First, --list-commands truncates long commands. This commit resolves
this by not truncating long commands, and removing the fixed-length
char buffer and writing directly to stdout so that the line length
can overflow slightly if needed.
Second, --list-commands recursively displays sub-commands. For
example, for `lctl`, it will display `pcc add`, `pcc del`, etc in
additon to just `pcc`. The bash completion tools would view these
as separate tokens and thus would inappropriately suggest `add`,
`del`, etc. as completions for `lctl`. This commit removes the
recursive behavior.
Removing the recursive behavior resolves an unrelated bug with the
recursion that can be observed for `lctl`, where a number of
top-level commands are skipped following recursion into a previous
sub-command, equal to the number of subcommands processed in the
recursive call. Specifically, the commands in the section "device
setup", e.g. `attach`, `detach`, were not displayed following the
recursive call into `pcc`.
Finally, this commit changes the command parser to recognize --help
and print the list of commands when this argument is seen.
Fixes:
bc69a8d058 ("LU-8621 utils: cmd help to stdout or short cmd error")
Signed-off-by: Thomas Bertschinger <bertschinger@lanl.gov>
Change-Id: Ib6e139402b9cd18e5a54b8fd3d6a2652d301e736
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/49484
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Vitaliy Kuznetsov <vkuznetsov@ddn.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
void Parser_printhelp(char *); /* Detailed help routine */
void Parser_exit(int, char **); /* Shuts down command parser */
int Parser_execarg(int argc, char **argv, command_t cmds[]);
-int Parser_list_commands(const command_t *cmdlist, char *buffer,
- size_t buf_size, const char *parent_cmd,
- int col_start, int col_num);
+int Parser_list_commands(const command_t *cmdlist, int line_len,
+ int col_num);
/* Converts a string to an integer */
int Parser_int(char *, int *);
}
return rc;
}
+ /*
+ * handle --help here as a synonym of --list-commands so that it can be
+ * applied to all commands without needing to individually add a --help
+ * option to each command.
+ * This does not apply to lctl which has its own --help option because
+ * `lctl --list-commands` uses a unique format.
+ */
+ if (strcmp(argv[0], "--help") == 0) {
+ Parser_list_commands(cmds, 80, 4);
+ return 0;
+ }
fprintf(stderr,
"%s: '%s' is not a valid command. See '%s --list-commands'.\n",
program_invocation_short_name, argv[0],
/**
* Parser_list_commands() - Output a list of the supported commands.
* @cmdlist: Array of structures describing the commands.
- * @buffer: String buffer used to temporarily store the output text.
- * @buf_size: Length of the string buffer.
- * @parent_cmd: When called recursively, contains the name of the parent cmd.
- * @col_start: Column where printing should begin.
+ * @line_len: Length of output line.
* @col_num: The number of commands printed in a single row.
*
* The commands and subcommands supported by the utility are printed, arranged
- * into several columns for readability. If a command supports subcommands, the
- * function is called recursively, and the name of the parent command is
- * supplied so that it can be prepended to the names of the subcommands.
+ * into several columns for readability.
*
* Return: The number of items that were printed.
*/
-int Parser_list_commands(const command_t *cmdlist, char *buffer,
- size_t buf_size, const char *parent_cmd,
- int col_start, int col_num)
+int Parser_list_commands(const command_t *cmdlist, int line_len,
+ int col_num)
{
- int col = col_start;
int char_max;
- int len;
int count = 0;
- int rc;
+ int col = 0;
- if (col_start >= col_num)
- return 0;
+ int nprinted = 0;
+ int offset = 0;
- char_max = (buf_size - 1) / col_num; /* Reserve 1 char for NUL */
+ char_max = line_len / col_num;
for (; cmdlist->pc_name; cmdlist++) {
if (!cmdlist->pc_func && !cmdlist->pc_sub_cmd)
break;
count++;
- if (parent_cmd)
- len = snprintf(&buffer[col * char_max],
- char_max + 1, "%s %s", parent_cmd,
- cmdlist->pc_name);
- else
- len = snprintf(&buffer[col * char_max],
- char_max + 1, "%s", cmdlist->pc_name);
-
- /* Add trailing spaces to pad the entry to the column size */
- if (len < char_max) {
- snprintf(&buffer[col * char_max] + len,
- char_max - len + 1, "%*s", char_max - len,
- " ");
- } else {
- buffer[(col + 1) * char_max - 1] = ' ';
- }
+
+ nprinted = fprintf(stdout, "%-*s ", char_max - offset - 1,
+ cmdlist->pc_name);
+ /*
+ * when a column is too wide, save offset so subsequent columns
+ * can be aligned properly
+ */
+ offset = offset + nprinted - char_max;
+ offset = offset > 0 ? offset : 0;
col++;
if (col >= col_num) {
- fprintf(stdout, "%s\n", buffer);
+ fprintf(stdout, "\n");
col = 0;
- buffer[0] = '\0';
- }
-
- if (cmdlist->pc_sub_cmd) {
- rc = Parser_list_commands(cmdlist->pc_sub_cmd, buffer,
- buf_size, cmdlist->pc_name,
- col, col_num);
- col = (col + rc) % col_num;
- count += rc;
+ offset = 0;
}
}
- if (!parent_cmd && col != 0)
- fprintf(stdout, "%s\n", buffer);
+ if (col != 0)
+ fprintf(stdout, "\n");
return count;
}
static int lnetctl_list_commands(int argc, char **argv)
{
- char buffer[81] = ""; /* 80 printable chars + terminating NUL */
-
- Parser_list_commands(cmd_list, buffer, sizeof(buffer), NULL, 0, 4);
+ Parser_list_commands(cmd_list, 80, 4);
return 0;
}
static int lst_list_commands(int argc, char **argv)
{
- char buffer[81] = ""; /* 80 printable chars + terminating NUL */
-
- Parser_list_commands(lst_cmdlist, buffer, sizeof(buffer), NULL, 0, 4);
+ Parser_list_commands(lst_cmdlist, 80, 4);
return 0;
}
local cmd="$1"
local sub="$2"
- # "--list-command" prints commands in columns and truncates long ones
- "$cmd" "$sub" --non-existent-option |
- sed -e 1d -e '$d' -e 's/"//g' -e /=/d -e /exit/d -e /quit/d
+ $cmd $sub --list-commands |
+ sed -e '/=/d' -e s/exit// -e s/quit//
}
_lustre_long_opts()
}
run_test 434 "Client should not send RPCs for security.selinux with SElinux disabled"
+test_440() {
+ if [[ -f $LUSTRE/scripts/bash-completion/lustre ]]; then
+ source $LUSTRE/scripts/bash-completion/lustre
+ elif [[ -f /usr/share/bash-completion/completions/lustre ]]; then
+ source /usr/share/bash-completion/completions/lustre
+ else
+ skip "bash completion scripts not found"
+ fi
+
+ local lctl_completions
+ local lfs_completions
+
+ lctl_completions=$(_lustre_cmds lctl)
+ if [[ ! $lctl_completions =~ "get_param" ]]; then
+ error "lctl bash completion failed"
+ fi
+
+ lfs_completions=$(_lustre_cmds lfs)
+ if [[ ! $lfs_completions =~ "setstripe" ]]; then
+ error "lfs bash completion failed"
+ fi
+}
+run_test 440 "bash completion for lfs, lctl"
+
prep_801() {
[[ $MDS1_VERSION -lt $(version_code 2.9.55) ]] ||
[[ $OST1_VERSION -lt $(version_code 2.9.55) ]] &&
{ .pc_name = "list", .pc_func = jt_pcc_list,
.pc_help = "List all PCC backends on a client.\n"
"usage: lctl pcc list <mntpath>\n" },
- { .pc_name = "list-commands", .pc_func = jt_pcc_list_commands,
+ { .pc_name = "--list-commands", .pc_func = jt_pcc_list_commands,
.pc_help = "list commands supported by lctl pcc"},
{ .pc_name = "help", .pc_func = Parser_help, .pc_help = "help" },
{ .pc_name = "exit", .pc_func = Parser_quit, .pc_help = "quit" },
"print build version of this utility and exit"},
{"--list-commands", lctl_list_commands, 0,
"list commands supported by this utility and exit"},
+ /* help is a synonym of --list-commands */
+ {"--help", lctl_list_commands, 0,
+ "list commands supported by this utility and exit"},
/* Network configuration commands */
{"===== network config =====", NULL, 0, "network config"},
*/
static int jt_pcc_list_commands(int argc, char **argv)
{
- char buffer[81] = "";
-
- Parser_list_commands(pccdev_cmdlist, buffer, sizeof(buffer),
- NULL, 0, 4);
+ Parser_list_commands(pccdev_cmdlist, 80, 4);
return 0;
}
static int lctl_list_commands(int argc, char **argv)
{
- char buffer[81] = ""; /* 80 printable chars + terminating NUL */
command_t *cmd;
int rc;
while (cmd->pc_name != NULL) {
printf("\n%s\n", cmd->pc_name); /* Command category */
cmd++;
- rc = Parser_list_commands(cmd, buffer, sizeof(buffer), NULL,
- 0, 4);
+ rc = Parser_list_commands(cmd, 80, 4);
cmd += rc;
}
.pc_help = "Verify mirrored file(s).\n"
"usage: lfs mirror verify [--only MIRROR_ID[,...]]\n"
"\t\t[--verbose|-v] <mirrored_file> [<mirrored_file2> ...]\n" },
- { .pc_name = "list-commands", .pc_func = lfs_mirror_list_commands,
+ { .pc_name = "--list-commands", .pc_func = lfs_mirror_list_commands,
.pc_help = "list commands supported by lfs mirror"},
{ .pc_name = "help", .pc_func = Parser_help, .pc_help = "help" },
{ .pc_name = "exit", .pc_func = Parser_quit, .pc_help = "quit" },
{ .pc_name = "detach_fid", .pc_func = lfs_pcc_detach_fid,
.pc_help = "Detach given files from PCC by FID(s).\n"
"usage: lfs pcc detach_fid <mntpath> <fid>...\n" },
- { .pc_name = "list-commands", .pc_func = lfs_pcc_list_commands,
+ { .pc_name = "--list-commands", .pc_func = lfs_pcc_list_commands,
.pc_help = "list commands supported by lfs pcc"},
{ .pc_name = "help", .pc_func = Parser_help, .pc_help = "help" },
{ .pc_name = "exit", .pc_func = Parser_quit, .pc_help = "quit" },
*/
static int lfs_mirror_list_commands(int argc, char **argv)
{
- char buffer[81] = "";
-
- Parser_list_commands(mirror_cmdlist, buffer, sizeof(buffer),
- NULL, 0, 4);
+ Parser_list_commands(mirror_cmdlist, 80, 4);
return 0;
}
*/
static int lfs_pcc_list_commands(int argc, char **argv)
{
- char buffer[81] = "";
-
- Parser_list_commands(pcc_cmdlist, buffer, sizeof(buffer),
- NULL, 0, 4);
+ Parser_list_commands(pcc_cmdlist, 80, 4);
return 0;
}
static int lfs_list_commands(int argc, char **argv)
{
- char buffer[81] = ""; /* 80 printable chars + terminating NUL */
-
- Parser_list_commands(cmdlist, buffer, sizeof(buffer), NULL, 0, 4);
+ Parser_list_commands(cmdlist, 80, 4);
return 0;
}