Whamcloud - gitweb
Numerous changes:
[fs/lustre-release.git] / lustre / utils / parser.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <ctype.h>
4 #include <string.h>
5 #include <stddef.h>
6 #include <sys/param.h>
7 #include <assert.h>
8
9 #define READLINE_LIBRARY
10 #include <readline/readline.h>
11
12 extern void using_history(void);
13 extern void stifle_history(int);
14 extern void add_history(char *);
15
16 #include "parser.h"
17 #define CMD_COMPLETE 0
18 #define CMD_INCOMPLETE 1
19 #define CMD_NONE 2
20 #define CMD_AMBIG 3
21
22 static command_t * top_level;      /* Top level of commands, initialized by
23                                     * InitParser                            */
24 static command_t * match_tbl;      /* Command completion against this table */
25 static char * parser_prompt = NULL;/* Parser prompt, set by InitParser      */
26 static int done;                   /* Set to 1 if user types exit or quit   */
27
28
29 /* static functions */
30 static char *skipwhitespace(char *s);
31 static char *skiptowhitespace(char *s);
32 static command_t *find_cmd(char *name, command_t cmds[], char **next);
33 static int process(char *s, char **next, command_t *lookup, command_t **result, char **prev);
34 static char *command_generator(const char *text, int state);
35 static char **command_completion(char *text, int start, int end);
36 static void print_commands(char *str, command_t *table);
37
38 static char * skipwhitespace(char * s) 
39 {
40     char * t;
41     int    len;
42
43     len = (int)strlen(s);
44     for (t = s; t <= s + len && isspace(*t); t++);
45     return(t);
46 }
47
48
49 static char * skiptowhitespace(char * s) 
50 {
51     char * t;
52
53     for (t = s; *t && !isspace(*t); t++);
54     return(t);
55 }
56
57 static int line2args(char *line, char **argv, int maxargs)
58 {
59     char *arg;
60     int i = 0; 
61     
62     arg = strtok(line, " \t");
63     if ( arg ) {
64         argv[i] = arg;
65         i++;
66     } else 
67         return 0;
68
69     while( (arg = strtok(NULL, " \t")) && (i <= maxargs)) {
70         argv[i] = arg;
71         i++;
72     }
73     return i;
74 }
75
76 /* find a command -- return it if unique otherwise print alternatives */
77     
78 static command_t *Parser_findargcmd(char *name, command_t cmds[])
79 {
80         command_t *cmd;
81         int i;
82
83         for (i = 0; cmds[i].pc_name; i++) {
84                 cmd = &cmds[i];
85
86                 if (strlen(name) != strlen(cmd->pc_name))
87                         continue;
88
89                 if (strlen(name) == strlen(cmd->pc_name)) {
90                         if (strcmp(name, cmd->pc_name) == 0) 
91                                 return cmd;
92                         else
93                                 continue;
94                 }
95
96         }
97         return NULL;
98 }
99
100 int Parser_execarg(int argc, char **argv, command_t cmds[])
101 {
102         command_t *cmd;
103         int i;
104
105         cmd = Parser_findargcmd(argv[0], cmds);
106         if ( cmd ) {
107                 return (cmd->pc_func)(argc, argv);
108         } else {
109                 printf("Try interactive use without arguments or use one of: ");
110                 for (i=0 ; cmds[i].pc_name ; i++) {
111                         cmd = &cmds[i];
112                         printf("\"%s\" ", cmd->pc_name);
113                 }
114                 printf("as argument.\n");
115         }
116         return -1;
117 }
118
119 /* returns the command_t * (NULL if not found) corresponding to a
120    _partial_ match with the first token in name.  It sets *next to
121    point to the following token. Does not modify *name. */
122 static command_t * find_cmd(char * name, command_t cmds[], char ** next) 
123 {
124     int    i, len;
125     
126     if (!cmds || !name ) 
127         return NULL;
128     
129     /* This sets name to point to the first non-white space character,
130     and next to the first whitespace after name, len to the length: do
131     this with strtok*/
132     name = skipwhitespace(name);
133     *next = skiptowhitespace(name);
134     len = *next - name;
135     if (len == 0) 
136         return NULL;
137
138     for (i = 0; cmds[i].pc_name; i++) {
139         if (strncasecmp(name, cmds[i].pc_name, len) == 0) {
140             *next = skipwhitespace(*next);
141             return(&cmds[i]);
142         }
143     }
144     return NULL;
145 }
146
147 /* Recursively process a command line string s and find the command
148    corresponding to it. This can be ambiguous, full, incomplete,
149    non-existent. */
150 static int process(char *s, char ** next, command_t *lookup,
151                    command_t **result, char **prev)
152 {
153     *result = find_cmd(s, lookup, next);
154     *prev = s; 
155
156     /* non existent */
157     if ( ! *result ) 
158         return CMD_NONE;
159
160     /* found entry: is it ambigous, i.e. not exact command name and
161        more than one command in the list matches.  Note that find_cmd
162        points to the first ambiguous entry */
163     if ( strncasecmp(s, (*result)->pc_name, strlen((*result)->pc_name)) &&
164          find_cmd(s, (*result) + 1, next)) 
165         return CMD_AMBIG;
166
167     /* found a unique command: component or full? */
168     if ( (*result)->pc_func ) {
169         return CMD_COMPLETE;
170     } else {
171         if ( *next == '\0' ) {
172             return CMD_INCOMPLETE;
173         } else {
174             return process(*next, next, (*result)->pc_sub_cmd, result, prev);
175         }
176     }
177 }
178
179 static char * command_generator(const char * text, int state) 
180 {
181     static int index,
182                len;
183     char       *name;
184
185     /* Do we have a match table? */
186     if (!match_tbl) 
187         return NULL;
188     
189     /* If this is the first time called on this word, state is 0 */
190     if (!state) {
191         index = 0;
192         len = (int)strlen(text);
193     }
194
195     /* Return the next name in the command list that paritally matches test */
196     while ( (name = (match_tbl + index)->pc_name) ) {
197         index++;
198
199         if (strncasecmp(name, text, len) == 0) {
200             return(strdup(name));
201         }
202     }
203
204     /* No more matches */
205     return NULL;
206 }
207
208 /* probably called by readline */
209 static char **command_completion(char * text, int start, int end) 
210 {
211     command_t   * table;
212     char        * pos;
213
214     match_tbl = top_level;
215     for (table = find_cmd(rl_line_buffer, match_tbl, &pos);
216          table;
217          table = find_cmd(pos, match_tbl, &pos)) {
218
219         if (*(pos - 1) == ' ') match_tbl = table->pc_sub_cmd;
220     }
221
222     return(rl_completion_matches(text, command_generator));
223 }
224
225 /* take a string and execute the function or print help */
226 void execute_line(char * line) 
227 {
228     command_t   *cmd, *ambig;
229     char *prev;
230     char *next, *tmp;
231     char *argv[MAXARGS];
232     int  i;
233
234     switch( process(line, &next, top_level, &cmd, &prev) ) {
235     case CMD_AMBIG:
236         fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
237         while( (ambig = find_cmd(prev, cmd, &tmp)) ) {
238             fprintf(stderr, "%s ", ambig->pc_name);
239             cmd = ambig + 1;
240         }
241         fprintf(stderr, "\n");
242         break;
243     case CMD_NONE:
244         fprintf(stderr, "No such command, type help\n");
245         break;
246     case CMD_INCOMPLETE:
247         fprintf(stderr,
248                 "'%s' incomplete command.  Use '%s x' where x is one of:\n",
249                 line, line);
250         fprintf(stderr, "\t");
251         for (i = 0; cmd->pc_sub_cmd[i].pc_name; i++) {
252             fprintf(stderr, "%s ", cmd->pc_sub_cmd[i].pc_name);
253         }
254         fprintf(stderr, "\n");
255         break;
256     case CMD_COMPLETE:
257         i = line2args(line, argv, MAXARGS);
258         (cmd->pc_func)(i, argv);
259         break;
260     }
261     
262     return;
263 }
264
265 /* this is the command execution machine */
266 void Parser_commands(void) 
267 {
268     char *line,
269          *s;
270
271     using_history();
272     stifle_history(HISTORY);
273
274     rl_attempted_completion_function = 
275             (rl_completion_func_t *)command_completion;
276     rl_completion_entry_function = (rl_compentry_func_t *)command_generator;
277     
278     while(!done) {
279         line = readline(parser_prompt);
280
281         if (!line) break;
282
283         s = skipwhitespace(line);
284
285         if (*s) {
286             add_history(s);
287             execute_line(s);
288         }
289
290         free(line);
291     }
292 }
293
294
295 /* sets the parser prompt */
296 void Parser_init(char * prompt, command_t * cmds) 
297 {
298     done = 0;
299     top_level = cmds;
300     if (parser_prompt) free(parser_prompt);
301     parser_prompt = strdup(prompt);
302 }
303
304 /* frees the parser prompt */
305 void Parser_exit(int argc, char *argv[]) 
306 {
307     done = 1;
308     free(parser_prompt);
309     parser_prompt = NULL;
310 }
311
312 /* convert a string to an integer */
313 int Parser_int(char *s, int *val)
314 {
315     int ret;
316
317     if (*s != '0')
318         ret = sscanf(s, "%d", val);
319     else if (*(s+1) != 'x')
320         ret = sscanf(s, "%o", val);
321     else {
322         s++;
323         ret = sscanf(++s, "%x", val);
324     }
325
326     return(ret);
327 }
328
329
330     
331 void Parser_qhelp(int argc, char *argv[]) {
332
333     printf("Available commands are:\n");
334         
335     print_commands(NULL, top_level);
336     printf("For more help type: help command-name\n");
337 }
338
339 int Parser_help(int argc, char **argv) 
340 {
341     char line[1024];
342     char *next, *prev, *tmp;
343     command_t *result, *ambig;
344     int i;
345
346     if ( argc == 1 ) {
347         Parser_qhelp(argc, argv);
348         return 0;
349     }
350
351     line[0]='\0';
352     for ( i = 1 ;  i < argc ; i++ ) {
353         strcat(line, argv[i]);
354     }
355
356     switch ( process(line, &next, top_level, &result, &prev) ) {
357     case CMD_COMPLETE:
358         fprintf(stderr, "%s: %s\n",line, result->pc_help);
359         break;
360     case CMD_NONE:
361         fprintf(stderr, "%s: Unknown command.\n", line);
362         break;
363     case CMD_INCOMPLETE:
364         fprintf(stderr,
365                 "'%s' incomplete command.  Use '%s x' where x is one of:\n",
366                 line, line);
367         fprintf(stderr, "\t");
368         for (i = 0; result->pc_sub_cmd[i].pc_name; i++) {
369             fprintf(stderr, "%s ", result->pc_sub_cmd[i].pc_name);
370         }
371         fprintf(stderr, "\n");
372         break;
373     case CMD_AMBIG:
374         fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
375         while( (ambig = find_cmd(prev, result, &tmp)) ) {
376             fprintf(stderr, "%s ", ambig->pc_name);
377             result = ambig + 1;
378         }
379         fprintf(stderr, "\n");
380         break;
381     }
382     return 0;
383 }  
384
385 /*************************************************************************
386  * COMMANDS                                                              *
387  *************************************************************************/ 
388
389
390 static void print_commands(char * str, command_t * table) {
391     command_t * cmds;
392     char        buf[80];
393
394     for (cmds = table; cmds->pc_name; cmds++) {
395         if (cmds->pc_func) {
396             if (str) printf("\t%s %s\n", str, cmds->pc_name);
397             else printf("\t%s\n", cmds->pc_name);
398         }
399         if (cmds->pc_sub_cmd) {
400             if (str) {
401                 sprintf(buf, "%s %s", str, cmds->pc_name);
402                 print_commands(buf, cmds->pc_sub_cmd);
403             } else {
404                 print_commands(cmds->pc_name, cmds->pc_sub_cmd);
405             }
406         }
407     }
408 }
409
410 char *Parser_getstr(const char *prompt, const char *deft, char *res, 
411                     size_t len)
412 {
413     char *line = NULL;
414     int size = strlen(prompt) + strlen(deft) + 8;
415     char *theprompt;
416     theprompt = malloc(size);
417     assert(theprompt);
418
419     sprintf(theprompt, "%s [%s]: ", prompt, deft);
420
421     line  = readline(theprompt);
422     free(theprompt);
423
424     if ( line == NULL || *line == '\0' ) {
425         strncpy(res, deft, len);
426     } else {
427         strncpy(res, line, len);
428     }
429
430     if ( line ) {
431         free(line);
432         return res;
433     } else {
434         return NULL;
435     }
436 }
437
438 /* get integer from prompt, loop forever to get it */
439 int Parser_getint(const char *prompt, long min, long max, long deft, int base)
440 {
441     int rc;
442     long result;
443     char *line;
444     int size = strlen(prompt) + 40;
445     char *theprompt = malloc(size);
446     assert(theprompt);
447     sprintf(theprompt,"%s [%ld, (0x%lx)]: ", prompt, deft, deft);
448
449     fflush(stdout);
450
451     do {
452         line = NULL;
453         line = readline(theprompt);
454         if ( !line ) {
455             fprintf(stdout, "Please enter an integer.\n");
456             fflush(stdout);
457             continue;
458         }
459         if ( *line == '\0' ) {
460             free(line);
461             result =  deft;
462             break;
463         }
464         rc = Parser_arg2int(line, &result, base);
465         free(line);
466         if ( rc != 0 ) {
467             fprintf(stdout, "Invalid string.\n");
468             fflush(stdout);
469         } else if ( result > max || result < min ) {
470             fprintf(stdout, "Error: response must lie between %ld and %ld.\n",
471                     min, max);
472             fflush(stdout);
473         } else {
474             break;
475         }
476     } while ( 1 ) ;
477
478     if (theprompt)
479         free(theprompt);
480     return result;
481
482 }
483
484 /* get boolean (starting with YyNn; loop forever */
485 int Parser_getbool(const char *prompt, int deft)
486 {
487     int result = 0;
488     char *line;
489     int size = strlen(prompt) + 8;
490     char *theprompt = malloc(size);
491     assert(theprompt);
492
493     fflush(stdout);
494     
495     if ( deft != 0 && deft != 1 ) {
496         fprintf(stderr, "Error: Parser_getbool given bad default (%d).\n",
497                 deft);
498         assert ( 0 );
499     }
500     sprintf(theprompt, "%s [%s]: ", prompt, (deft==0)? "N" : "Y");
501
502     do {
503         line = NULL;
504         line = readline(theprompt);
505         if ( line == NULL ) {
506             result = deft;
507             break;
508         }
509         if ( *line == '\0' ) {
510             result = deft;
511             break;
512         }
513         if ( *line == 'y' || *line == 'Y' ) {
514             result = 1;
515             break;
516         }
517         if ( *line == 'n' || *line == 'N' ) {
518             result = 0;
519             break;
520         }
521         if ( line ) 
522             free(line);
523         fprintf(stdout, "Invalid string. Must start with yY or nN\n");
524         fflush(stdout);
525     } while ( 1 );
526
527     if ( line ) 
528         free(line);
529     if ( theprompt ) 
530         free(theprompt);
531     return result;
532 }
533
534 /* parse int out of a string or prompt for it */
535 long Parser_intarg(const char *inp, const char *prompt, int deft,
536                   int min, int max, int base)
537 {
538     long result;
539     int rc; 
540     
541     rc = Parser_arg2int(inp, &result, base);
542
543     if ( rc == 0 ) {
544         return result;
545     } else {
546         return Parser_getint(prompt, deft, min, max, base);
547     }
548 }
549
550 /* parse int out of a string or prompt for it */
551 char *Parser_strarg(char *inp, const char *prompt, const char *deft,
552                     char *answer, int len)
553 {
554     
555     if ( inp == NULL || *inp == '\0' ) {
556         return Parser_getstr(prompt, deft, answer, len);
557     } else 
558         return inp;
559 }
560
561 /* change a string into a number: return 0 on success. No invalid characters
562    allowed. The processing of base and validity follows strtol(3)*/
563 int Parser_arg2int(const char *inp, long *result, int base)
564 {
565     char *endptr;
566
567     if ( (base !=0) && (base < 2 || base > 36) )
568         return 1;
569
570     *result = strtol(inp, &endptr, base);
571
572     if ( *inp != '\0' && *endptr == '\0' )
573         return 0;
574     else 
575         return 1;
576 }
577
578 int Parser_quit(int argc, char **argv)
579 {
580         argc = argc;
581         argv = argv;
582         exit(0);
583         return 0;
584 }