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