Whamcloud - gitweb
LU-17744 ldiskfs: mballoc stats fixes
[fs/lustre-release.git] / libcfs / libcfs / util / parser.c
1 // SPDX-License-Identifier: GPL-2.0
2
3 /*
4  * Copyright (C) 2001 Cluster File Systems, Inc.
5  *
6  * Copyright (c) 2014, 2017, Intel Corporation.
7  *
8  */
9
10 /*
11  * This file is part of Lustre, http://www.lustre.org/
12  *
13  * libcfs/libcfs/parser.c
14  *
15  * A command line parser.
16  *
17  */
18
19 #include <stddef.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <assert.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <getopt.h>
26 #include <malloc.h>
27 #ifdef HAVE_LIBREADLINE
28 # include <readline/history.h>
29 # include <readline/readline.h>
30 #endif /* HAVE_LIBREADLINE */
31 #include <string.h>
32 #include <unistd.h>
33
34 #include <libcfs/util/parser.h>
35 #include <linux/lustre/lustre_ver.h>
36
37 /* Top level of commands */
38 static command_t top_level[MAXCMDS];
39 /* Set to 1 if user types exit or quit */
40 static int done;
41 /*
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.
44  */
45 static int ignore_errors;
46
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,
51                    char **prev);
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,
56                                 int col_num);
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);
62
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 }
83 };
84
85 static char *skipwhitespace(char *s)
86 {
87         char *t;
88         int len;
89
90         len = (int)strlen(s);
91         for (t = s; t <= s + len && isspace(*t); t++)
92                 ;
93         return t;
94 }
95
96 static char *skiptowhitespace(char *s)
97 {
98         char *t;
99
100         for (t = s; *t && !isspace(*t); t++)
101                 ;
102         return t;
103 }
104
105 static int line2args(char *line, char **argv, int maxargs)
106 {
107         char *arg;
108         int i = 0;
109
110         arg = strtok(line, " \t");
111         if (!arg || maxargs < 1)
112                 return 0;
113
114         argv[i++] = arg;
115         while ((arg = strtok(NULL, " \t")) != NULL && i < maxargs)
116                 argv[i++] = arg;
117         return i;
118 }
119
120 /* find a command -- return it if unique otherwise print alternatives */
121 static command_t *cfs_parser_findargcmd(char *name, command_t cmds[])
122 {
123         command_t *cmd;
124
125         for (cmd = cmds; cmd->pc_name; cmd++) {
126                 if (strcmp(name, cmd->pc_name) == 0)
127                         return cmd;
128         }
129         return NULL;
130 }
131
132 static int cfs_parser_ignore_errors(int argc, char **argv)
133 {
134         (void) argc;
135         (void) argv;
136
137         ignore_errors = 1;
138
139         return 0;
140 }
141
142 int cfs_parser(int argc, char **argv, command_t cmds[])
143 {
144         command_t *cmd;
145         int rc = 0;
146         int i = 0;
147
148         done = 0;
149
150         for (cmd = override_cmdlist; cmd->pc_name && i < MAXCMDS; cmd++)
151                 top_level[i++] = *cmd;
152
153         for (cmd = cmds; cmd->pc_name && i < MAXCMDS; cmd++)
154                 top_level[i++] = *cmd;
155
156         if (argc > 1)
157                 rc = cfs_parser_execarg(argc - 1, argv + 1, cmds);
158         else
159                 rc = cfs_parser_commands(cmds);
160
161         return rc;
162 }
163
164 static int cfs_parser_execarg(int argc, char **argv, command_t cmds[])
165 {
166         command_t *cmd;
167
168         cmd = cfs_parser_findargcmd(argv[0], override_cmdlist);
169
170         if (!cmd)
171                 cmd = cfs_parser_findargcmd(argv[0], cmds);
172
173         if (cmd && cmd->pc_func) {
174                 int rc = cmd->pc_func(argc, argv);
175
176                 if (rc == CMD_HELP) {
177                         fprintf(stdout, "%s\n", cmd->pc_help);
178                         fflush(stdout);
179                 }
180                 return rc;
181         }
182
183         fprintf(stderr,
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);
187
188         return -1;
189 }
190
191 /*
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.
195  */
196 static command_t *find_cmd(char *name, command_t cmds[], char **next)
197 {
198         int i, len;
199
200         if (!cmds || !name)
201                 return NULL;
202
203         /*
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
206          * this with strtok
207          */
208         name = skipwhitespace(name);
209         *next = skiptowhitespace(name);
210         len = (int)(*next - name);
211         if (len == 0)
212                 return NULL;
213
214         for (i = 0; cmds[i].pc_name; i++) {
215                 if (strncasecmp(name, cmds[i].pc_name, len) == 0) {
216                         *next = skipwhitespace(*next);
217                         return &cmds[i];
218                 }
219         }
220         return NULL;
221 }
222
223 /*
224  * Recursively process a command line string s and find the command
225  * corresponding to it. This can be ambiguous, full, incomplete,
226  * non-existent.
227  */
228 static int process(char *s, char **next, command_t *lookup,
229                    command_t **result, char **prev)
230 {
231         static int depth;
232
233         *result = find_cmd(s, lookup, next);
234         *prev = s;
235
236         /* non existent */
237         if (!*result)
238                 return CMD_NONE;
239
240         /*
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
244          */
245         if (strncasecmp(s, (*result)->pc_name, strlen((*result)->pc_name))) {
246                 char *another_next;
247                 int found_another = 0;
248
249                 command_t *another_result = find_cmd(s, (*result) + 1,
250                                                      &another_next);
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;
256                                 goto got_it;
257                         }
258                         another_result = find_cmd(s, another_result + 1,
259                                                   &another_next);
260                         found_another = 1;
261
262                         /*
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.
268                          */
269                         if (depth > 50)
270                                 return CMD_NONE;
271
272                         depth++;
273                 }
274                 if (found_another)
275                         return CMD_AMBIG;
276         }
277
278 got_it:
279         /* found a unique command: component or full? */
280         if ((*result)->pc_func)
281                 return CMD_COMPLETE;
282
283         if (**next == '\0')
284                 return CMD_INCOMPLETE;
285         return process(*next, next, (*result)->pc_sub_cmd,
286                        result, prev);
287 }
288
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)
292 {
293         static int index, len;
294         char *name;
295
296         /* Do we have a match table? */
297         if (!match_tbl)
298                 return NULL;
299
300         /* If this is the first time called on this word, state is 0 */
301         if (!state) {
302                 index = 0;
303                 len = (int)strlen(text);
304         }
305
306         /* Return next name in the command list that paritally matches test */
307         while ((name = (match_tbl + index)->pc_name)) {
308                 index++;
309
310                 if (strncasecmp(name, text, len) == 0)
311                         return strdup(name);
312         }
313
314         /* No more matches */
315         return NULL;
316 }
317
318 /* probably called by readline */
319 static char **command_completion(const char *text, int start, int end)
320 {
321         command_t *table;
322         char *pos;
323
324         match_tbl = top_level;
325
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;
330         }
331
332         return rl_completion_matches(text, command_generator);
333 }
334 #endif
335
336 /* take a string and execute the function or print help */
337 static int execute_line(char *line)
338 {
339         command_t *cmd, *ambig;
340         char *prev;
341         char *next, *tmp;
342         char *argv[MAXARGS];
343         int i;
344         int rc = 0;
345
346         switch (process(line, &next, top_level, &cmd, &prev)) {
347         case CMD_AMBIG:
348                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
349                 while ((ambig = find_cmd(prev, cmd, &tmp))) {
350                         fprintf(stderr, "%s ", ambig->pc_name);
351                         cmd = ambig + 1;
352                 }
353                 fprintf(stderr, "\n");
354                 break;
355         case CMD_NONE:
356                 fprintf(stderr, "No such command. Try --list-commands to see available commands.\n");
357                 break;
358         case CMD_INCOMPLETE:
359                 fprintf(stderr,
360                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
361                         line, line);
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");
366                 break;
367         case CMD_COMPLETE:
368                 optind = 0;
369                 i = line2args(line, argv, MAXARGS);
370                 rc = cmd->pc_func(i, argv);
371
372                 if (rc == CMD_HELP) {
373                         fprintf(stdout, "%s\n", cmd->pc_help);
374                         fflush(stdout);
375                 }
376
377                 break;
378         }
379
380         return rc;
381 }
382
383 #ifdef HAVE_LIBREADLINE
384 static void noop_int_fn(int unused) { }
385 static void noop_void_fn(void) { }
386 #endif
387
388 /*
389  * just in case you're ever in an airplane and discover you
390  * forgot to install readline-dev. :)
391  */
392 static int init_input(void)
393 {
394         int interactive = isatty(fileno(stdin));
395
396 #ifdef HAVE_LIBREADLINE
397         using_history();
398         stifle_history(HISTORY);
399
400         if (!interactive) {
401                 rl_prep_term_function = noop_int_fn;
402                 rl_deprep_term_function = noop_void_fn;
403         }
404
405         rl_attempted_completion_function = command_completion;
406         rl_completion_entry_function = command_generator;
407 #endif
408         return interactive;
409 }
410
411 #ifndef HAVE_LIBREADLINE
412 #define add_history(s)
413 static char *readline(char *prompt)
414 {
415         int size = 2048;
416         char *line = malloc(size);
417         char *ptr = line;
418         int c;
419         int eof = 0;
420
421         if (!line)
422                 return NULL;
423         if (prompt)
424                 printf("%s", prompt);
425
426         while (1) {
427                 if ((c = fgetc(stdin)) != EOF) {
428                         if (c == '\n')
429                                 goto out;
430                         *ptr++ = (char)c;
431
432                         if (ptr - line >= size - 1) {
433                                 char *tmp;
434
435                                 size *= 2;
436                                 tmp = malloc(size);
437                                 if (!tmp)
438                                         goto outfree;
439                                 memcpy(tmp, line, ptr - line);
440                                 ptr = tmp + (ptr - line);
441                                 free(line);
442                                 line = tmp;
443                         }
444                 } else {
445                         eof = 1;
446                         if (ferror(stdin) || feof(stdin))
447                                 goto outfree;
448                         goto out;
449                 }
450         }
451 out:
452         *ptr = 0;
453         if (eof && (strlen(line) == 0)) {
454                 free(line);
455                 line = NULL;
456         }
457         return line;
458 outfree:
459         free(line);
460         return NULL;
461 }
462 #endif
463
464 /* this is the command execution machine */
465 static int cfs_parser_commands(command_t *cmds)
466 {
467         char *line, *s;
468         int rc = 0, save_error = 0;
469         int interactive;
470
471         interactive = init_input();
472
473         while (!done) {
474                 line = readline(interactive ? "> " : NULL);
475
476                 if (!line)
477                         break;
478
479                 s = skipwhitespace(line);
480
481                 if (*s) {
482                         add_history(s);
483                         rc = execute_line(s);
484                 }
485                 /* stop on error if not-interactive */
486                 if (rc != 0 && !interactive) {
487                         if (save_error == 0)
488                                 save_error = rc;
489                         if (!ignore_errors)
490                                 done = 1;
491                 }
492                 free(line);
493         }
494
495         if (save_error)
496                 rc = save_error;
497
498         return rc;
499 }
500
501 static int cfs_parser_help(int argc, char **argv)
502 {
503         char line[1024];
504         char *next, *prev, *tmp;
505         command_t *result, *ambig;
506         int i;
507
508         if (argc == 1) {
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);
515                 return 0;
516         }
517
518         /*
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.
524          */
525         line[0] = '\0';
526         for (i = 1;  i < argc; i++) {
527                 if (strlen(argv[i]) >= sizeof(line) - strlen(line) - 1)
528                         return -E2BIG;
529                 /*
530                  * The function strlcat() cannot be used here because of
531                  * this function is used in LNet utils that is not linked
532                  * with libcfs.a.
533                  */
534                 strncat(line, argv[i], sizeof(line) - strlen(line) - 1);
535         }
536
537         switch (process(line, &next, top_level, &result, &prev)) {
538         case CMD_COMPLETE:
539                 fprintf(stderr, "%s: %s\n", line, result->pc_help);
540                 break;
541         case CMD_NONE:
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);
545                 break;
546         case CMD_INCOMPLETE:
547                 fprintf(stderr,
548                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
549                         line, line);
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");
554                 break;
555         case CMD_AMBIG:
556                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
557                 while ((ambig = find_cmd(prev, result, &tmp))) {
558                         fprintf(stderr, "%s ", ambig->pc_name);
559                         result = ambig + 1;
560                 }
561                 fprintf(stderr, "\n");
562                 break;
563         }
564         return 0;
565 }
566
567 /**
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.
572  *
573  * The commands and subcommands supported by the utility are printed, arranged
574  * into several columns for readability.
575  *
576  * Return: The number of items that were printed.
577  */
578 static int cfs_parser_list_commands(const command_t *cmdlist, int line_len,
579                                 int col_num)
580 {
581         int char_max;
582         int count = 0;
583         int col = 0;
584
585         int nprinted = 0;
586         int offset = 0;
587
588         char_max = line_len / col_num;
589
590         for (; cmdlist->pc_name; cmdlist++) {
591                 if (!cmdlist->pc_func && !cmdlist->pc_sub_cmd)
592                         break;
593                 count++;
594
595                 nprinted = fprintf(stdout, "%-*s ", char_max - offset - 1,
596                                    cmdlist->pc_name);
597                 /*
598                  * when a column is too wide, save offset so subsequent columns
599                  * can be aligned properly
600                  */
601                 offset = offset + nprinted - char_max;
602                 offset = offset > 0 ? offset : 0;
603
604                 col++;
605                 if (col >= col_num) {
606                         fprintf(stdout, "\n");
607                         col = 0;
608                         offset = 0;
609                 }
610         }
611         if (col != 0)
612                 fprintf(stdout, "\n");
613         return count;
614 }
615
616 static int cfs_parser_quit(int argc, char **argv)
617 {
618         (void) argc;
619         (void) argv;
620
621         done = 1;
622
623         return 0;
624 }
625
626 static int cfs_parser_version(int argc, char **argv)
627 {
628         (void) argc;
629         (void) argv;
630
631         fprintf(stdout, "%s %s\n", program_invocation_short_name,
632                 LUSTRE_VERSION_STRING);
633
634         return 0;
635 }
636
637 static int cfs_parser_list(int argc, char **argv)
638 {
639         command_t *cmd;
640         int num_cmds_listed;
641
642         (void) argc;
643         (void) argv;
644
645         cmd = top_level;
646         while (cmd->pc_name != NULL) {
647                 if (!cmd->pc_func) {
648                         /*
649                          * print the command category
650                          */
651                         printf("\n%s\n", cmd->pc_name);
652                         cmd++;
653                 }
654                 num_cmds_listed = cfs_parser_list_commands(cmd, 80, 4);
655                 cmd += num_cmds_listed;
656         }
657
658         return 0;
659 }