Whamcloud - gitweb
* Compiles after merging b1_4
[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         int size = 2048;
345         char *line = malloc(size);
346         char *ptr = line;
347         int c;
348
349         if (line == NULL)
350                 return NULL;
351         if (prompt)
352                 printf ("%s", prompt);
353
354         while (1) {
355                 if ((c = fgetc(stdin)) != EOF) {
356                         if (c == '\n')
357                                 goto out;
358                         *ptr++ = c;
359  
360                         if (ptr - line >= size - 1) {
361                                 char *tmp;
362
363                                 size *= 2;
364                                 tmp = malloc(size);
365                                 if (tmp == NULL)
366                                         goto outfree;
367                                 memcpy(tmp, line, ptr - line);
368                                 ptr = tmp + (ptr - line);
369                                 free(line);
370                                 line = tmp;
371                         }
372                 } else {
373                         if (ferror(stdin))
374                                 goto outfree;
375                         goto out;
376                 }
377         }
378 out:
379         *ptr = 0;
380         return line;
381 outfree:
382         free(line);
383         return NULL;
384 }
385 #endif
386
387 /* this is the command execution machine */
388 int Parser_commands(void)
389 {
390         char *line, *s;
391         int rc = 0, save_error = 0;
392         int interactive;
393
394         interactive = init_input();
395
396         while(!done) {
397                 line = readline(interactive ? parser_prompt : NULL);
398
399                 if (!line) break;
400
401                 s = skipwhitespace(line);
402
403                 if (*s) {
404                         add_history(s);
405                         rc = execute_line(s);
406                 }
407                 /* stop on error if not-interactive */
408                 if (rc != 0 && !interactive) {
409                         if (save_error == 0)
410                                 save_error = rc;
411                         if (!ignore_errors)
412                                 done = 1;
413                 }
414
415                 free(line);
416         }
417         if (save_error)
418                 rc = save_error;
419         return rc;
420 }
421
422
423 /* sets the parser prompt */
424 void Parser_init(char * prompt, command_t * cmds)
425 {
426         done = 0;
427         top_level = cmds;
428         if (parser_prompt) free(parser_prompt);
429         parser_prompt = strdup(prompt);
430 }
431
432 /* frees the parser prompt */
433 void Parser_exit(int argc, char *argv[])
434 {
435         done = 1;
436         free(parser_prompt);
437         parser_prompt = NULL;
438 }
439
440 /* convert a string to an integer */
441 int Parser_int(char *s, int *val)
442 {
443         int ret;
444
445         if (*s != '0')
446                 ret = sscanf(s, "%d", val);
447         else if (*(s+1) != 'x')
448                 ret = sscanf(s, "%o", val);
449         else {
450                 s++;
451                 ret = sscanf(++s, "%x", val);
452         }
453
454         return(ret);
455 }
456
457
458 void Parser_qhelp(int argc, char *argv[]) {
459
460         printf("Available commands are:\n");
461
462         print_commands(NULL, top_level);
463         printf("For more help type: help command-name\n");
464 }
465
466 int Parser_help(int argc, char **argv)
467 {
468         char line[1024];
469         char *next, *prev, *tmp;
470         command_t *result, *ambig;
471         int i;
472
473         if ( argc == 1 ) {
474                 Parser_qhelp(argc, argv);
475                 return 0;
476         }
477
478         line[0]='\0';
479         for ( i = 1 ;  i < argc ; i++ ) {
480                 strcat(line, argv[i]);
481         }
482
483         switch ( process(line, &next, top_level, &result, &prev) ) {
484         case CMD_COMPLETE:
485                 fprintf(stderr, "%s: %s\n",line, result->pc_help);
486                 break;
487         case CMD_NONE:
488                 fprintf(stderr, "%s: Unknown command.\n", line);
489                 break;
490         case CMD_INCOMPLETE:
491                 fprintf(stderr,
492                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
493                         line, line);
494                 fprintf(stderr, "\t");
495                 for (i = 0; result->pc_sub_cmd[i].pc_name; i++) {
496                         fprintf(stderr, "%s ", result->pc_sub_cmd[i].pc_name);
497                 }
498                 fprintf(stderr, "\n");
499                 break;
500         case CMD_AMBIG:
501                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
502                 while( (ambig = find_cmd(prev, result, &tmp)) ) {
503                         fprintf(stderr, "%s ", ambig->pc_name);
504                         result = ambig + 1;
505                 }
506                 fprintf(stderr, "\n");
507                 break;
508         }
509         return 0;
510 }
511
512
513 void Parser_printhelp(char *cmd)
514 {
515         char *argv[] = { "help", cmd };
516         Parser_help(2, argv);
517 }
518
519
520 /*************************************************************************
521  * COMMANDS                                                              *
522  *************************************************************************/
523 static void print_commands(char * str, command_t * table) {
524         command_t * cmds;
525         char         buf[80];
526
527         for (cmds = table; cmds->pc_name; cmds++) {
528                 if (cmds->pc_func) {
529                         if (str) printf("\t%s %s\n", str, cmds->pc_name);
530                         else printf("\t%s\n", cmds->pc_name);
531                 }
532                 if (cmds->pc_sub_cmd) {
533                         if (str) {
534                                 sprintf(buf, "%s %s", str, cmds->pc_name);
535                                 print_commands(buf, cmds->pc_sub_cmd);
536                         } else {
537                                 print_commands(cmds->pc_name, cmds->pc_sub_cmd);
538                         }
539                 }
540         }
541 }
542
543 char *Parser_getstr(const char *prompt, const char *deft, char *res,
544                     size_t len)
545 {
546         char *line = NULL;
547         int size = strlen(prompt) + strlen(deft) + 8;
548         char *theprompt;
549         theprompt = malloc(size);
550         assert(theprompt);
551
552         sprintf(theprompt, "%s [%s]: ", prompt, deft);
553
554         line  = readline(theprompt);
555         free(theprompt);
556
557         if ( line == NULL || *line == '\0' ) {
558                 strncpy(res, deft, len);
559         } else {
560                 strncpy(res, line, len);
561         }
562
563         if ( line ) {
564                 free(line);
565                 return res;
566         } else {
567                 return NULL;
568         }
569 }
570
571 /* get integer from prompt, loop forever to get it */
572 int Parser_getint(const char *prompt, long min, long max, long deft, int base)
573 {
574         int rc;
575         long result;
576         char *line;
577         int size = strlen(prompt) + 40;
578         char *theprompt = malloc(size);
579         assert(theprompt);
580         sprintf(theprompt,"%s [%ld, (0x%lx)]: ", prompt, deft, deft);
581
582         fflush(stdout);
583
584         do {
585                 line = NULL;
586                 line = readline(theprompt);
587                 if ( !line ) {
588                         fprintf(stdout, "Please enter an integer.\n");
589                         fflush(stdout);
590                         continue;
591                 }
592                 if ( *line == '\0' ) {
593                         free(line);
594                         result =  deft;
595                         break;
596                 }
597                 rc = Parser_arg2int(line, &result, base);
598                 free(line);
599                 if ( rc != 0 ) {
600                         fprintf(stdout, "Invalid string.\n");
601                         fflush(stdout);
602                 } else if ( result > max || result < min ) {
603                         fprintf(stdout, "Error: response must lie between %ld and %ld.\n",
604                                 min, max);
605                         fflush(stdout);
606                 } else {
607                         break;
608                 }
609         } while ( 1 ) ;
610
611         if (theprompt)
612                 free(theprompt);
613         return result;
614
615 }
616
617 /* get boolean (starting with YyNn; loop forever */
618 int Parser_getbool(const char *prompt, int deft)
619 {
620         int result = 0;
621         char *line;
622         int size = strlen(prompt) + 8;
623         char *theprompt = malloc(size);
624         assert(theprompt);
625
626         fflush(stdout);
627
628         if ( deft != 0 && deft != 1 ) {
629                 fprintf(stderr, "Error: Parser_getbool given bad default %d\n",
630                         deft);
631                 assert ( 0 );
632         }
633         sprintf(theprompt, "%s [%s]: ", prompt, (deft==0)? "N" : "Y");
634
635         do {
636                 line = NULL;
637                 line = readline(theprompt);
638                 if ( line == NULL ) {
639                         result = deft;
640                         break;
641                 }
642                 if ( *line == '\0' ) {
643                         result = deft;
644                         break;
645                 }
646                 if ( *line == 'y' || *line == 'Y' ) {
647                         result = 1;
648                         break;
649                 }
650                 if ( *line == 'n' || *line == 'N' ) {
651                         result = 0;
652                         break;
653                 }
654                 if ( line )
655                         free(line);
656                 fprintf(stdout, "Invalid string. Must start with yY or nN\n");
657                 fflush(stdout);
658         } while ( 1 );
659
660         if ( line )
661                 free(line);
662         if ( theprompt )
663                 free(theprompt);
664         return result;
665 }
666
667 /* parse int out of a string or prompt for it */
668 long Parser_intarg(const char *inp, const char *prompt, int deft,
669                    int min, int max, int base)
670 {
671         long result;
672         int rc;
673
674         rc = Parser_arg2int(inp, &result, base);
675
676         if ( rc == 0 ) {
677                 return result;
678         } else {
679                 return Parser_getint(prompt, deft, min, max, base);
680         }
681 }
682
683 /* parse int out of a string or prompt for it */
684 char *Parser_strarg(char *inp, const char *prompt, const char *deft,
685                     char *answer, int len)
686 {
687         if ( inp == NULL || *inp == '\0' ) {
688                 return Parser_getstr(prompt, deft, answer, len);
689         } else
690                 return inp;
691 }
692
693 /* change a string into a number: return 0 on success. No invalid characters
694    allowed. The processing of base and validity follows strtol(3)*/
695 int Parser_arg2int(const char *inp, long *result, int base)
696 {
697         char *endptr;
698
699         if ( (base !=0) && (base < 2 || base > 36) )
700                 return 1;
701
702         *result = strtol(inp, &endptr, base);
703
704         if ( *inp != '\0' && *endptr == '\0' )
705                 return 0;
706         else
707                 return 1;
708 }
709
710 /* Convert human readable size string to and int; "1k" -> 1000 */
711 int Parser_size (int *sizep, char *str) {
712         int size;
713         char mod[32];
714
715         switch (sscanf (str, "%d%1[gGmMkK]", &size, mod)) {
716         default:
717                 return (-1);
718
719         case 1:
720                 *sizep = size;
721                 return (0);
722
723         case 2:
724                 switch (*mod) {
725                 case 'g':
726                 case 'G':
727                         *sizep = size << 30;
728                         return (0);
729
730                 case 'm':
731                 case 'M':
732                         *sizep = size << 20;
733                         return (0);
734
735                 case 'k':
736                 case 'K':
737                         *sizep = size << 10;
738                         return (0);
739
740                 default:
741                         *sizep = size;
742                         return (0);
743                 }
744         }
745 }
746
747 /* Convert a string boolean to an int; "enable" -> 1 */
748 int Parser_bool (int *b, char *str) {
749         if (!strcasecmp (str, "no") ||
750             !strcasecmp (str, "n") ||
751             !strcasecmp (str, "off") ||
752             !strcasecmp (str, "down") ||
753             !strcasecmp (str, "disable"))
754         {
755                 *b = 0;
756                 return (0);
757         }
758
759         if (!strcasecmp (str, "yes") ||
760             !strcasecmp (str, "y") ||
761             !strcasecmp (str, "on") ||
762             !strcasecmp (str, "up") ||
763             !strcasecmp (str, "enable"))
764         {
765                 *b = 1;
766                 return (0);
767         }
768
769         return (-1);
770 }
771
772 int Parser_quit(int argc, char **argv)
773 {
774         argc = argc;
775         argv = argv;
776         done = 1;
777         return 0;
778 }