Whamcloud - gitweb
Landing b_bug1414
[fs/lustre-release.git] / lustre / utils / parser.c
1 /* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
2  * vim:expandtab:shiftwidth=8:tabstop=8:
3  *
4  * Copyright (C) 2001 Cluster File Systems, Inc.
5  *
6  *   This file is part of Lustre, http://www.sf.net/projects/lustre/
7  *
8  *   Lustre is free software; you can redistribute it and/or
9  *   modify it under the terms of version 2 of the GNU General Public
10  *   License as published by the Free Software Foundation.
11  *
12  *   Lustre is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with Lustre; if not, write to the Free Software
19  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  *
21  */
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <ctype.h>
25 #include <string.h>
26 #include <stddef.h>
27 #include <unistd.h>
28 #include <sys/param.h>
29 #include <assert.h>
30
31 #ifdef HAVE_LIBREADLINE
32 #define READLINE_LIBRARY
33 #include <readline/readline.h>
34
35 /* completion_matches() is #if 0-ed out in modern glibc */
36 #ifndef completion_matches
37 #define completion_matches rl_completion_matches
38 #endif
39 extern void using_history(void);
40 extern void stifle_history(int);
41 extern void add_history(char *);
42 #endif
43
44 #include "parser.h"
45
46 static command_t * top_level;           /* Top level of commands, initialized by
47                                     * InitParser                              */
48 static char * parser_prompt = NULL;/* Parser prompt, set by InitParser      */
49 static int done;                   /* Set to 1 if user types exit or quit   */
50 static int ignore_errors;       /* Normally, the parser will quit when
51                                    an error occurs in non-interacive
52                                    mode. Setting this to non-zero will
53                                    force it to keep buggering on. */
54
55
56 /* static functions */
57 static char *skipwhitespace(char *s);
58 static char *skiptowhitespace(char *s);
59 static command_t *find_cmd(char *name, command_t cmds[], char **next);
60 static int process(char *s, char **next, command_t *lookup, command_t **result,
61                    char **prev);
62 static void print_commands(char *str, command_t *table);
63
64 static char * skipwhitespace(char * s)
65 {
66         char * t;
67         int    len;
68
69         len = (int)strlen(s);
70         for (t = s; t <= s + len && isspace(*t); t++);
71         return(t);
72 }
73
74
75 static char * skiptowhitespace(char * s)
76 {
77         char * t;
78
79         for (t = s; *t && !isspace(*t); t++);
80         return(t);
81 }
82
83 static int line2args(char *line, char **argv, int maxargs)
84 {
85         char *arg;
86         int i = 0;
87
88         arg = strtok(line, " \t");
89         if ( arg ) {
90                 argv[i] = arg;
91                 i++;
92         } else
93                 return 0;
94
95         while( (arg = strtok(NULL, " \t")) && (i <= maxargs)) {
96                 argv[i] = arg;
97                 i++;
98         }
99         return i;
100 }
101
102 /* find a command -- return it if unique otherwise print alternatives */
103 static command_t *Parser_findargcmd(char *name, command_t cmds[])
104 {
105         command_t *cmd;
106
107         for (cmd = cmds; cmd->pc_name; cmd++) {
108                 if (strcmp(name, cmd->pc_name) == 0)
109                         return cmd;
110         }
111         return NULL;
112 }
113
114 void Parser_ignore_errors(int ignore)
115 {
116         ignore_errors = ignore;
117 }
118
119 int Parser_execarg(int argc, char **argv, command_t cmds[])
120 {
121         command_t *cmd;
122
123         cmd = Parser_findargcmd(argv[0], cmds);
124         if ( cmd ) {
125                 int rc = (cmd->pc_func)(argc, argv);
126                 if (rc == CMD_HELP)
127                         fprintf(stderr, "%s\n", cmd->pc_help);
128                 return rc;
129         } else {
130                 printf("Try interactive use without arguments or use one of:\n");
131                 for (cmd = cmds; cmd->pc_name; cmd++)
132                         printf("\"%s\"\n", cmd->pc_name);
133                 printf("as argument.\n");
134         }
135         return -1;
136 }
137
138 /* returns the command_t * (NULL if not found) corresponding to a
139    _partial_ match with the first token in name.  It sets *next to
140    point to the following token. Does not modify *name. */
141 static command_t * find_cmd(char * name, command_t cmds[], char ** next)
142 {
143         int    i, len;
144
145         if (!cmds || !name )
146                 return NULL;
147
148         /* This sets name to point to the first non-white space character,
149            and next to the first whitespace after name, len to the length: do
150            this with strtok*/
151         name = skipwhitespace(name);
152         *next = skiptowhitespace(name);
153         len = *next - name;
154         if (len == 0)
155                 return NULL;
156
157         for (i = 0; cmds[i].pc_name; i++) {
158                 if (strncasecmp(name, cmds[i].pc_name, len) == 0) {
159                         *next = skipwhitespace(*next);
160                         return(&cmds[i]);
161                 }
162         }
163         return NULL;
164 }
165
166 /* Recursively process a command line string s and find the command
167    corresponding to it. This can be ambiguous, full, incomplete,
168    non-existent. */
169 static int process(char *s, char ** next, command_t *lookup,
170                    command_t **result, char **prev)
171 {
172         *result = find_cmd(s, lookup, next);
173         *prev = s;
174
175         /* non existent */
176         if (!*result)
177                 return CMD_NONE;
178
179         /* found entry: is it ambigous, i.e. not exact command name and
180            more than one command in the list matches.  Note that find_cmd
181            points to the first ambiguous entry */
182         if (strncasecmp(s, (*result)->pc_name, strlen((*result)->pc_name))) {
183                 char *another_next;
184                 command_t *another_result = find_cmd(s, (*result) + 1,
185                                                      &another_next);
186                 int found_another = 0;
187
188                 while (another_result) {
189                         if (strncasecmp(s, another_result->pc_name,
190                                         strlen(another_result->pc_name)) == 0){
191                                 *result = another_result;
192                                 *next = another_next;
193                                 goto got_it;
194                         }
195                         another_result = find_cmd(s, another_result + 1,
196                                                   &another_next);
197                         found_another = 1;
198                 }
199                 if (found_another)
200                         return CMD_AMBIG;
201         }
202
203 got_it:
204         /* found a unique command: component or full? */
205         if ( (*result)->pc_func ) {
206                 return CMD_COMPLETE;
207         } else {
208                 if ( *next == '\0' ) {
209                         return CMD_INCOMPLETE;
210                 } else {
211                         return process(*next, next, (*result)->pc_sub_cmd,
212                                        result, prev);
213                 }
214         }
215 }
216
217 #ifdef HAVE_LIBREADLINE
218 static command_t * match_tbl;   /* Command completion against this table */
219 static char * command_generator(const char * text, int state)
220 {
221         static int index,
222                 len;
223         char       *name;
224
225         /* Do we have a match table? */
226         if (!match_tbl)
227                 return NULL;
228
229         /* If this is the first time called on this word, state is 0 */
230         if (!state) {
231                 index = 0;
232                 len = (int)strlen(text);
233         }
234
235         /* Return next name in the command list that paritally matches test */
236         while ( (name = (match_tbl + index)->pc_name) ) {
237                 index++;
238
239                 if (strncasecmp(name, text, len) == 0) {
240                         return(strdup(name));
241                 }
242         }
243
244         /* No more matches */
245         return NULL;
246 }
247
248 /* probably called by readline */
249 static char **command_completion(char * text, int start, int end)
250 {
251         command_t         * table;
252         char        * pos;
253
254         match_tbl = top_level;
255         for (table = find_cmd(rl_line_buffer, match_tbl, &pos);
256              table;
257              table = find_cmd(pos, match_tbl, &pos)) {
258
259                 if (*(pos - 1) == ' ') match_tbl = table->pc_sub_cmd;
260         }
261
262         return(completion_matches(text, command_generator));
263 }
264 #endif
265
266 /* take a string and execute the function or print help */
267 int execute_line(char * line)
268 {
269         command_t         *cmd, *ambig;
270         char *prev;
271         char *next, *tmp;
272         char *argv[MAXARGS];
273         int         i;
274         int rc = 0;
275
276         switch (process(line, &next, top_level, &cmd, &prev)) {
277         case CMD_AMBIG:
278                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
279                 while( (ambig = find_cmd(prev, cmd, &tmp)) ) {
280                         fprintf(stderr, "%s ", ambig->pc_name);
281                         cmd = ambig + 1;
282                 }
283                 fprintf(stderr, "\n");
284                 break;
285         case CMD_NONE:
286                 fprintf(stderr, "No such command, type help\n");
287                 break;
288         case CMD_INCOMPLETE:
289                 fprintf(stderr,
290                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
291                         line, line);
292                 fprintf(stderr, "\t");
293                 for (i = 0; cmd->pc_sub_cmd[i].pc_name; i++) {
294                         fprintf(stderr, "%s ", cmd->pc_sub_cmd[i].pc_name);
295                 }
296                 fprintf(stderr, "\n");
297                 break;
298         case CMD_COMPLETE:
299                 i = line2args(line, argv, MAXARGS);
300                 rc = (cmd->pc_func)(i, argv);
301
302                 if (rc == CMD_HELP)
303                         fprintf(stderr, "%s\n", cmd->pc_help);
304
305                 break;
306         }
307
308         return rc;
309 }
310
311 int
312 noop_fn ()
313 {
314         return (0);
315 }
316
317 /* just in case you're ever in an airplane and discover you
318    forgot to install readline-dev. :) */
319 int init_input()
320 {
321         int   interactive = isatty (fileno (stdin));
322
323 #ifdef HAVE_LIBREADLINE
324         using_history();
325         stifle_history(HISTORY);
326
327         if (!interactive)
328         {
329                 rl_prep_term_function = (rl_vintfunc_t *)noop_fn;
330                 rl_deprep_term_function = (rl_voidfunc_t *)noop_fn;
331         }
332
333         rl_attempted_completion_function = (CPPFunction *)command_completion;
334         rl_completion_entry_function = (void *)command_generator;
335 #endif
336         return interactive;
337 }
338
339 #ifndef HAVE_LIBREADLINE
340 #define add_history(s)
341 char * readline(char * prompt)
342 {
343         char line[2048];
344         int n = 0;
345         if (prompt)
346                 printf ("%s", prompt);
347         if (fgets(line, sizeof(line), stdin) == NULL)
348                 return (NULL);
349         n = strlen(line);
350         if (n && line[n-1] == '\n')
351                 line[n-1] = '\0';
352         return strdup(line);
353 }
354 #endif
355
356 /* this is the command execution machine */
357 int Parser_commands(void)
358 {
359         char *line, *s;
360         int rc = 0, save_error = 0;
361         int interactive;
362
363         interactive = init_input();
364
365         while(!done) {
366                 line = readline(interactive ? parser_prompt : NULL);
367
368                 if (!line) break;
369
370                 s = skipwhitespace(line);
371
372                 if (*s) {
373                         add_history(s);
374                         rc = execute_line(s);
375                 }
376                 /* stop on error if not-interactive */
377                 if (rc != 0 && !interactive) {
378                         if (save_error == 0)
379                                 save_error = rc;
380                         if (!ignore_errors)
381                                 done = 1;
382                 }
383
384                 free(line);
385         }
386         if (save_error)
387                 rc = save_error;
388         return rc;
389 }
390
391
392 /* sets the parser prompt */
393 void Parser_init(char * prompt, command_t * cmds)
394 {
395         done = 0;
396         top_level = cmds;
397         if (parser_prompt) free(parser_prompt);
398         parser_prompt = strdup(prompt);
399 }
400
401 /* frees the parser prompt */
402 void Parser_exit(int argc, char *argv[])
403 {
404         done = 1;
405         free(parser_prompt);
406         parser_prompt = NULL;
407 }
408
409 /* convert a string to an integer */
410 int Parser_int(char *s, int *val)
411 {
412         int ret;
413
414         if (*s != '0')
415                 ret = sscanf(s, "%d", val);
416         else if (*(s+1) != 'x')
417                 ret = sscanf(s, "%o", val);
418         else {
419                 s++;
420                 ret = sscanf(++s, "%x", val);
421         }
422
423         return(ret);
424 }
425
426
427 void Parser_qhelp(int argc, char *argv[]) {
428
429         printf("Available commands are:\n");
430
431         print_commands(NULL, top_level);
432         printf("For more help type: help command-name\n");
433 }
434
435 int Parser_help(int argc, char **argv)
436 {
437         char line[1024];
438         char *next, *prev, *tmp;
439         command_t *result, *ambig;
440         int i;
441
442         if ( argc == 1 ) {
443                 Parser_qhelp(argc, argv);
444                 return 0;
445         }
446
447         line[0]='\0';
448         for ( i = 1 ;  i < argc ; i++ ) {
449                 strcat(line, argv[i]);
450         }
451
452         switch ( process(line, &next, top_level, &result, &prev) ) {
453         case CMD_COMPLETE:
454                 fprintf(stderr, "%s: %s\n",line, result->pc_help);
455                 break;
456         case CMD_NONE:
457                 fprintf(stderr, "%s: Unknown command.\n", line);
458                 break;
459         case CMD_INCOMPLETE:
460                 fprintf(stderr,
461                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
462                         line, line);
463                 fprintf(stderr, "\t");
464                 for (i = 0; result->pc_sub_cmd[i].pc_name; i++) {
465                         fprintf(stderr, "%s ", result->pc_sub_cmd[i].pc_name);
466                 }
467                 fprintf(stderr, "\n");
468                 break;
469         case CMD_AMBIG:
470                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
471                 while( (ambig = find_cmd(prev, result, &tmp)) ) {
472                         fprintf(stderr, "%s ", ambig->pc_name);
473                         result = ambig + 1;
474                 }
475                 fprintf(stderr, "\n");
476                 break;
477         }
478         return 0;
479 }
480
481
482 void Parser_printhelp(char *cmd)
483 {
484         char *argv[] = { "help", cmd };
485         Parser_help(2, argv);
486 }
487
488
489 /*************************************************************************
490  * COMMANDS                                                              *
491  *************************************************************************/
492 static void print_commands(char * str, command_t * table) {
493         command_t * cmds;
494         char         buf[80];
495
496         for (cmds = table; cmds->pc_name; cmds++) {
497                 if (cmds->pc_func) {
498                         if (str) printf("\t%s %s\n", str, cmds->pc_name);
499                         else printf("\t%s\n", cmds->pc_name);
500                 }
501                 if (cmds->pc_sub_cmd) {
502                         if (str) {
503                                 sprintf(buf, "%s %s", str, cmds->pc_name);
504                                 print_commands(buf, cmds->pc_sub_cmd);
505                         } else {
506                                 print_commands(cmds->pc_name, cmds->pc_sub_cmd);
507                         }
508                 }
509         }
510 }
511
512 char *Parser_getstr(const char *prompt, const char *deft, char *res,
513                     size_t len)
514 {
515         char *line = NULL;
516         int size = strlen(prompt) + strlen(deft) + 8;
517         char *theprompt;
518         theprompt = malloc(size);
519         assert(theprompt);
520
521         sprintf(theprompt, "%s [%s]: ", prompt, deft);
522
523         line  = readline(theprompt);
524         free(theprompt);
525
526         if ( line == NULL || *line == '\0' ) {
527                 strncpy(res, deft, len);
528         } else {
529                 strncpy(res, line, len);
530         }
531
532         if ( line ) {
533                 free(line);
534                 return res;
535         } else {
536                 return NULL;
537         }
538 }
539
540 /* get integer from prompt, loop forever to get it */
541 int Parser_getint(const char *prompt, long min, long max, long deft, int base)
542 {
543         int rc;
544         long result;
545         char *line;
546         int size = strlen(prompt) + 40;
547         char *theprompt = malloc(size);
548         assert(theprompt);
549         sprintf(theprompt,"%s [%ld, (0x%lx)]: ", prompt, deft, deft);
550
551         fflush(stdout);
552
553         do {
554                 line = NULL;
555                 line = readline(theprompt);
556                 if ( !line ) {
557                         fprintf(stdout, "Please enter an integer.\n");
558                         fflush(stdout);
559                         continue;
560                 }
561                 if ( *line == '\0' ) {
562                         free(line);
563                         result =  deft;
564                         break;
565                 }
566                 rc = Parser_arg2int(line, &result, base);
567                 free(line);
568                 if ( rc != 0 ) {
569                         fprintf(stdout, "Invalid string.\n");
570                         fflush(stdout);
571                 } else if ( result > max || result < min ) {
572                         fprintf(stdout, "Error: response must lie between %ld and %ld.\n",
573                                 min, max);
574                         fflush(stdout);
575                 } else {
576                         break;
577                 }
578         } while ( 1 ) ;
579
580         if (theprompt)
581                 free(theprompt);
582         return result;
583
584 }
585
586 /* get boolean (starting with YyNn; loop forever */
587 int Parser_getbool(const char *prompt, int deft)
588 {
589         int result = 0;
590         char *line;
591         int size = strlen(prompt) + 8;
592         char *theprompt = malloc(size);
593         assert(theprompt);
594
595         fflush(stdout);
596
597         if ( deft != 0 && deft != 1 ) {
598                 fprintf(stderr, "Error: Parser_getbool given bad default %d\n",
599                         deft);
600                 assert ( 0 );
601         }
602         sprintf(theprompt, "%s [%s]: ", prompt, (deft==0)? "N" : "Y");
603
604         do {
605                 line = NULL;
606                 line = readline(theprompt);
607                 if ( line == NULL ) {
608                         result = deft;
609                         break;
610                 }
611                 if ( *line == '\0' ) {
612                         result = deft;
613                         break;
614                 }
615                 if ( *line == 'y' || *line == 'Y' ) {
616                         result = 1;
617                         break;
618                 }
619                 if ( *line == 'n' || *line == 'N' ) {
620                         result = 0;
621                         break;
622                 }
623                 if ( line )
624                         free(line);
625                 fprintf(stdout, "Invalid string. Must start with yY or nN\n");
626                 fflush(stdout);
627         } while ( 1 );
628
629         if ( line )
630                 free(line);
631         if ( theprompt )
632                 free(theprompt);
633         return result;
634 }
635
636 /* parse int out of a string or prompt for it */
637 long Parser_intarg(const char *inp, const char *prompt, int deft,
638                    int min, int max, int base)
639 {
640         long result;
641         int rc;
642
643         rc = Parser_arg2int(inp, &result, base);
644
645         if ( rc == 0 ) {
646                 return result;
647         } else {
648                 return Parser_getint(prompt, deft, min, max, base);
649         }
650 }
651
652 /* parse int out of a string or prompt for it */
653 char *Parser_strarg(char *inp, const char *prompt, const char *deft,
654                     char *answer, int len)
655 {
656         if ( inp == NULL || *inp == '\0' ) {
657                 return Parser_getstr(prompt, deft, answer, len);
658         } else
659                 return inp;
660 }
661
662 /* change a string into a number: return 0 on success. No invalid characters
663    allowed. The processing of base and validity follows strtol(3)*/
664 int Parser_arg2int(const char *inp, long *result, int base)
665 {
666         char *endptr;
667
668         if ( (base !=0) && (base < 2 || base > 36) )
669                 return 1;
670
671         *result = strtol(inp, &endptr, base);
672
673         if ( *inp != '\0' && *endptr == '\0' )
674                 return 0;
675         else
676                 return 1;
677 }
678
679 /* Convert human readable size string to and int; "1k" -> 1000 */
680 int Parser_size (int *sizep, char *str) {
681         int size;
682         char mod[32];
683
684         switch (sscanf (str, "%d%1[gGmMkK]", &size, mod)) {
685         default:
686                 return (-1);
687
688         case 1:
689                 *sizep = size;
690                 return (0);
691
692         case 2:
693                 switch (*mod) {
694                 case 'g':
695                 case 'G':
696                         *sizep = size << 30;
697                         return (0);
698
699                 case 'm':
700                 case 'M':
701                         *sizep = size << 20;
702                         return (0);
703
704                 case 'k':
705                 case 'K':
706                         *sizep = size << 10;
707                         return (0);
708
709                 default:
710                         *sizep = size;
711                         return (0);
712                 }
713         }
714 }
715
716 /* Convert a string boolean to an int; "enable" -> 1 */
717 int Parser_bool (int *b, char *str) {
718         if (!strcasecmp (str, "no") ||
719             !strcasecmp (str, "n") ||
720             !strcasecmp (str, "off") ||
721             !strcasecmp (str, "down") ||
722             !strcasecmp (str, "disable"))
723         {
724                 *b = 0;
725                 return (0);
726         }
727
728         if (!strcasecmp (str, "yes") ||
729             !strcasecmp (str, "y") ||
730             !strcasecmp (str, "on") ||
731             !strcasecmp (str, "up") ||
732             !strcasecmp (str, "enable"))
733         {
734                 *b = 1;
735                 return (0);
736         }
737
738         return (-1);
739 }
740
741 int Parser_quit(int argc, char **argv)
742 {
743         argc = argc;
744         argv = argv;
745         done = 1;
746         return 0;
747 }