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