cmd: Tidy up -help output
[openafs.git] / src / cmd / cmd.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12
13 #include <roken.h>
14
15 #include <ctype.h>
16 #include <assert.h>
17
18 #include "cmd.h"
19
20 /* declaration of private token type */
21 struct cmd_token {
22     struct cmd_token *next;
23     char *key;
24 };
25
26 static struct cmd_item dummy;           /* non-null ptr used for flag existence */
27 static struct cmd_syndesc *allSyntax = 0;
28 static int noOpcodes = 0;
29 static int (*beforeProc) (struct cmd_syndesc * ts, void *beforeRock) = NULL;
30 static int (*afterProc) (struct cmd_syndesc * ts, void *afterRock) = NULL;
31 static int enablePositional = 1;
32 static int enableAbbreviation = 1;
33 static void *beforeRock, *afterRock;
34 static char initcmd_opcode[] = "initcmd";       /*Name of initcmd opcode */
35
36 /* take name and string, and return null string if name is empty, otherwise return
37    the concatenation of the two strings */
38 static char *
39 NName(char *a1, char *a2)
40 {
41     static char tbuffer[300];
42     if (strlen(a1) == 0) {
43         return "";
44     } else {
45         strncpy(tbuffer, a1, sizeof(tbuffer));
46         strncat(tbuffer, a2, sizeof(tbuffer));
47         tbuffer[sizeof(tbuffer)-1]='\0';
48         return tbuffer;
49     }
50 }
51
52 /* return true if asub is a substring of amain */
53 static int
54 SubString(char *amain, char *asub)
55 {
56     int mlen, slen;
57     int i, j;
58     mlen = (int) strlen(amain);
59     slen = (int) strlen(asub);
60     j = mlen - slen;
61     if (j < 0)
62         return 0;               /* not a substring */
63     for (i = 0; i <= j; i++) {
64         if (strncmp(amain, asub, slen) == 0)
65             return 1;
66         amain++;
67     }
68     return 0;                   /* didn't find it */
69 }
70
71 static int
72 FindType(struct cmd_syndesc *as, char *aname)
73 {
74     int i;
75     size_t cmdlen;
76     int ambig;
77     int best;
78     struct cmd_item *alias;
79
80     /* Allow --long-style options. */
81     if (aname[0] == '-' && aname[1] == '-' && aname[2] && aname[3]) {
82         aname++;
83     }
84
85     cmdlen = strlen(aname);
86     ambig = 0;
87     best = -1;
88     for (i = 0; i < CMD_MAXPARMS; i++) {
89         if (as->parms[i].type == 0)
90             continue;           /* this slot not set (seeked over) */
91         if (strcmp(as->parms[i].name, aname) == 0)
92             return i;
93         if (strlen(as->parms[i].name) < cmdlen)
94             continue;
95
96         /* Check for aliases, which must be full matches */
97         alias = as->parms[i].aliases;
98         while (alias != NULL) {
99             if (strcmp(alias->data, aname) == 0)
100                 return i;
101             alias = alias->next;
102         }
103
104         /* A hidden option must be a full match (no best matches) */
105         if (as->parms[i].flags & CMD_HIDE || !enableAbbreviation)
106             continue;
107
108         if (strncmp(as->parms[i].name, aname, cmdlen) == 0) {
109             if (best != -1)
110                 ambig = 1;
111             else
112                 best = i;
113         }
114     }
115     return (ambig ? -1 : best);
116 }
117
118 static struct cmd_syndesc *
119 FindSyntax(char *aname, int *aambig)
120 {
121     struct cmd_syndesc *ts;
122     struct cmd_syndesc *best;
123     size_t cmdLen;
124     int ambig;
125
126     cmdLen = strlen(aname);
127     best = (struct cmd_syndesc *)0;
128     ambig = 0;
129     if (aambig)
130         *aambig = 0;            /* initialize to unambiguous */
131     for (ts = allSyntax; ts; ts = ts->next) {
132         if (strcmp(aname, ts->name) == 0)
133             return (ts);
134         if (strlen(ts->name) < cmdLen)
135             continue;           /* we typed more than item has */
136         /* A hidden command must be a full match (no best matches) */
137         if (ts->flags & CMD_HIDDEN)
138             continue;
139
140         /* This is just an alias for *best, or *best is just an alias for us.
141          * If we don't make this check explicitly, then an alias which is just a
142          * short prefix of the real command's name might make things ambiguous
143          * for no apparent reason.
144          */
145         if (best && ts->aliasOf == best->aliasOf)
146             continue;
147         if (strncmp(ts->name, aname, cmdLen) == 0) {
148             if (best)
149                 ambig = 1;      /* ambiguous name */
150             else
151                 best = ts;
152         }
153     }
154     if (ambig) {
155         if (aambig)
156             *aambig = ambig;    /* if ambiguous and they care, tell them */
157         return (struct cmd_syndesc *)0; /* fails */
158     } else
159         return best;            /* otherwise its not ambiguous, and they know */
160 }
161
162 /* print the help for a single parameter */
163 static char *
164 ParmHelpString(struct cmd_parmdesc *aparm)
165 {
166     char *str;
167     if (aparm->type == CMD_FLAG) {
168         return strdup("");
169     } else {
170         asprintf(&str, " %s<%s>%s%s",
171                  aparm->type == CMD_SINGLE_OR_FLAG?"[":"",
172                  aparm->help?aparm->help:"arg",
173                  aparm->type == CMD_LIST?"+":"",
174                  aparm->type == CMD_SINGLE_OR_FLAG?"]":"");
175         return str;
176     }
177 }
178
179 extern char *AFSVersion;
180
181 static int
182 VersionProc(struct cmd_syndesc *as, void *arock)
183 {
184     printf("%s\n", AFSVersion);
185     return 0;
186 }
187
188 void
189 PrintSyntax(struct cmd_syndesc *as)
190 {
191     int i;
192     struct cmd_parmdesc *tp;
193     char *str;
194     size_t len;
195     size_t xtralen;
196
197     /* now print usage, from syntax table */
198     if (noOpcodes)
199         asprintf(&str, "Usage: %s", as->a0name);
200     else {
201         if (!strcmp(as->name, initcmd_opcode))
202             asprintf(&str, "Usage: %s[%s]", NName(as->a0name, " "), as->name);
203         else
204             asprintf(&str, "Usage: %s%s", NName(as->a0name, " "), as->name);
205     }
206
207     len = strlen(str);
208     printf("%s", str);
209     free(str);
210
211     for (i = 0; i < CMD_MAXPARMS; i++) {
212         tp = &as->parms[i];
213         if (tp->type == 0)
214             continue;           /* seeked over slot */
215         if (tp->flags & CMD_HIDE)
216             continue;           /* skip hidden options */
217         /* Work out if we can fit what we want to on this line, or if we need to
218          * start a new one */
219         str = ParmHelpString(tp);
220         xtralen = 1 + strlen(tp->name) + strlen(str) +
221                   ((tp->flags & CMD_OPTIONAL)? 2: 0);
222
223         if (len + xtralen > 78) {
224             printf("\n        ");
225             len = 8;
226         }
227
228         printf(" %s%s%s%s",
229                tp->flags & CMD_OPTIONAL?"[":"",
230                tp->name,
231                str,
232                tp->flags & CMD_OPTIONAL?"]":"");
233         free(str);
234         len+=xtralen;
235     }
236     printf("\n");
237 }
238
239 /* must print newline in any case, to terminate preceding line */
240 static void
241 PrintAliases(struct cmd_syndesc *as)
242 {
243     struct cmd_syndesc *ts;
244
245     if (as->flags & CMD_ALIAS) {
246         ts = as->aliasOf;
247         printf("(alias for %s)\n", ts->name);
248     } else {
249         printf("\n");
250         if (!as->nextAlias)
251             return;             /* none, print nothing */
252         printf("aliases: ");
253         for (as = as->nextAlias; as; as = as->nextAlias) {
254             printf("%s ", as->name);
255         }
256         printf("\n");
257     }
258 }
259
260 void
261 PrintFlagHelp(struct cmd_syndesc *as)
262 {
263     int i;
264     struct cmd_parmdesc *tp;
265     int flag_width;
266     char *flag_prefix;
267
268     /* find flag name length */
269     flag_width = 0;
270     for (i = 0; i < CMD_MAXPARMS; i++) {
271         if (i == CMD_HELPPARM)
272             continue;
273         tp = &as->parms[i];
274         if (tp->type != CMD_FLAG)
275             continue;
276         if (tp->flags & CMD_HIDE)
277             continue;           /* skip hidden options */
278         if (!tp->help)
279             continue;
280
281         if (strlen(tp->name) > flag_width)
282             flag_width = strlen(tp->name);
283     }
284
285     /* print flag help */
286     flag_prefix = "Where:";
287     for (i = 0; i < CMD_MAXPARMS; i++) {
288         if (i == CMD_HELPPARM)
289             continue;
290         tp = &as->parms[i];
291         if (tp->type != CMD_FLAG)
292             continue;
293         if (tp->flags & CMD_HIDE)
294             continue;           /* skip hidden options */
295         if (!tp->help)
296             continue;
297
298         printf("%-7s%-*s  %s\n", flag_prefix, flag_width, tp->name, tp->help);
299         flag_prefix = "";
300     }
301 }
302
303 static int
304 AproposProc(struct cmd_syndesc *as, void *arock)
305 {
306     struct cmd_syndesc *ts;
307     char *tsub;
308     int didAny;
309
310     didAny = 0;
311     tsub = as->parms[0].items->data;
312     for (ts = allSyntax; ts; ts = ts->next) {
313         if ((ts->flags & CMD_ALIAS) || (ts->flags & CMD_HIDDEN))
314             continue;
315         if (SubString(ts->help, tsub)) {
316             printf("%s: %s\n", ts->name, ts->help);
317             didAny = 1;
318         } else if (SubString(ts->name, tsub)) {
319             printf("%s: %s\n", ts->name, ts->help);
320             didAny = 1;
321         }
322     }
323     if (!didAny)
324         printf("Sorry, no commands found\n");
325     return 0;
326 }
327
328 static int
329 HelpProc(struct cmd_syndesc *as, void *arock)
330 {
331     struct cmd_syndesc *ts;
332     struct cmd_item *ti;
333     int ambig;
334     int code = 0;
335
336     if (as->parms[0].items == 0) {
337         printf("%sCommands are:\n", NName(as->a0name, ": "));
338         for (ts = allSyntax; ts; ts = ts->next) {
339             if ((ts->flags & CMD_ALIAS) || (ts->flags & CMD_HIDDEN))
340                 continue;
341             printf("%-15s %s\n", ts->name, (ts->help ? ts->help : ""));
342         }
343     } else {
344         /* print out individual help topics */
345         for (ti = as->parms[0].items; ti; ti = ti->next) {
346             code = 0;
347             ts = FindSyntax(ti->data, &ambig);
348             if (ts && (ts->flags & CMD_HIDDEN))
349                 ts = 0;         /* no hidden commands */
350             if (ts) {
351                 /* print out command name and help */
352                 printf("%s%s: %s ", NName(as->a0name, " "), ts->name,
353                        (ts->help ? ts->help : ""));
354                 ts->a0name = as->a0name;
355                 PrintAliases(ts);
356                 PrintSyntax(ts);
357                 PrintFlagHelp(ts);
358             } else {
359                 if (!ambig)
360                     fprintf(stderr, "%sUnknown topic '%s'\n",
361                             NName(as->a0name, ": "), ti->data);
362                 else {
363                     /* ambiguous, list 'em all */
364                     fprintf(stderr,
365                             "%sAmbiguous topic '%s'; use 'apropos' to list\n",
366                             NName(as->a0name, ": "), ti->data);
367                 }
368                 code = CMD_UNKNOWNCMD;
369             }
370         }
371     }
372     return (code);
373 }
374
375 int
376 cmd_SetBeforeProc(int (*aproc) (struct cmd_syndesc * ts, void *beforeRock),
377                   void *arock)
378 {
379     beforeProc = aproc;
380     beforeRock = arock;
381     return 0;
382 }
383
384 int
385 cmd_SetAfterProc(int (*aproc) (struct cmd_syndesc * ts, void *afterRock),
386                  void *arock)
387 {
388     afterProc = aproc;
389     afterRock = arock;
390     return 0;
391 }
392
393 /* thread on list in alphabetical order */
394 static int
395 SortSyntax(struct cmd_syndesc *as)
396 {
397     struct cmd_syndesc **ld, *ud;
398
399     for (ld = &allSyntax, ud = *ld; ud; ld = &ud->next, ud = *ld) {
400         if (strcmp(ud->name, as->name) > 0) {   /* next guy is bigger than us */
401             break;
402         }
403     }
404     /* thread us on the list now */
405     *ld = as;
406     as->next = ud;
407     return 0;
408 }
409
410 struct cmd_syndesc *
411 cmd_CreateSyntax(char *aname,
412                  int (*aproc) (struct cmd_syndesc * ts, void *arock),
413                  void *arock, char *ahelp)
414 {
415     struct cmd_syndesc *td;
416
417     /* can't have two cmds in no opcode mode */
418     if (noOpcodes)
419         return NULL;
420
421     td = calloc(1, sizeof(struct cmd_syndesc));
422     assert(td);
423     td->aliasOf = td;           /* treat aliasOf as pointer to real command, no matter what */
424
425     /* copy in name, etc */
426     if (aname) {
427         td->name = malloc(strlen(aname) + 1);
428         assert(td->name);
429         strcpy(td->name, aname);
430     } else {
431         td->name = NULL;
432         noOpcodes = 1;
433     }
434     if (ahelp) {
435         /* Piggy-back the hidden option onto the help string */
436         if (ahelp == (char *)CMD_HIDDEN) {
437             td->flags |= CMD_HIDDEN;
438         } else {
439             td->help = malloc(strlen(ahelp) + 1);
440             assert(td->help);
441             strcpy(td->help, ahelp);
442         }
443     } else
444         td->help = NULL;
445     td->proc = aproc;
446     td->rock = arock;
447
448     SortSyntax(td);
449
450     cmd_Seek(td, CMD_HELPPARM);
451     cmd_AddParm(td, "-help", CMD_FLAG, CMD_OPTIONAL, "get detailed help");
452     cmd_Seek(td, 0);
453
454     return td;
455 }
456
457 int
458 cmd_CreateAlias(struct cmd_syndesc *as, char *aname)
459 {
460     struct cmd_syndesc *td;
461
462     td = malloc(sizeof(struct cmd_syndesc));
463     assert(td);
464     memcpy(td, as, sizeof(struct cmd_syndesc));
465     td->name = malloc(strlen(aname) + 1);
466     assert(td->name);
467     strcpy(td->name, aname);
468     td->flags |= CMD_ALIAS;
469     /* if ever free things, make copy of help string, too */
470
471     /* thread on list */
472     SortSyntax(td);
473
474     /* thread on alias lists */
475     td->nextAlias = as->nextAlias;
476     as->nextAlias = td;
477     td->aliasOf = as;
478
479     return 0;                   /* all done */
480 }
481
482 void
483 cmd_DisablePositionalCommands(void)
484 {
485     enablePositional = 0;
486 }
487
488 void
489 cmd_DisableAbbreviations(void)
490 {
491     enableAbbreviation = 0;
492 }
493
494 int
495 cmd_IsAdministratorCommand(struct cmd_syndesc *as)
496 {
497     as->flags |= CMD_ADMIN;
498     return 0;
499 }
500
501 int
502 cmd_Seek(struct cmd_syndesc *as, int apos)
503 {
504     if (apos >= CMD_MAXPARMS)
505         return CMD_EXCESSPARMS;
506     as->nParms = apos;
507     return 0;
508 }
509
510 int
511 cmd_AddParmAtOffset(struct cmd_syndesc *as, char *aname, int atype,
512                     afs_int32 aflags, char *ahelp, int ref)
513 {
514     struct cmd_parmdesc *tp;
515
516     if (ref >= CMD_MAXPARMS)
517         return CMD_EXCESSPARMS;
518     tp = &as->parms[ref];
519
520     tp->name = malloc(strlen(aname) + 1);
521     assert(tp->name);
522     strcpy(tp->name, aname);
523     tp->type = atype;
524     tp->flags = aflags;
525     tp->items = NULL;
526     if (ahelp) {
527         tp->help = malloc(strlen(ahelp) + 1);
528         assert(tp->help);
529         strcpy(tp->help, ahelp);
530     } else
531         tp->help = NULL;
532
533     tp->aliases = NULL;
534
535     if (as->nParms <= ref)
536         as->nParms = ref+1;
537
538     return 0;
539 }
540
541 int
542 cmd_AddParm(struct cmd_syndesc *as, char *aname, int atype,
543             afs_int32 aflags, char *ahelp)
544 {
545     if (as->nParms >= CMD_MAXPARMS)
546         return CMD_EXCESSPARMS;
547
548     return cmd_AddParmAtOffset(as, aname, atype, aflags, ahelp, as->nParms++);
549 }
550
551 int
552 cmd_AddParmAlias(struct cmd_syndesc *as, int pos, char *alias)
553 {
554     struct cmd_item *item;
555
556     if (pos > as->nParms)
557         return CMD_EXCESSPARMS;
558
559     item = calloc(1, sizeof(struct cmd_item));
560     item->data = strdup(alias);
561     item->next = as->parms[pos].aliases;
562     as->parms[pos].aliases = item;
563
564     return 0;
565 }
566
567 /* add a text item to the end of the parameter list */
568 static int
569 AddItem(struct cmd_parmdesc *aparm, char *aval)
570 {
571     struct cmd_item *ti, *ni;
572     ti = calloc(1, sizeof(struct cmd_item));
573     assert(ti);
574     ti->data = malloc(strlen(aval) + 1);
575     assert(ti->data);
576     strcpy(ti->data, aval);
577     /* now put ti at the *end* of the list */
578     if ((ni = aparm->items)) {
579         for (; ni; ni = ni->next)
580             if (ni->next == 0)
581                 break;          /* skip to last one */
582         ni->next = ti;
583     } else
584         aparm->items = ti;      /* we're first */
585     return 0;
586 }
587
588 /* skip to next non-flag item, if any */
589 static int
590 AdvanceType(struct cmd_syndesc *as, afs_int32 aval)
591 {
592     afs_int32 next;
593     struct cmd_parmdesc *tp;
594
595     /* first see if we should try to grab rest of line for this dude */
596     if (as->parms[aval].flags & CMD_EXPANDS)
597         return aval;
598
599     /* if not, find next non-flag used slot */
600     for (next = aval + 1; next < CMD_MAXPARMS; next++) {
601         tp = &as->parms[next];
602         if (tp->type != 0 && tp->type != CMD_FLAG)
603             return next;
604     }
605     return aval;
606 }
607
608 /* discard parameters filled in by dispatch */
609 static void
610 ResetSyntax(struct cmd_syndesc *as)
611 {
612     int i;
613     struct cmd_parmdesc *tp;
614     struct cmd_item *ti, *ni;
615
616     tp = as->parms;
617     for (i = 0; i < CMD_MAXPARMS; i++, tp++) {
618         switch (tp->type) {
619         case CMD_SINGLE_OR_FLAG:
620             if (tp->items == &dummy)
621                 break;
622             /* Deliberately fall through here */
623         case CMD_SINGLE:
624         case CMD_LIST:
625             /* free whole list in both cases, just for fun */
626             for (ti = tp->items; ti; ti = ni) {
627                 ni = ti->next;
628                 free(ti->data);
629                 free(ti);
630             }
631             break;
632
633         default:
634             break;
635         }
636         tp->items = NULL;
637     }
638 }
639
640 /* move the expands flag to the last one in the list */
641 static int
642 SetupExpandsFlag(struct cmd_syndesc *as)
643 {
644     struct cmd_parmdesc *tp;
645     int last, i;
646
647     last = -1;
648     /* find last CMD_LIST type parameter, optional or not, and make it expandable
649      * if no other dude is expandable */
650     for (i = 0; i < CMD_MAXPARMS; i++) {
651         tp = &as->parms[i];
652         if (tp->type == CMD_LIST) {
653             if (tp->flags & CMD_EXPANDS)
654                 return 0;       /* done if already specified */
655             last = i;
656         }
657     }
658     if (last >= 0)
659         as->parms[last].flags |= CMD_EXPANDS;
660     return 0;
661 }
662
663 /* Take the current argv & argc and alter them so that the initialization
664  * opcode is made to appear.  This is used in cases where the initialization
665  * opcode is implicitly invoked.*/
666 static char **
667 InsertInitOpcode(int *aargc, char **aargv)
668 {
669     char **newargv;             /*Ptr to new, expanded argv space */
670     char *pinitopcode;          /*Ptr to space for name of init opcode */
671     int i;                      /*Loop counter */
672
673     /* Allocate the new argv array, plus one for the new opcode, plus one
674      * more for the trailing null pointer */
675     newargv = malloc(((*aargc) + 2) * sizeof(char *));
676     if (!newargv) {
677         fprintf(stderr, "%s: Can't create new argv array with %d+2 slots\n",
678                 aargv[0], *aargc);
679         return (NULL);
680     }
681
682     /* Create space for the initial opcode & fill it in */
683     pinitopcode = malloc(sizeof(initcmd_opcode));
684     if (!pinitopcode) {
685         fprintf(stderr, "%s: Can't malloc initial opcode space\n", aargv[0]);
686         free(newargv);
687         return (NULL);
688     }
689     strcpy(pinitopcode, initcmd_opcode);
690
691     /* Move all the items in the old argv into the new argv, in their
692      * proper places */
693     for (i = *aargc; i > 1; i--)
694         newargv[i] = aargv[i - 1];
695
696     /* Slip in the opcode and the trailing null pointer, and bump the
697      * argument count up by one for the new opcode */
698     newargv[0] = aargv[0];
699     newargv[1] = pinitopcode;
700     (*aargc)++;
701     newargv[*aargc] = NULL;
702
703     /* Return the happy news */
704     return (newargv);
705
706 }                               /*InsertInitOpcode */
707
708 static int
709 NoParmsOK(struct cmd_syndesc *as)
710 {
711     int i;
712     struct cmd_parmdesc *td;
713
714     for (i = 0; i < CMD_MAXPARMS; i++) {
715         td = &as->parms[i];
716         if (td->type != 0 && !(td->flags & CMD_OPTIONAL)) {
717             /* found a non-optional (e.g. required) parm, so NoParmsOK
718              * is false (some parms are required) */
719             return 0;
720         }
721     }
722     return 1;
723 }
724
725 /* Add help, apropos commands once */
726 static void
727 initSyntax(void)
728 {
729     struct cmd_syndesc *ts;
730
731     if (!noOpcodes) {
732         ts = cmd_CreateSyntax("help", HelpProc, NULL,
733                               "get help on commands");
734         cmd_AddParm(ts, "-topic", CMD_LIST, CMD_OPTIONAL, "help string");
735         cmd_AddParm(ts, "-admin", CMD_FLAG, CMD_OPTIONAL, NULL);
736
737         ts = cmd_CreateSyntax("apropos", AproposProc, NULL,
738                               "search by help text");
739         cmd_AddParm(ts, "-topic", CMD_SINGLE, CMD_REQUIRED, "help string");
740         ts = cmd_CreateSyntax("version", VersionProc, NULL,
741                               (char *)CMD_HIDDEN);
742         ts = cmd_CreateSyntax("-version", VersionProc, NULL,
743                               (char *)CMD_HIDDEN);
744         ts = cmd_CreateSyntax("-help", HelpProc, NULL,
745                               (char *)CMD_HIDDEN);
746         ts = cmd_CreateSyntax("--version", VersionProc, NULL,
747                               (char *)CMD_HIDDEN);
748         ts = cmd_CreateSyntax("--help", HelpProc, NULL,
749                               (char *)CMD_HIDDEN);
750     }
751 }
752
753 /* Call the appropriate function, or return syntax error code.  Note: if
754  * no opcode is specified, an initialization routine exists, and it has
755  * NOT been called before, we invoke the special initialization opcode
756  */
757 int
758 cmd_Parse(int argc, char **argv, struct cmd_syndesc **outsyntax)
759 {
760     char *pname;
761     struct cmd_syndesc *ts = NULL;
762     struct cmd_parmdesc *tparm;
763     afs_int32 i, j;
764     int curType;
765     int positional;
766     int ambig;
767     int code = 0;
768     static int initd = 0;       /*Is this the first time this routine has been called? */
769     static int initcmdpossible = 1;     /*Should be consider parsing the initial command? */
770
771     *outsyntax = NULL;
772
773     if (!initd) {
774         initd = 1;
775         initSyntax();
776     }
777
778     /*Remember the program name */
779     pname = argv[0];
780
781     if (noOpcodes) {
782         if (argc == 1) {
783             if (!NoParmsOK(allSyntax)) {
784                 printf("%s: Type '%s -help' for help\n", pname, pname);
785                 code = CMD_USAGE;
786                 goto out;
787             }
788         }
789     } else {
790         if (argc < 2) {
791             /* if there is an initcmd, don't print an error message, just
792              * setup to use the initcmd below. */
793             if (!(initcmdpossible && FindSyntax(initcmd_opcode, NULL))) {
794                 printf("%s: Type '%s help' or '%s help <topic>' for help\n",
795                        pname, pname, pname);
796                 code = CMD_USAGE;
797                 goto out;
798             }
799         }
800     }
801
802     /* Find the syntax descriptor for this command, doing prefix matching properly */
803     if (noOpcodes) {
804         ts = allSyntax;
805     } else {
806         ts = (argc < 2 ? 0 : FindSyntax(argv[1], &ambig));
807         if (!ts) {
808             /*First token doesn't match a syntax descriptor */
809             if (initcmdpossible) {
810                 /*If initial command line handling hasn't been done yet,
811                  * see if there is a descriptor for the initialization opcode.
812                  * Only try this once. */
813                 initcmdpossible = 0;
814                 ts = FindSyntax(initcmd_opcode, NULL);
815                 if (!ts) {
816                     /*There is no initialization opcode available, so we declare
817                      * an error */
818                     if (ambig) {
819                         fprintf(stderr, "%s", NName(pname, ": "));
820                         fprintf(stderr,
821                                 "Ambiguous operation '%s'; type '%shelp' for list\n",
822                                 argv[1], NName(pname, " "));
823                     } else {
824                         fprintf(stderr, "%s", NName(pname, ": "));
825                         fprintf(stderr,
826                                 "Unrecognized operation '%s'; type '%shelp' for list\n",
827                                 argv[1], NName(pname, " "));
828                     }
829                     code = CMD_UNKNOWNCMD;
830                     goto out;
831                 } else {
832                     /*Found syntax structure for an initialization opcode.  Fix
833                      * up argv and argc to relect what the user
834                      * ``should have'' typed */
835                     if (!(argv = InsertInitOpcode(&argc, argv))) {
836                         fprintf(stderr,
837                                 "%sCan't insert implicit init opcode into command line\n",
838                                 NName(pname, ": "));
839                         code = CMD_INTERNALERROR;
840                         goto out;
841                     }
842                 }
843             } /*Initial opcode not yet attempted */
844             else {
845                 /* init cmd already run and no syntax entry found */
846                 if (ambig) {
847                     fprintf(stderr, "%s", NName(pname, ": "));
848                     fprintf(stderr,
849                             "Ambiguous operation '%s'; type '%shelp' for list\n",
850                             argv[1], NName(pname, " "));
851                 } else {
852                     fprintf(stderr, "%s", NName(pname, ": "));
853                     fprintf(stderr,
854                             "Unrecognized operation '%s'; type '%shelp' for list\n",
855                             argv[1], NName(pname, " "));
856                 }
857                 code = CMD_UNKNOWNCMD;
858                 goto out;
859             }
860         }                       /*Argv[1] is not a valid opcode */
861     }                           /*Opcodes are defined */
862
863     /* Found the descriptor; start parsing.  curType is the type we're
864      * trying to parse */
865     curType = 0;
866
867     /* We start off parsing in "positional" mode, where tokens are put in
868      * slots positionally.  If we find a name that takes args, we go
869      * out of positional mode, and from that point on, expect a switch
870      * before any particular token. */
871
872     positional = enablePositional;      /* Accepting positional cmds ? */
873     i = noOpcodes ? 1 : 2;
874     SetupExpandsFlag(ts);
875     for (; i < argc; i++) {
876         /* Only tokens that start with a hyphen and are not followed by a digit
877          * are considered switches.  This allow negative numbers. */
878         if ((argv[i][0] == '-') && !isdigit(argv[i][1])) {
879             /* Find switch */
880             j = FindType(ts, argv[i]);
881             if (j < 0) {
882                 fprintf(stderr,
883                         "%sUnrecognized or ambiguous switch '%s'; type ",
884                         NName(pname, ": "), argv[i]);
885                 if (noOpcodes)
886                     fprintf(stderr, "'%s -help' for detailed help\n",
887                             argv[0]);
888                 else
889                     fprintf(stderr, "'%shelp %s' for detailed help\n",
890                             NName(argv[0], " "), ts->name);
891                 code = CMD_UNKNOWNSWITCH;
892                 goto out;
893             }
894             if (j >= CMD_MAXPARMS) {
895                 fprintf(stderr, "%sInternal parsing error\n",
896                         NName(pname, ": "));
897                 code = CMD_INTERNALERROR;
898                 goto out;
899             }
900             if (ts->parms[j].type == CMD_FLAG) {
901                 ts->parms[j].items = &dummy;
902             } else {
903                 positional = 0;
904                 curType = j;
905                 ts->parms[j].flags |= CMD_PROCESSED;
906             }
907         } else {
908             /* Try to fit in this descr */
909             if (curType >= CMD_MAXPARMS) {
910                 fprintf(stderr, "%sToo many arguments\n", NName(pname, ": "));
911                 code = CMD_TOOMANY;
912                 goto out;
913             }
914             tparm = &ts->parms[curType];
915
916             if ((tparm->type == 0) ||   /* No option in this slot */
917                 (tparm->type == CMD_FLAG)) {    /* A flag (not an argument */
918                 /* skipped parm slot */
919                 curType++;      /* Skip this slot and reprocess this parm */
920                 i--;
921                 continue;
922             }
923
924             if (!(tparm->flags & CMD_PROCESSED) && (tparm->flags & CMD_HIDE)) {
925                 curType++;      /* Skip this slot and reprocess this parm */
926                 i--;
927                 continue;
928             }
929
930             if (tparm->type == CMD_SINGLE ||
931                 tparm->type == CMD_SINGLE_OR_FLAG) {
932                 if (tparm->items) {
933                     fprintf(stderr, "%sToo many values after switch %s\n",
934                             NName(pname, ": "), tparm->name);
935                     code = CMD_NOTLIST;
936                     goto out;
937                 }
938                 AddItem(tparm, argv[i]);        /* Add to end of list */
939             } else if (tparm->type == CMD_LIST) {
940                 AddItem(tparm, argv[i]);        /* Add to end of list */
941             }
942             /* Now, if we're in positional mode, advance to the next item */
943             if (positional)
944                 curType = AdvanceType(ts, curType);
945         }
946     }
947
948     /* keep track of this for messages */
949     ts->a0name = argv[0];
950
951     /* If we make it here, all the parameters are filled in.  Check to see if
952      * this is a -help version.  Must do this before checking for all
953      * required parms, otherwise it is a real nuisance */
954     if (ts->parms[CMD_HELPPARM].items) {
955         PrintSyntax(ts);
956         /* Display full help syntax if we don't have subcommands */
957         if (noOpcodes)
958             PrintFlagHelp(ts);
959         code = CMD_USAGE;
960         goto out;
961     }
962
963     /* Parsing done, see if we have all of our required parameters */
964     for (i = 0; i < CMD_MAXPARMS; i++) {
965         tparm = &ts->parms[i];
966         if (tparm->type == 0)
967             continue;           /* Skipped parm slot */
968         if ((tparm->flags & CMD_PROCESSED) && tparm->items == 0) {
969             if (tparm->type == CMD_SINGLE_OR_FLAG) {
970                 tparm->items = &dummy;
971             } else {
972                 fprintf(stderr, "%s The field '%s' isn't completed properly\n",
973                     NName(pname, ": "), tparm->name);
974                 code = CMD_TOOFEW;
975                 goto out;
976             }
977         }
978         if (!(tparm->flags & CMD_OPTIONAL) && tparm->items == 0) {
979             fprintf(stderr, "%sMissing required parameter '%s'\n",
980                     NName(pname, ": "), tparm->name);
981             code = CMD_TOOFEW;
982             goto out;
983         }
984         tparm->flags &= ~CMD_PROCESSED;
985     }
986     *outsyntax = ts;
987
988 out:
989     if (code && ts != NULL)
990         ResetSyntax(ts);
991
992     return code;
993 }
994
995 int
996 cmd_Dispatch(int argc, char **argv)
997 {
998     struct cmd_syndesc *ts = NULL;
999     int code;
1000
1001     code = cmd_Parse(argc, argv, &ts);
1002     if (code)
1003         return code;
1004
1005     /*
1006      * Before calling the beforeProc and afterProc and all the implications
1007      * from those calls, check if the help procedure was called and call it
1008      * now.
1009      */
1010     if ((ts->proc == HelpProc) || (ts->proc == AproposProc)) {
1011         code = (*ts->proc) (ts, ts->rock);
1012         goto out;
1013     }
1014
1015     /* Now, we just call the procedure and return */
1016     if (beforeProc)
1017         code = (*beforeProc) (ts, beforeRock);
1018
1019     if (code)
1020         goto out;
1021
1022     code = (*ts->proc) (ts, ts->rock);
1023
1024     if (afterProc)
1025         (*afterProc) (ts, afterRock);
1026 out:
1027     cmd_FreeOptions(&ts);
1028     return code;
1029 }
1030
1031 void
1032 cmd_FreeOptions(struct cmd_syndesc **ts)
1033 {
1034     if (*ts != NULL) {
1035         ResetSyntax(*ts);
1036         *ts = NULL;
1037     }
1038 }
1039
1040 /* free token list returned by parseLine */
1041 static int
1042 FreeTokens(struct cmd_token *alist)
1043 {
1044     struct cmd_token *nlist;
1045     for (; alist; alist = nlist) {
1046         nlist = alist->next;
1047         free(alist->key);
1048         free(alist);
1049     }
1050     return 0;
1051 }
1052
1053 /* free an argv list returned by parseline */
1054 int
1055 cmd_FreeArgv(char **argv)
1056 {
1057     char *tp;
1058     for (tp = *argv; tp; argv++, tp = *argv)
1059         free(tp);
1060     return 0;
1061 }
1062
1063 /* copy back the arg list to the argv array, freeing the cmd_tokens as you go;
1064  * the actual data is still malloc'd, and will be freed when the caller calls
1065  * cmd_FreeArgv later on
1066  */
1067 #define INITSTR ""
1068 static int
1069 CopyBackArgs(struct cmd_token *alist, char **argv,
1070              afs_int32 * an, afs_int32 amaxn)
1071 {
1072     struct cmd_token *next;
1073     afs_int32 count;
1074
1075     count = 0;
1076     if (amaxn <= 1)
1077         return CMD_TOOMANY;
1078     *argv = (char *)malloc(strlen(INITSTR) + 1);
1079     assert(*argv);
1080     strcpy(*argv, INITSTR);
1081     amaxn--;
1082     argv++;
1083     count++;
1084     while (alist) {
1085         if (amaxn <= 1)
1086             return CMD_TOOMANY; /* argv is too small for his many parms. */
1087         *argv = alist->key;
1088         next = alist->next;
1089         free(alist);
1090         alist = next;
1091         amaxn--;
1092         argv++;
1093         count++;
1094     }
1095     *(argv++) = 0;              /* use last slot for terminating null */
1096     /* don't count terminating null */
1097     *an = count;
1098     return 0;
1099 }
1100
1101 static int
1102 quote(int x)
1103 {
1104     if (x == '"' || x == 39 /* single quote */ )
1105         return 1;
1106     else
1107         return 0;
1108 }
1109
1110 static int
1111 space(int x)
1112 {
1113     if (x == 0 || x == ' ' || x == '\t' || x == '\n')
1114         return 1;
1115     else
1116         return 0;
1117 }
1118
1119 int
1120 cmd_ParseLine(char *aline, char **argv, afs_int32 * an, afs_int32 amaxn)
1121 {
1122     char tbuffer[256];
1123     char *tptr = 0;
1124     int inToken, inQuote;
1125     struct cmd_token *first, *last;
1126     struct cmd_token *ttok;
1127     int tc;
1128
1129     inToken = 0;                /* not copying token chars at start */
1130     first = NULL;
1131     last = NULL;
1132     inQuote = 0;                /* not in a quoted string */
1133     while (1) {
1134         tc = *aline++;
1135         if (tc == 0 || (!inQuote && space(tc))) {       /* terminating null gets us in here, too */
1136             if (inToken) {
1137                 inToken = 0;    /* end of this token */
1138                 if (!tptr)
1139                     return -1;  /* should never get here */
1140                 else
1141                     *tptr++ = 0;
1142                 ttok = malloc(sizeof(struct cmd_token));
1143                 assert(ttok);
1144                 ttok->next = NULL;
1145                 ttok->key = malloc(strlen(tbuffer) + 1);
1146                 assert(ttok->key);
1147                 strcpy(ttok->key, tbuffer);
1148                 if (last) {
1149                     last->next = ttok;
1150                     last = ttok;
1151                 } else
1152                     last = ttok;
1153                 if (!first)
1154                     first = ttok;
1155             }
1156         } else {
1157             /* an alpha character */
1158             if (!inToken) {
1159                 tptr = tbuffer;
1160                 inToken = 1;
1161             }
1162             if (tptr - tbuffer >= sizeof(tbuffer)) {
1163                 FreeTokens(first);
1164                 return CMD_TOOBIG;      /* token too long */
1165             }
1166             if (quote(tc)) {
1167                 /* hit a quote, toggle inQuote flag but don't insert character */
1168                 inQuote = !inQuote;
1169             } else {
1170                 /* insert character */
1171                 *tptr++ = tc;
1172             }
1173         }
1174         if (tc == 0) {
1175             /* last token flushed 'cause space(0) --> true */
1176             if (last)
1177                 last->next = NULL;
1178             return CopyBackArgs(first, argv, an, amaxn);
1179         }
1180     }
1181 }
1182
1183 int
1184 cmd_OptionAsInt(struct cmd_syndesc *syn, int pos, int *value)
1185 {
1186     if (pos > syn->nParms)
1187         return CMD_EXCESSPARMS;
1188     if (syn->parms[pos].items == NULL ||
1189         syn->parms[pos].items->data == NULL)
1190         return CMD_MISSING;
1191     if (syn->parms[pos].items == &dummy)
1192         return 0;
1193
1194     *value = strtol(syn->parms[pos].items->data, NULL, 10);
1195
1196     return 0;
1197 }
1198
1199 int
1200 cmd_OptionAsString(struct cmd_syndesc *syn, int pos, char **value)
1201 {
1202     if (pos > syn->nParms)
1203         return CMD_EXCESSPARMS;
1204     if (syn->parms[pos].items == NULL || syn->parms[pos].items->data == NULL)
1205         return CMD_MISSING;
1206     if (syn->parms[pos].items == &dummy)
1207         return 0;
1208
1209     if (*value)
1210         free(*value);
1211
1212     *value = strdup(syn->parms[pos].items->data);
1213
1214     return 0;
1215 }
1216
1217 int
1218 cmd_OptionAsList(struct cmd_syndesc *syn, int pos, struct cmd_item **value)
1219 {
1220     if (pos > syn->nParms)
1221         return CMD_EXCESSPARMS;
1222     if (syn->parms[pos].items == NULL)
1223         return CMD_MISSING;
1224
1225     *value = syn->parms[pos].items;
1226     return 0;
1227 }
1228
1229 int
1230 cmd_OptionAsFlag(struct cmd_syndesc *syn, int pos, int *value)
1231 {
1232     if (pos > syn->nParms)
1233         return CMD_EXCESSPARMS;
1234     if (syn->parms[pos].items == NULL)
1235         return CMD_MISSING;
1236
1237     *value = 1;
1238     return 0;
1239 }
1240
1241 int
1242 cmd_OptionPresent(struct cmd_syndesc *syn, int pos)
1243 {
1244     if (pos > syn->nParms || syn->parms[pos].items == NULL)
1245         return 0;
1246
1247     return 1;
1248 }