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