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