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