Whamcloud - gitweb
b=3031
[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         
256         for (table = find_cmd(rl_line_buffer, match_tbl, &pos);
257              table; table = find_cmd(pos, match_tbl, &pos)) 
258         {
259
260                 if (*(pos - 1) == ' ') match_tbl = table->pc_sub_cmd;
261         }
262
263         return completion_matches(text, command_generator);
264 }
265 #endif
266
267 /* take a string and execute the function or print help */
268 int execute_line(char * line)
269 {
270         command_t         *cmd, *ambig;
271         char *prev;
272         char *next, *tmp;
273         char *argv[MAXARGS];
274         int         i;
275         int rc = 0;
276
277         switch (process(line, &next, top_level, &cmd, &prev)) {
278         case CMD_AMBIG:
279                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
280                 while( (ambig = find_cmd(prev, cmd, &tmp)) ) {
281                         fprintf(stderr, "%s ", ambig->pc_name);
282                         cmd = ambig + 1;
283                 }
284                 fprintf(stderr, "\n");
285                 break;
286         case CMD_NONE:
287                 fprintf(stderr, "No such command, type help\n");
288                 break;
289         case CMD_INCOMPLETE:
290                 fprintf(stderr,
291                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
292                         line, line);
293                 fprintf(stderr, "\t");
294                 for (i = 0; cmd->pc_sub_cmd[i].pc_name; i++) {
295                         fprintf(stderr, "%s ", cmd->pc_sub_cmd[i].pc_name);
296                 }
297                 fprintf(stderr, "\n");
298                 break;
299         case CMD_COMPLETE:
300                 i = line2args(line, argv, MAXARGS);
301                 rc = (cmd->pc_func)(i, argv);
302
303                 if (rc == CMD_HELP)
304                         fprintf(stderr, "%s\n", cmd->pc_help);
305
306                 break;
307         }
308
309         return rc;
310 }
311
312 int
313 noop_fn ()
314 {
315         return (0);
316 }
317
318 /* just in case you're ever in an airplane and discover you
319    forgot to install readline-dev. :) */
320 int init_input()
321 {
322         int   interactive = isatty (fileno (stdin));
323
324 #ifdef HAVE_LIBREADLINE
325         using_history();
326         stifle_history(HISTORY);
327
328         if (!interactive)
329         {
330                 rl_prep_term_function = (rl_vintfunc_t *)noop_fn;
331                 rl_deprep_term_function = (rl_voidfunc_t *)noop_fn;
332         }
333
334         rl_attempted_completion_function = (CPPFunction *)command_completion;
335         rl_completion_entry_function = (void *)command_generator;
336 #endif
337         return interactive;
338 }
339
340 #ifndef HAVE_LIBREADLINE
341 #define add_history(s)
342 char * readline(char * prompt)
343 {
344         char line[2048];
345         int n = 0;
346         if (prompt)
347                 printf ("%s", prompt);
348         if (fgets(line, sizeof(line), stdin) == NULL)
349                 return (NULL);
350         n = strlen(line);
351         if (n && line[n-1] == '\n')
352                 line[n-1] = '\0';
353         return strdup(line);
354 }
355 #endif
356
357 /* this is the command execution machine */
358 int Parser_commands(void)
359 {
360         char *line, *s;
361         int rc = 0, save_error = 0;
362         int interactive;
363
364         interactive = init_input();
365
366         while(!done) {
367                 line = readline(interactive ? parser_prompt : NULL);
368
369                 if (!line) break;
370
371                 s = skipwhitespace(line);
372
373                 if (*s) {
374                         add_history(s);
375                         rc = execute_line(s);
376                 }
377                 /* stop on error if not-interactive */
378                 if (rc != 0 && !interactive) {
379                         if (save_error == 0)
380                                 save_error = rc;
381                         if (!ignore_errors)
382                                 done = 1;
383                 }
384
385                 free(line);
386         }
387         if (save_error)
388                 rc = save_error;
389         return rc;
390 }
391
392
393 /* sets the parser prompt */
394 void Parser_init(char * prompt, command_t * cmds)
395 {
396         done = 0;
397         top_level = cmds;
398         if (parser_prompt) free(parser_prompt);
399         parser_prompt = strdup(prompt);
400 }
401
402 /* frees the parser prompt */
403 void Parser_exit(int argc, char *argv[])
404 {
405         done = 1;
406         free(parser_prompt);
407         parser_prompt = NULL;
408 }
409
410 /* convert a string to an integer */
411 int Parser_int(char *s, int *val)
412 {
413         int ret;
414
415         if (*s != '0')
416                 ret = sscanf(s, "%d", val);
417         else if (*(s+1) != 'x')
418                 ret = sscanf(s, "%o", val);
419         else {
420                 s++;
421                 ret = sscanf(++s, "%x", val);
422         }
423
424         return(ret);
425 }
426
427
428 void Parser_qhelp(int argc, char *argv[]) {
429
430         printf("Available commands are:\n");
431
432         print_commands(NULL, top_level);
433         printf("For more help type: help command-name\n");
434 }
435
436 int Parser_help(int argc, char **argv)
437 {
438         char line[1024];
439         char *next, *prev, *tmp;
440         command_t *result, *ambig;
441         int i;
442
443         if ( argc == 1 ) {
444                 Parser_qhelp(argc, argv);
445                 return 0;
446         }
447
448         line[0]='\0';
449         for ( i = 1 ;  i < argc ; i++ ) {
450                 strcat(line, argv[i]);
451         }
452
453         switch ( process(line, &next, top_level, &result, &prev) ) {
454         case CMD_COMPLETE:
455                 fprintf(stderr, "%s: %s\n",line, result->pc_help);
456                 break;
457         case CMD_NONE:
458                 fprintf(stderr, "%s: Unknown command.\n", line);
459                 break;
460         case CMD_INCOMPLETE:
461                 fprintf(stderr,
462                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
463                         line, line);
464                 fprintf(stderr, "\t");
465                 for (i = 0; result->pc_sub_cmd[i].pc_name; i++) {
466                         fprintf(stderr, "%s ", result->pc_sub_cmd[i].pc_name);
467                 }
468                 fprintf(stderr, "\n");
469                 break;
470         case CMD_AMBIG:
471                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
472                 while( (ambig = find_cmd(prev, result, &tmp)) ) {
473                         fprintf(stderr, "%s ", ambig->pc_name);
474                         result = ambig + 1;
475                 }
476                 fprintf(stderr, "\n");
477                 break;
478         }
479         return 0;
480 }
481
482
483 void Parser_printhelp(char *cmd)
484 {
485         char *argv[] = { "help", cmd };
486         Parser_help(2, argv);
487 }
488
489
490 /*************************************************************************
491  * COMMANDS                                                              *
492  *************************************************************************/
493 static void print_commands(char * str, command_t * table) {
494         command_t * cmds;
495         char         buf[80];
496
497         for (cmds = table; cmds->pc_name; cmds++) {
498                 if (cmds->pc_func) {
499                         if (str) printf("\t%s %s\n", str, cmds->pc_name);
500                         else printf("\t%s\n", cmds->pc_name);
501                 }
502                 if (cmds->pc_sub_cmd) {
503                         if (str) {
504                                 sprintf(buf, "%s %s", str, cmds->pc_name);
505                                 print_commands(buf, cmds->pc_sub_cmd);
506                         } else {
507                                 print_commands(cmds->pc_name, cmds->pc_sub_cmd);
508                         }
509                 }
510         }
511 }
512
513 char *Parser_getstr(const char *prompt, const char *deft, char *res,
514                     size_t len)
515 {
516         char *line = NULL;
517         int size = strlen(prompt) + strlen(deft) + 8;
518         char *theprompt;
519         theprompt = malloc(size);
520         assert(theprompt);
521
522         sprintf(theprompt, "%s [%s]: ", prompt, deft);
523
524         line  = readline(theprompt);
525         free(theprompt);
526
527         if ( line == NULL || *line == '\0' ) {
528                 strncpy(res, deft, len);
529         } else {
530                 strncpy(res, line, len);
531         }
532
533         if ( line ) {
534                 free(line);
535                 return res;
536         } else {
537                 return NULL;
538         }
539 }
540
541 /* get integer from prompt, loop forever to get it */
542 int Parser_getint(const char *prompt, long min, long max, long deft, int base)
543 {
544         int rc;
545         long result;
546         char *line;
547         int size = strlen(prompt) + 40;
548         char *theprompt = malloc(size);
549         assert(theprompt);
550         sprintf(theprompt,"%s [%ld, (0x%lx)]: ", prompt, deft, deft);
551
552         fflush(stdout);
553
554         do {
555                 line = NULL;
556                 line = readline(theprompt);
557                 if ( !line ) {
558                         fprintf(stdout, "Please enter an integer.\n");
559                         fflush(stdout);
560                         continue;
561                 }
562                 if ( *line == '\0' ) {
563                         free(line);
564                         result =  deft;
565                         break;
566                 }
567                 rc = Parser_arg2int(line, &result, base);
568                 free(line);
569                 if ( rc != 0 ) {
570                         fprintf(stdout, "Invalid string.\n");
571                         fflush(stdout);
572                 } else if ( result > max || result < min ) {
573                         fprintf(stdout, "Error: response must lie between %ld and %ld.\n",
574                                 min, max);
575                         fflush(stdout);
576                 } else {
577                         break;
578                 }
579         } while ( 1 ) ;
580
581         if (theprompt)
582                 free(theprompt);
583         return result;
584
585 }
586
587 /* get boolean (starting with YyNn; loop forever */
588 int Parser_getbool(const char *prompt, int deft)
589 {
590         int result = 0;
591         char *line;
592         int size = strlen(prompt) + 8;
593         char *theprompt = malloc(size);
594         assert(theprompt);
595
596         fflush(stdout);
597
598         if ( deft != 0 && deft != 1 ) {
599                 fprintf(stderr, "Error: Parser_getbool given bad default %d\n",
600                         deft);
601                 assert ( 0 );
602         }
603         sprintf(theprompt, "%s [%s]: ", prompt, (deft==0)? "N" : "Y");
604
605         do {
606                 line = NULL;
607                 line = readline(theprompt);
608                 if ( line == NULL ) {
609                         result = deft;
610                         break;
611                 }
612                 if ( *line == '\0' ) {
613                         result = deft;
614                         break;
615                 }
616                 if ( *line == 'y' || *line == 'Y' ) {
617                         result = 1;
618                         break;
619                 }
620                 if ( *line == 'n' || *line == 'N' ) {
621                         result = 0;
622                         break;
623                 }
624                 if ( line )
625                         free(line);
626                 fprintf(stdout, "Invalid string. Must start with yY or nN\n");
627                 fflush(stdout);
628         } while ( 1 );
629
630         if ( line )
631                 free(line);
632         if ( theprompt )
633                 free(theprompt);
634         return result;
635 }
636
637 /* parse int out of a string or prompt for it */
638 long Parser_intarg(const char *inp, const char *prompt, int deft,
639                    int min, int max, int base)
640 {
641         long result;
642         int rc;
643
644         rc = Parser_arg2int(inp, &result, base);
645
646         if ( rc == 0 ) {
647                 return result;
648         } else {
649                 return Parser_getint(prompt, deft, min, max, base);
650         }
651 }
652
653 /* parse int out of a string or prompt for it */
654 char *Parser_strarg(char *inp, const char *prompt, const char *deft,
655                     char *answer, int len)
656 {
657         if ( inp == NULL || *inp == '\0' ) {
658                 return Parser_getstr(prompt, deft, answer, len);
659         } else
660                 return inp;
661 }
662
663 /* change a string into a number: return 0 on success. No invalid characters
664    allowed. The processing of base and validity follows strtol(3)*/
665 int Parser_arg2int(const char *inp, long *result, int base)
666 {
667         char *endptr;
668
669         if ( (base !=0) && (base < 2 || base > 36) )
670                 return 1;
671
672         *result = strtol(inp, &endptr, base);
673
674         if ( *inp != '\0' && *endptr == '\0' )
675                 return 0;
676         else
677                 return 1;
678 }
679
680 /* Convert human readable size string to and int; "1k" -> 1000 */
681 int Parser_size (int *sizep, char *str) {
682         int size;
683         char mod[32];
684
685         switch (sscanf (str, "%d%1[gGmMkK]", &size, mod)) {
686         default:
687                 return (-1);
688
689         case 1:
690                 *sizep = size;
691                 return (0);
692
693         case 2:
694                 switch (*mod) {
695                 case 'g':
696                 case 'G':
697                         *sizep = size << 30;
698                         return (0);
699
700                 case 'm':
701                 case 'M':
702                         *sizep = size << 20;
703                         return (0);
704
705                 case 'k':
706                 case 'K':
707                         *sizep = size << 10;
708                         return (0);
709
710                 default:
711                         *sizep = size;
712                         return (0);
713                 }
714         }
715 }
716
717 /* Convert a string boolean to an int; "enable" -> 1 */
718 int Parser_bool (int *b, char *str) {
719         if (!strcasecmp (str, "no") ||
720             !strcasecmp (str, "n") ||
721             !strcasecmp (str, "off") ||
722             !strcasecmp (str, "down") ||
723             !strcasecmp (str, "disable"))
724         {
725                 *b = 0;
726                 return (0);
727         }
728
729         if (!strcasecmp (str, "yes") ||
730             !strcasecmp (str, "y") ||
731             !strcasecmp (str, "on") ||
732             !strcasecmp (str, "up") ||
733             !strcasecmp (str, "enable"))
734         {
735                 *b = 1;
736                 return (0);
737         }
738
739         return (-1);
740 }
741
742 int Parser_quit(int argc, char **argv)
743 {
744         argc = argc;
745         argv = argv;
746         done = 1;
747         return 0;
748 }