1 // SPDX-License-Identifier: GPL-2.0
4 * Copyright (C) 2001 Cluster File Systems, Inc.
6 * Copyright (c) 2014, 2017, Intel Corporation.
11 * This file is part of Lustre, http://www.lustre.org/
13 * libcfs/libcfs/parser.c
15 * A command line parser.
27 #ifdef HAVE_LIBREADLINE
28 # include <readline/history.h>
29 # include <readline/readline.h>
30 #endif /* HAVE_LIBREADLINE */
34 #include <libcfs/util/parser.h>
35 #include <linux/lustre/lustre_ver.h>
37 /* Top level of commands */
38 static command_t top_level[MAXCMDS];
39 /* Set to 1 if user types exit or quit */
42 * Normally, the parser will quit when an error occurs in non-interacive
43 * mode. Setting this to non-zero will force it to keep buggering on.
45 static int ignore_errors;
47 static char *skipwhitespace(char *s);
48 static char *skiptowhitespace(char *s);
49 static command_t *find_cmd(char *name, command_t cmds[], char **next);
50 static int process(char *s, char **next, command_t *lookup, command_t **result,
52 static int line2args(char *line, char **argv, int maxargs);
53 static int cfs_parser_commands(command_t *cmds);
54 static int cfs_parser_execarg(int argc, char **argv, command_t cmds[]);
55 static int cfs_parser_list_commands(const command_t *cmdlist, int line_len,
57 static int cfs_parser_list(int argc, char **argv);
58 static int cfs_parser_help(int argc, char **argv);
59 static int cfs_parser_quit(int argc, char **argv);
60 static int cfs_parser_version(int argc, char **argv);
61 static int cfs_parser_ignore_errors(int argc, char **argv);
63 command_t override_cmdlist[] = {
64 { .pc_name = "quit", .pc_func = cfs_parser_quit, .pc_help = "quit" },
65 { .pc_name = "exit", .pc_func = cfs_parser_quit, .pc_help = "exit" },
66 { .pc_name = "help", .pc_func = cfs_parser_help,
67 .pc_help = "provide useful information about a command" },
68 { .pc_name = "--help", .pc_func = cfs_parser_help,
69 .pc_help = "provide useful information about a command" },
70 { .pc_name = "version", .pc_func = cfs_parser_version,
71 .pc_help = "show program version" },
72 { .pc_name = "--version", .pc_func = cfs_parser_version,
73 .pc_help = "show program version" },
74 { .pc_name = "list-commands", .pc_func = cfs_parser_list,
75 .pc_help = "list available commands" },
76 { .pc_name = "--list-commands", .pc_func = cfs_parser_list,
77 .pc_help = "list available commands" },
78 { .pc_name = "--ignore_errors", .pc_func = cfs_parser_ignore_errors,
79 .pc_help = "ignore errors that occur during script processing"},
80 { .pc_name = "ignore_errors", .pc_func = cfs_parser_ignore_errors,
81 .pc_help = "ignore errors that occur during script processing"},
82 { .pc_name = 0, .pc_func = NULL, .pc_help = 0 }
85 static char *skipwhitespace(char *s)
91 for (t = s; t <= s + len && isspace(*t); t++)
96 static char *skiptowhitespace(char *s)
100 for (t = s; *t && !isspace(*t); t++)
105 static int line2args(char *line, char **argv, int maxargs)
110 arg = strtok(line, " \t");
111 if (!arg || maxargs < 1)
115 while ((arg = strtok(NULL, " \t")) != NULL && i < maxargs)
120 /* find a command -- return it if unique otherwise print alternatives */
121 static command_t *cfs_parser_findargcmd(char *name, command_t cmds[])
125 for (cmd = cmds; cmd->pc_name; cmd++) {
126 if (strcmp(name, cmd->pc_name) == 0)
132 static int cfs_parser_ignore_errors(int argc, char **argv)
142 int cfs_parser(int argc, char **argv, command_t cmds[])
150 for (cmd = override_cmdlist; cmd->pc_name && i < MAXCMDS; cmd++)
151 top_level[i++] = *cmd;
153 for (cmd = cmds; cmd->pc_name && i < MAXCMDS; cmd++)
154 top_level[i++] = *cmd;
157 rc = cfs_parser_execarg(argc - 1, argv + 1, cmds);
159 rc = cfs_parser_commands(cmds);
164 static int cfs_parser_execarg(int argc, char **argv, command_t cmds[])
168 cmd = cfs_parser_findargcmd(argv[0], override_cmdlist);
171 cmd = cfs_parser_findargcmd(argv[0], cmds);
173 if (cmd && cmd->pc_func) {
174 int rc = cmd->pc_func(argc, argv);
176 if (rc == CMD_HELP) {
177 fprintf(stdout, "%s\n", cmd->pc_help);
184 "%s: '%s' is not a valid command. See '%s --list-commands'.\n",
185 program_invocation_short_name, argv[0],
186 program_invocation_short_name);
192 * Returns the command_t * (NULL if not found) corresponding to a
193 * _partial_ match with the first token in name. It sets *next to
194 * point to the following token. Does not modify *name.
196 static command_t *find_cmd(char *name, command_t cmds[], char **next)
204 * This sets name to point to the first non-white space character,
205 * and next to the first whitespace after name, len to the length: do
208 name = skipwhitespace(name);
209 *next = skiptowhitespace(name);
210 len = (int)(*next - name);
214 for (i = 0; cmds[i].pc_name; i++) {
215 if (strncasecmp(name, cmds[i].pc_name, len) == 0) {
216 *next = skipwhitespace(*next);
224 * Recursively process a command line string s and find the command
225 * corresponding to it. This can be ambiguous, full, incomplete,
228 static int process(char *s, char **next, command_t *lookup,
229 command_t **result, char **prev)
233 *result = find_cmd(s, lookup, next);
241 * found entry: is it ambigous, i.e. not exact command name and
242 * more than one command in the list matches. Note that find_cmd
243 * points to the first ambiguous entry
245 if (strncasecmp(s, (*result)->pc_name, strlen((*result)->pc_name))) {
247 int found_another = 0;
249 command_t *another_result = find_cmd(s, (*result) + 1,
251 while (another_result) {
252 if (strncasecmp(s, another_result->pc_name,
253 strlen(another_result->pc_name)) == 0) {
254 *result = another_result;
255 *next = another_next;
258 another_result = find_cmd(s, another_result + 1,
263 * In some circumstances, process will fail to find a
264 * suitable command. We want to be able to escape both
265 * the while loop and the recursion. So, track the
266 * number of times we've been here and give up if
267 * things start to get out-of-hand.
279 /* found a unique command: component or full? */
280 if ((*result)->pc_func)
284 return CMD_INCOMPLETE;
285 return process(*next, next, (*result)->pc_sub_cmd,
289 #ifdef HAVE_LIBREADLINE
290 static command_t *match_tbl; /* Command completion against this table */
291 static char *command_generator(const char *text, int state)
293 static int index, len;
296 /* Do we have a match table? */
300 /* If this is the first time called on this word, state is 0 */
303 len = (int)strlen(text);
306 /* Return next name in the command list that paritally matches test */
307 while ((name = (match_tbl + index)->pc_name)) {
310 if (strncasecmp(name, text, len) == 0)
314 /* No more matches */
318 /* probably called by readline */
319 static char **command_completion(const char *text, int start, int end)
324 match_tbl = top_level;
326 for (table = find_cmd(rl_line_buffer, match_tbl, &pos);
327 table; table = find_cmd(pos, match_tbl, &pos)) {
328 if (*(pos - 1) == ' ')
329 match_tbl = table->pc_sub_cmd;
332 return rl_completion_matches(text, command_generator);
336 /* take a string and execute the function or print help */
337 static int execute_line(char *line)
339 command_t *cmd, *ambig;
346 switch (process(line, &next, top_level, &cmd, &prev)) {
348 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
349 while ((ambig = find_cmd(prev, cmd, &tmp))) {
350 fprintf(stderr, "%s ", ambig->pc_name);
353 fprintf(stderr, "\n");
356 fprintf(stderr, "No such command. Try --list-commands to see available commands.\n");
360 "'%s' incomplete command. Use '%s x' where x is one of:\n",
362 fprintf(stderr, "\t");
363 for (i = 0; cmd->pc_sub_cmd[i].pc_name; i++)
364 fprintf(stderr, "%s ", cmd->pc_sub_cmd[i].pc_name);
365 fprintf(stderr, "\n");
369 i = line2args(line, argv, MAXARGS);
370 rc = cmd->pc_func(i, argv);
372 if (rc == CMD_HELP) {
373 fprintf(stdout, "%s\n", cmd->pc_help);
383 #ifdef HAVE_LIBREADLINE
384 static void noop_int_fn(int unused) { }
385 static void noop_void_fn(void) { }
389 * just in case you're ever in an airplane and discover you
390 * forgot to install readline-dev. :)
392 static int init_input(void)
394 int interactive = isatty(fileno(stdin));
396 #ifdef HAVE_LIBREADLINE
398 stifle_history(HISTORY);
401 rl_prep_term_function = noop_int_fn;
402 rl_deprep_term_function = noop_void_fn;
405 rl_attempted_completion_function = command_completion;
406 rl_completion_entry_function = command_generator;
411 #ifndef HAVE_LIBREADLINE
412 #define add_history(s)
413 static char *readline(char *prompt)
416 char *line = malloc(size);
424 printf("%s", prompt);
427 if ((c = fgetc(stdin)) != EOF) {
432 if (ptr - line >= size - 1) {
439 memcpy(tmp, line, ptr - line);
440 ptr = tmp + (ptr - line);
446 if (ferror(stdin) || feof(stdin))
453 if (eof && (strlen(line) == 0)) {
464 /* this is the command execution machine */
465 static int cfs_parser_commands(command_t *cmds)
468 int rc = 0, save_error = 0;
471 interactive = init_input();
474 line = readline(interactive ? "> " : NULL);
479 s = skipwhitespace(line);
483 rc = execute_line(s);
485 /* stop on error if not-interactive */
486 if (rc != 0 && !interactive) {
501 static int cfs_parser_help(int argc, char **argv)
504 char *next, *prev, *tmp;
505 command_t *result, *ambig;
509 printf("usage: %s [COMMAND] [OPTIONS]... [ARGS]\n",
510 program_invocation_short_name);
511 printf("Without any parameters, interactive mode is invoked\n");
512 printf("Try '%s help <COMMAND>', or '%s --list-commands' for a list of commands.\n",
513 program_invocation_short_name,
514 program_invocation_short_name);
519 * Joining command line arguments without space is not critical here
520 * because of this string is used for search a help topic and assume
521 * that only one argument will be (the name of topic). For example:
522 * lst > help ping run
523 * pingrun: Unknown command.
526 for (i = 1; i < argc; i++) {
527 if (strlen(argv[i]) >= sizeof(line) - strlen(line) - 1)
530 * The function strlcat() cannot be used here because of
531 * this function is used in LNet utils that is not linked
534 strncat(line, argv[i], sizeof(line) - strlen(line) - 1);
537 switch (process(line, &next, top_level, &result, &prev)) {
539 fprintf(stderr, "%s: %s\n", line, result->pc_help);
542 fprintf(stderr, "%s: '%s' is not a valid command. See '%s --list-commands'.\n",
543 program_invocation_short_name, line,
544 program_invocation_short_name);
548 "'%s' incomplete command. Use '%s x' where x is one of:\n",
550 fprintf(stderr, "\t");
551 for (i = 0; result->pc_sub_cmd[i].pc_name; i++)
552 fprintf(stderr, "%s ", result->pc_sub_cmd[i].pc_name);
553 fprintf(stderr, "\n");
556 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
557 while ((ambig = find_cmd(prev, result, &tmp))) {
558 fprintf(stderr, "%s ", ambig->pc_name);
561 fprintf(stderr, "\n");
568 * cfs_parser_list_commands() - Output a list of the supported commands.
569 * @cmdlist: Array of structures describing the commands.
570 * @line_len: Length of output line.
571 * @col_num: The number of commands printed in a single row.
573 * The commands and subcommands supported by the utility are printed, arranged
574 * into several columns for readability.
576 * Return: The number of items that were printed.
578 static int cfs_parser_list_commands(const command_t *cmdlist, int line_len,
588 char_max = line_len / col_num;
590 for (; cmdlist->pc_name; cmdlist++) {
591 if (!cmdlist->pc_func && !cmdlist->pc_sub_cmd)
595 nprinted = fprintf(stdout, "%-*s ", char_max - offset - 1,
598 * when a column is too wide, save offset so subsequent columns
599 * can be aligned properly
601 offset = offset + nprinted - char_max;
602 offset = offset > 0 ? offset : 0;
605 if (col >= col_num) {
606 fprintf(stdout, "\n");
612 fprintf(stdout, "\n");
616 static int cfs_parser_quit(int argc, char **argv)
626 static int cfs_parser_version(int argc, char **argv)
631 fprintf(stdout, "%s %s\n", program_invocation_short_name,
632 LUSTRE_VERSION_STRING);
637 static int cfs_parser_list(int argc, char **argv)
646 while (cmd->pc_name != NULL) {
649 * print the command category
651 printf("\n%s\n", cmd->pc_name);
654 num_cmds_listed = cfs_parser_list_commands(cmd, 80, 4);
655 cmd += num_cmds_listed;