Whamcloud - gitweb
be97809ce45e749d7847a76a13c948b3c1f92938
[fs/lustre-release.git] / libcfs / libcfs / util / parser.c
1 /*
2  * Copyright (C) 2001 Cluster File Systems, Inc.
3  *
4  * Copyright (c) 2014, 2017, Intel Corporation.
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
23 #include <stddef.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <assert.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <getopt.h>
30 #include <malloc.h>
31 #ifdef HAVE_LIBREADLINE
32 # include <readline/history.h>
33 # include <readline/readline.h>
34 #endif /* HAVE_LIBREADLINE */
35 #include <string.h>
36 #include <unistd.h>
37
38 #include <libcfs/util/parser.h>
39 #include <linux/lustre/lustre_ver.h>
40
41 /* Top level of commands, initialized by InitParser */
42 static command_t *top_level;
43 /* Parser prompt, set by InitParser */
44 static char *parser_prompt;
45 /* Set to 1 if user types exit or quit */
46 static int done;
47 /*
48  * Normally, the parser will quit when an error occurs in non-interacive
49  * mode. Setting this to non-zero will force it to keep buggering on.
50  */
51 static int ignore_errors;
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
60 static char *skipwhitespace(char *s)
61 {
62         char *t;
63         int len;
64
65         len = (int)strlen(s);
66         for (t = s; t <= s + len && isspace(*t); t++)
67                 ;
68         return t;
69 }
70
71 static char *skiptowhitespace(char *s)
72 {
73         char *t;
74
75         for (t = s; *t && !isspace(*t); t++)
76                 ;
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 || maxargs < 1)
87                 return 0;
88
89         argv[i++] = arg;
90         while ((arg = strtok(NULL, " \t")) != NULL && i < maxargs)
91                 argv[i++] = arg;
92         return i;
93 }
94
95 /* find a command -- return it if unique otherwise print alternatives */
96 static command_t *Parser_findargcmd(char *name, command_t cmds[])
97 {
98         command_t *cmd;
99
100         for (cmd = cmds; cmd->pc_name; cmd++) {
101                 if (strcmp(name, cmd->pc_name) == 0)
102                         return cmd;
103         }
104         return NULL;
105 }
106
107 void Parser_ignore_errors(int ignore)
108 {
109         ignore_errors = ignore;
110 }
111
112 int Parser_execarg(int argc, char **argv, command_t cmds[])
113 {
114         command_t *cmd;
115
116         cmd = Parser_findargcmd(argv[0], cmds);
117         if (cmd && cmd->pc_func) {
118                 int rc = cmd->pc_func(argc, argv);
119
120                 if (rc == CMD_HELP) {
121                         fprintf(stdout, "%s\n", cmd->pc_help);
122                         fflush(stdout);
123                 }
124                 return rc;
125         }
126         fprintf(stderr,
127                 "%s: '%s' is not a valid command. See '%s --list-commands'.\n",
128                 program_invocation_short_name, argv[0],
129                 program_invocation_short_name);
130
131         return -1;
132 }
133
134 /*
135  * Returns the command_t * (NULL if not found) corresponding to a
136  * _partial_ match with the first token in name.  It sets *next to
137  * point to the following token. Does not modify *name.
138  */
139 static command_t *find_cmd(char *name, command_t cmds[], char **next)
140 {
141         int i, len;
142
143         if (!cmds || !name)
144                 return NULL;
145
146         /*
147          * This sets name to point to the first non-white space character,
148          * and next to the first whitespace after name, len to the length: do
149          * this with strtok
150          */
151         name = skipwhitespace(name);
152         *next = skiptowhitespace(name);
153         len = (int)(*next - name);
154         if (len == 0)
155                 return NULL;
156
157         for (i = 0; cmds[i].pc_name; i++) {
158                 if (strncasecmp(name, cmds[i].pc_name, len) == 0) {
159                         *next = skipwhitespace(*next);
160                         return &cmds[i];
161                 }
162         }
163         return NULL;
164 }
165
166 /*
167  * Recursively process a command line string s and find the command
168  * corresponding to it. This can be ambiguous, full, incomplete,
169  * non-existent.
170  */
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         /*
182          * found entry: is it ambigous, i.e. not exact command name and
183          * more than one command in the list matches.  Note that find_cmd
184          * points to the first ambiguous entry
185          */
186         if (strncasecmp(s, (*result)->pc_name, strlen((*result)->pc_name))) {
187                 char *another_next;
188                 int found_another = 0;
189
190                 command_t *another_result = find_cmd(s, (*result) + 1,
191                                                      &another_next);
192                 while (another_result) {
193                         if (strncasecmp(s, another_result->pc_name,
194                                         strlen(another_result->pc_name)) == 0) {
195                                 *result = another_result;
196                                 *next = another_next;
197                                 goto got_it;
198                         }
199                         another_result = find_cmd(s, another_result + 1,
200                                                   &another_next);
201                         found_another = 1;
202                 }
203                 if (found_another)
204                         return CMD_AMBIG;
205         }
206
207 got_it:
208         /* found a unique command: component or full? */
209         if ((*result)->pc_func)
210                 return CMD_COMPLETE;
211
212         if (**next == '\0')
213                 return CMD_INCOMPLETE;
214         return process(*next, next, (*result)->pc_sub_cmd,
215                        result, prev);
216 }
217
218 #ifdef HAVE_LIBREADLINE
219 static command_t *match_tbl; /* Command completion against this table */
220 static char *command_generator(const char *text, int state)
221 {
222         static int index, len;
223         char *name;
224
225         /* Do we have a match table? */
226         if (!match_tbl)
227                 return NULL;
228
229         /* If this is the first time called on this word, state is 0 */
230         if (!state) {
231                 index = 0;
232                 len = (int)strlen(text);
233         }
234
235         /* Return next name in the command list that paritally matches test */
236         while ((name = (match_tbl + index)->pc_name)) {
237                 index++;
238
239                 if (strncasecmp(name, text, len) == 0)
240                         return strdup(name);
241         }
242
243         /* No more matches */
244         return NULL;
245 }
246
247 /* probably called by readline */
248 static char **command_completion(const char *text, int start, int end)
249 {
250         command_t *table;
251         char *pos;
252
253         match_tbl = top_level;
254
255         for (table = find_cmd(rl_line_buffer, match_tbl, &pos);
256              table; table = find_cmd(pos, match_tbl, &pos)) {
257                 if (*(pos - 1) == ' ')
258                         match_tbl = table->pc_sub_cmd;
259         }
260
261         return rl_completion_matches(text, command_generator);
262 }
263 #endif
264
265 /* take a string and execute the function or print help */
266 static int execute_line(char *line)
267 {
268         command_t *cmd, *ambig;
269         char *prev;
270         char *next, *tmp;
271         char *argv[MAXARGS];
272         int i;
273         int rc = 0;
274
275         switch (process(line, &next, top_level, &cmd, &prev)) {
276         case CMD_AMBIG:
277                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
278                 while ((ambig = find_cmd(prev, cmd, &tmp))) {
279                         fprintf(stderr, "%s ", ambig->pc_name);
280                         cmd = ambig + 1;
281                 }
282                 fprintf(stderr, "\n");
283                 break;
284         case CMD_NONE:
285                 fprintf(stderr, "No such command, type help\n");
286                 break;
287         case CMD_INCOMPLETE:
288                 fprintf(stderr,
289                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
290                         line, line);
291                 fprintf(stderr, "\t");
292                 for (i = 0; cmd->pc_sub_cmd[i].pc_name; i++)
293                         fprintf(stderr, "%s ", cmd->pc_sub_cmd[i].pc_name);
294                 fprintf(stderr, "\n");
295                 break;
296         case CMD_COMPLETE:
297                 optind = 0;
298                 i = line2args(line, argv, MAXARGS);
299                 rc = cmd->pc_func(i, argv);
300
301                 if (rc == CMD_HELP) {
302                         fprintf(stdout, "%s\n", cmd->pc_help);
303                         fflush(stdout);
304                 }
305
306                 break;
307         }
308
309         return rc;
310 }
311
312 #ifdef HAVE_LIBREADLINE
313 static void noop_int_fn(int unused) { }
314 static void noop_void_fn(void) { }
315 #endif
316
317 /*
318  * just in case you're ever in an airplane and discover you
319  * forgot to install readline-dev. :)
320  */
321 static int init_input(void)
322 {
323         int interactive = isatty(fileno(stdin));
324
325 #ifdef HAVE_LIBREADLINE
326         using_history();
327         stifle_history(HISTORY);
328
329         if (!interactive) {
330                 rl_prep_term_function = noop_int_fn;
331                 rl_deprep_term_function = noop_void_fn;
332         }
333
334         rl_attempted_completion_function = command_completion;
335         rl_completion_entry_function = command_generator;
336 #endif
337         return interactive;
338 }
339
340 #ifndef HAVE_LIBREADLINE
341 #define add_history(s)
342 static char *readline(char *prompt)
343 {
344         int size = 2048;
345         char *line = malloc(size);
346         char *ptr = line;
347         int c;
348         int eof = 0;
349
350         if (!line)
351                 return NULL;
352         if (prompt)
353                 printf("%s", prompt);
354
355         while (1) {
356                 if ((c = fgetc(stdin)) != EOF) {
357                         if (c == '\n')
358                                 goto out;
359                         *ptr++ = (char)c;
360
361                         if (ptr - line >= size - 1) {
362                                 char *tmp;
363
364                                 size *= 2;
365                                 tmp = malloc(size);
366                                 if (!tmp)
367                                         goto outfree;
368                                 memcpy(tmp, line, ptr - line);
369                                 ptr = tmp + (ptr - line);
370                                 free(line);
371                                 line = tmp;
372                         }
373                 } else {
374                         eof = 1;
375                         if (ferror(stdin) || feof(stdin))
376                                 goto outfree;
377                         goto out;
378                 }
379         }
380 out:
381         *ptr = 0;
382         if (eof && (strlen(line) == 0)) {
383                 free(line);
384                 line = NULL;
385         }
386         return line;
387 outfree:
388         free(line);
389         return NULL;
390 }
391 #endif
392
393 /* this is the command execution machine */
394 int Parser_commands(void)
395 {
396         char *line, *s;
397         int rc = 0, save_error = 0;
398         int interactive;
399
400         interactive = init_input();
401
402         while (!done) {
403                 line = readline(interactive ? parser_prompt : NULL);
404
405                 if (!line)
406                         break;
407
408                 s = skipwhitespace(line);
409
410                 if (*s) {
411                         add_history(s);
412                         rc = execute_line(s);
413                 }
414                 /* stop on error if not-interactive */
415                 if (rc != 0 && !interactive) {
416                         if (save_error == 0)
417                                 save_error = rc;
418                         if (!ignore_errors)
419                                 done = 1;
420                 }
421                 free(line);
422         }
423
424         if (save_error)
425                 rc = save_error;
426
427         return rc;
428 }
429
430 /* sets the parser prompt */
431 void Parser_init(char *prompt, command_t *cmds)
432 {
433         done = 0;
434         top_level = cmds;
435         if (parser_prompt)
436                 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 void Parser_qhelp(int argc, char *argv[])
466 {
467         printf("usage: %s [COMMAND] [OPTIONS]... [ARGS]\n",
468                program_invocation_short_name);
469         printf("Without any parameters, interactive mode is invoked\n");
470
471         printf("Try '%s help <COMMAND>', or '%s --list-commands' for a list of commands.\n",
472                 program_invocation_short_name, program_invocation_short_name);
473 }
474
475 int Parser_help(int argc, char **argv)
476 {
477         char line[1024];
478         char *next, *prev, *tmp;
479         command_t *result, *ambig;
480         int i;
481
482         if (argc == 1) {
483                 Parser_qhelp(argc, argv);
484                 return 0;
485         }
486
487         /*
488          * Joining command line arguments without space is not critical here
489          * because of this string is used for search a help topic and assume
490          * that only one argument will be (the name of topic). For example:
491          * lst > help ping run
492          * pingrun: Unknown command.
493          */
494         line[0] = '\0';
495         for (i = 1;  i < argc; i++) {
496                 if (strlen(argv[i]) >= sizeof(line) - strlen(line))
497                         return -E2BIG;
498                 /*
499                  * The function strlcat() cannot be used here because of
500                  * this function is used in LNet utils that is not linked
501                  * with libcfs.a.
502                  */
503                 strncat(line, argv[i], sizeof(line) - strlen(line));
504         }
505
506         switch (process(line, &next, top_level, &result, &prev)) {
507         case CMD_COMPLETE:
508                 fprintf(stderr, "%s: %s\n", line, result->pc_help);
509                 break;
510         case CMD_NONE:
511                 fprintf(stderr, "%s: Unknown command.\n", line);
512                 break;
513         case CMD_INCOMPLETE:
514                 fprintf(stderr,
515                         "'%s' incomplete command.  Use '%s x' where x is one of:\n",
516                         line, line);
517                 fprintf(stderr, "\t");
518                 for (i = 0; result->pc_sub_cmd[i].pc_name; i++)
519                         fprintf(stderr, "%s ", result->pc_sub_cmd[i].pc_name);
520                 fprintf(stderr, "\n");
521                 break;
522         case CMD_AMBIG:
523                 fprintf(stderr, "Ambiguous command \'%s\'\nOptions: ", line);
524                 while ((ambig = find_cmd(prev, result, &tmp))) {
525                         fprintf(stderr, "%s ", ambig->pc_name);
526                         result = ambig + 1;
527                 }
528                 fprintf(stderr, "\n");
529                 break;
530         }
531         return 0;
532 }
533
534 void Parser_printhelp(char *cmd)
535 {
536         char *argv[] = { "help", cmd };
537
538         Parser_help(2, argv);
539 }
540
541 /* COMMANDS */
542
543 /**
544  * Parser_list_commands() - Output a list of the supported commands.
545  * @cmdlist:      Array of structures describing the commands.
546  * @buffer:       String buffer used to temporarily store the output text.
547  * @buf_size:     Length of the string buffer.
548  * @parent_cmd:   When called recursively, contains the name of the parent cmd.
549  * @col_start:    Column where printing should begin.
550  * @col_num:      The number of commands printed in a single row.
551  *
552  * The commands and subcommands supported by the utility are printed, arranged
553  * into several columns for readability.  If a command supports subcommands, the
554  * function is called recursively, and the name of the parent command is
555  * supplied so that it can be prepended to the names of the subcommands.
556  *
557  * Return: The number of items that were printed.
558  */
559 int Parser_list_commands(const command_t *cmdlist, char *buffer,
560                          size_t buf_size, const char *parent_cmd,
561                          int col_start, int col_num)
562 {
563         int col = col_start;
564         int char_max;
565         int len;
566         int count = 0;
567         int rc;
568
569         if (col_start >= col_num)
570                 return 0;
571
572         char_max = (buf_size - 1) / col_num; /* Reserve 1 char for NUL */
573
574         for (; cmdlist->pc_name; cmdlist++) {
575                 if (!cmdlist->pc_func && !cmdlist->pc_sub_cmd)
576                         break;
577                 count++;
578                 if (parent_cmd)
579                         len = snprintf(&buffer[col * char_max],
580                                        char_max + 1, "%s %s", parent_cmd,
581                                        cmdlist->pc_name);
582                 else
583                         len = snprintf(&buffer[col * char_max],
584                                        char_max + 1, "%s", cmdlist->pc_name);
585
586                 /* Add trailing spaces to pad the entry to the column size */
587                 if (len < char_max) {
588                         snprintf(&buffer[col * char_max] + len,
589                                  char_max - len + 1, "%*s", char_max - len,
590                                  " ");
591                 } else {
592                         buffer[(col + 1) * char_max - 1] = ' ';
593                 }
594
595                 col++;
596                 if (col >= col_num) {
597                         fprintf(stdout, "%s\n", buffer);
598                         col = 0;
599                         buffer[0] = '\0';
600                 }
601
602                 if (cmdlist->pc_sub_cmd) {
603                         rc = Parser_list_commands(cmdlist->pc_sub_cmd, buffer,
604                                                   buf_size, cmdlist->pc_name,
605                                                   col, col_num);
606                         col = (col + rc) % col_num;
607                         count += rc;
608                 }
609         }
610         if (!parent_cmd && col != 0)
611                 fprintf(stdout, "%s\n", buffer);
612         return count;
613 }
614
615 char *Parser_getstr(const char *prompt, const char *deft, char *res,
616                     size_t len)
617 {
618         char *line = NULL;
619         int size = strlen(prompt) + strlen(deft) + 8;
620         char *theprompt;
621
622         theprompt = malloc(size);
623         assert(theprompt);
624
625         snprintf(theprompt, size, "%s [%s]: ", prompt, deft);
626
627         line  = readline(theprompt);
628         free(theprompt);
629
630         /*
631          * The function strlcpy() cannot be used here because of
632          * this function is used in LNet utils that is not linked
633          * with libcfs.a.
634          */
635         if (!line || *line == '\0')
636                 strncpy(res, deft, len);
637         else
638                 strncpy(res, line, len);
639         res[len - 1] = '\0';
640
641         if (line) {
642                 free(line);
643                 return res;
644         }
645         return NULL;
646 }
647
648 /* get integer from prompt, loop forever to get it */
649 int Parser_getint(const char *prompt, long min, long max, long deft, int base)
650 {
651         int rc;
652         long result;
653         char *line;
654         int size = strlen(prompt) + 40;
655         char *theprompt = malloc(size);
656
657         assert(theprompt);
658         snprintf(theprompt, size, "%s [%ld, (0x%lx)]: ", prompt, deft, deft);
659         fflush(stdout);
660
661         do {
662                 line = NULL;
663                 line = readline(theprompt);
664                 if (!line) {
665                         fprintf(stdout, "Please enter an integer.\n");
666                         fflush(stdout);
667                         continue;
668                 }
669                 if (*line == '\0') {
670                         free(line);
671                         result =  deft;
672                         break;
673                 }
674                 rc = Parser_arg2int(line, &result, base);
675                 free(line);
676                 if (rc != 0) {
677                         fprintf(stdout, "Invalid string.\n");
678                         fflush(stdout);
679                 } else if (result > max || result < min) {
680                         fprintf(stdout,
681                                 "Error: response must lie between %ld and %ld.\n",
682                                 min, max);
683                         fflush(stdout);
684                 } else {
685                         break;
686                 }
687         } while (1);
688
689         if (theprompt)
690                 free(theprompt);
691         return result;
692 }
693
694 /* get boolean (starting with YyNn; loop forever */
695 int Parser_getbool(const char *prompt, int deft)
696 {
697         int result = 0;
698         char *line;
699         int size = strlen(prompt) + 8;
700         char *theprompt = malloc(size);
701
702         assert(theprompt);
703         fflush(stdout);
704
705         if (deft != 0 && deft != 1) {
706                 fprintf(stderr, "Error: Parser_getbool given bad default %d\n",
707                         deft);
708                 assert(0);
709         }
710         snprintf(theprompt, size, "%s [%s]: ", prompt, (deft == 0) ? "N" : "Y");
711
712         do {
713                 line = NULL;
714                 line = readline(theprompt);
715                 if (!line) {
716                         result = deft;
717                         break;
718                 }
719                 if (*line == '\0') {
720                         result = deft;
721                         break;
722                 }
723                 if (*line == 'y' || *line == 'Y') {
724                         result = 1;
725                         break;
726                 }
727                 if (*line == 'n' || *line == 'N') {
728                         result = 0;
729                         break;
730                 }
731                 if (line)
732                         free(line);
733                 fprintf(stdout, "Invalid string. Must start with yY or nN\n");
734                 fflush(stdout);
735         } while (1);
736
737         if (line)
738                 free(line);
739         if (theprompt)
740                 free(theprompt);
741
742         return result;
743 }
744
745 /* parse int out of a string or prompt for it */
746 long Parser_intarg(const char *inp, const char *prompt, int deft,
747                    int min, int max, int base)
748 {
749         long result;
750         int rc;
751
752         rc = Parser_arg2int(inp, &result, base);
753         if (rc == 0)
754                 return result;
755         else
756                 return Parser_getint(prompt, deft, min, max, base);
757 }
758
759 /* parse int out of a string or prompt for it */
760 char *Parser_strarg(char *inp, const char *prompt, const char *deft,
761                     char *answer, int len)
762 {
763         if (!inp || *inp == '\0')
764                 return Parser_getstr(prompt, deft, answer, len);
765         else
766                 return inp;
767 }
768
769 /*
770  * change a string into a number: return 0 on success. No invalid characters
771  * allowed. The processing of base and validity follows strtol(3)
772  */
773 int Parser_arg2int(const char *inp, long *result, int base)
774 {
775         char *endptr;
776
777         if ((base != 0) && (base < 2 || base > 36))
778                 return 1;
779
780         *result = strtol(inp, &endptr, base);
781
782         if (*inp != '\0' && *endptr == '\0')
783                 return 0;
784         else
785                 return 1;
786 }
787
788 /* Convert human readable size string to and int; "1k" -> 1000 */
789 int Parser_size(unsigned long *sizep, char *str)
790 {
791         unsigned long size;
792         char mod[32];
793
794         switch (sscanf(str, "%lu%1[gGmMkK]", &size, mod)) {
795         default:
796                 return -1;
797         case 1:
798                 *sizep = size;
799                 return 0;
800         case 2:
801                 switch (*mod) {
802                 case 'g':
803                 case 'G':
804                         *sizep = size << 30;
805                         return 0;
806                 case 'm':
807                 case 'M':
808                         *sizep = size << 20;
809                         return 0;
810                 case 'k':
811                 case 'K':
812                         *sizep = size << 10;
813                         return 0;
814                 default:
815                         *sizep = size;
816                         return 0;
817                 }
818         }
819 }
820
821 /* Convert a string boolean to an int; "enable" -> 1 */
822 int Parser_bool(int *b, char *str)
823 {
824         if (!strcasecmp(str, "no") || !strcasecmp(str, "n") ||
825             !strcasecmp(str, "off") || !strcasecmp(str, "down") ||
826             !strcasecmp(str, "disable")) {
827                 *b = 0;
828                 return 0;
829         }
830
831         if (!strcasecmp(str, "yes") || !strcasecmp(str, "y") ||
832             !strcasecmp(str, "on") || !strcasecmp(str, "up") ||
833             !strcasecmp(str, "enable")) {
834                 *b = 1;
835                 return 0;
836         }
837
838         return -1;
839 }
840
841 int Parser_quit(int argc, char **argv)
842 {
843         argc = argc;
844         argv = argv;
845         done = 1;
846         return 0;
847 }
848
849 int Parser_version(int argc, char **argv)
850 {
851         fprintf(stdout, "%s %s\n", program_invocation_short_name,
852                 LUSTRE_VERSION_STRING);
853         return 0;
854 }