cmd: Fix parsing positional args
[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 curType;
796     int positional;
797     int ambig;
798     int code = 0;
799     char *param = NULL;
800     char *embeddedvalue = NULL;
801     static int initd = 0;       /*Is this the first time this routine has been called? */
802     static int initcmdpossible = 1;     /*Should be consider parsing the initial command? */
803
804     *outsyntax = NULL;
805
806     if (!initd) {
807         initd = 1;
808         initSyntax();
809     }
810
811     /*Remember the program name */
812     pname = argv[0];
813
814     if (noOpcodes) {
815         if (argc == 1) {
816             if (!NoParmsOK(allSyntax)) {
817                 printf("%s: Type '%s -help' for help\n", pname, pname);
818                 code = CMD_USAGE;
819                 goto out;
820             }
821         }
822     } else {
823         if (argc < 2) {
824             /* if there is an initcmd, don't print an error message, just
825              * setup to use the initcmd below. */
826             if (!(initcmdpossible && FindSyntax(initcmd_opcode, NULL))) {
827                 printf("%s: Type '%s help' or '%s help <topic>' for help\n",
828                        pname, pname, pname);
829                 code = CMD_USAGE;
830                 goto out;
831             }
832         }
833     }
834
835     /* Find the syntax descriptor for this command, doing prefix matching properly */
836     if (noOpcodes) {
837         ts = allSyntax;
838     } else {
839         ts = (argc < 2 ? 0 : FindSyntax(argv[1], &ambig));
840         if (!ts) {
841             /*First token doesn't match a syntax descriptor */
842             if (initcmdpossible) {
843                 /*If initial command line handling hasn't been done yet,
844                  * see if there is a descriptor for the initialization opcode.
845                  * Only try this once. */
846                 initcmdpossible = 0;
847                 ts = FindSyntax(initcmd_opcode, NULL);
848                 if (!ts) {
849                     /*There is no initialization opcode available, so we declare
850                      * an error */
851                     if (ambig) {
852                         fprintf(stderr, "%s", NName(pname, ": "));
853                         fprintf(stderr,
854                                 "Ambiguous operation '%s'; type '%shelp' for list\n",
855                                 argv[1], NName(pname, " "));
856                     } else {
857                         fprintf(stderr, "%s", NName(pname, ": "));
858                         fprintf(stderr,
859                                 "Unrecognized operation '%s'; type '%shelp' for list\n",
860                                 argv[1], NName(pname, " "));
861                     }
862                     code = CMD_UNKNOWNCMD;
863                     goto out;
864                 } else {
865                     /*Found syntax structure for an initialization opcode.  Fix
866                      * up argv and argc to relect what the user
867                      * ``should have'' typed */
868                     if (!(argv = InsertInitOpcode(&argc, argv))) {
869                         fprintf(stderr,
870                                 "%sCan't insert implicit init opcode into command line\n",
871                                 NName(pname, ": "));
872                         code = CMD_INTERNALERROR;
873                         goto out;
874                     }
875                 }
876             } /*Initial opcode not yet attempted */
877             else {
878                 /* init cmd already run and no syntax entry found */
879                 if (ambig) {
880                     fprintf(stderr, "%s", NName(pname, ": "));
881                     fprintf(stderr,
882                             "Ambiguous operation '%s'; type '%shelp' for list\n",
883                             argv[1], NName(pname, " "));
884                 } else {
885                     fprintf(stderr, "%s", NName(pname, ": "));
886                     fprintf(stderr,
887                             "Unrecognized operation '%s'; type '%shelp' for list\n",
888                             argv[1], NName(pname, " "));
889                 }
890                 code = CMD_UNKNOWNCMD;
891                 goto out;
892             }
893         }                       /*Argv[1] is not a valid opcode */
894     }                           /*Opcodes are defined */
895
896     /* Found the descriptor; start parsing.  curType is the type we're
897      * trying to parse */
898     curType = 0;
899
900     /* We start off parsing in "positional" mode, where tokens are put in
901      * slots positionally.  If we find a name that takes args, we go
902      * out of positional mode, and from that point on, expect a switch
903      * before any particular token. */
904
905     positional = enablePositional;      /* Accepting positional cmds ? */
906     i = noOpcodes ? 1 : 2;
907     SetupExpandsFlag(ts);
908     for (; i < argc; i++) {
909         if (param) {
910             free(param);
911             param = NULL;
912             embeddedvalue = NULL;
913         }
914
915         /* Only tokens that start with a hyphen and are not followed by a digit
916          * are considered switches.  This allow negative numbers. */
917
918         if ((argv[i][0] == '-') && !isdigit(argv[i][1])) {
919             int j;
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 (tparm->type == CMD_SINGLE ||
993                 tparm->type == CMD_SINGLE_OR_FLAG) {
994                 if (tparm->items) {
995                     fprintf(stderr, "%sToo many values after switch %s\n",
996                             NName(pname, ": "), tparm->name);
997                     code = CMD_NOTLIST;
998                     goto out;
999                 }
1000                 AddItem(tparm, argv[i], pname);        /* Add to end of list */
1001             } else if (tparm->type == CMD_LIST) {
1002                 AddItem(tparm, argv[i], pname);        /* Add to end of list */
1003             }
1004
1005             /* Now, if we're in positional mode, advance to the next item */
1006             if (positional)
1007                 curType = AdvanceType(ts, curType);
1008         }
1009     }
1010
1011     /* keep track of this for messages */
1012     ts->a0name = argv[0];
1013
1014     /* If we make it here, all the parameters are filled in.  Check to see if
1015      * this is a -help version.  Must do this before checking for all
1016      * required parms, otherwise it is a real nuisance */
1017     if (ts->parms[CMD_HELPPARM].items) {
1018         PrintSyntax(ts);
1019         /* Display full help syntax if we don't have subcommands */
1020         if (noOpcodes)
1021             PrintFlagHelp(ts);
1022         code = CMD_USAGE;
1023         goto out;
1024     }
1025
1026     /* Parsing done, see if we have all of our required parameters */
1027     for (i = 0; i < CMD_MAXPARMS; i++) {
1028         tparm = &ts->parms[i];
1029         if (tparm->type == 0)
1030             continue;           /* Skipped parm slot */
1031         if ((tparm->flags & CMD_PROCESSED) && tparm->items == 0) {
1032             if (tparm->type == CMD_SINGLE_OR_FLAG) {
1033                 tparm->items = &dummy;
1034             } else {
1035                 fprintf(stderr, "%s The field '%s' isn't completed properly\n",
1036                     NName(pname, ": "), tparm->name);
1037                 code = CMD_TOOFEW;
1038                 goto out;
1039             }
1040         }
1041         if (!(tparm->flags & CMD_OPTIONAL) && tparm->items == 0) {
1042             fprintf(stderr, "%sMissing required parameter '%s'\n",
1043                     NName(pname, ": "), tparm->name);
1044             code = CMD_TOOFEW;
1045             goto out;
1046         }
1047         tparm->flags &= ~CMD_PROCESSED;
1048     }
1049     *outsyntax = ts;
1050
1051 out:
1052     if (code && ts != NULL)
1053         ResetSyntax(ts);
1054
1055     return code;
1056 }
1057
1058 int
1059 cmd_Dispatch(int argc, char **argv)
1060 {
1061     struct cmd_syndesc *ts = NULL;
1062     int code;
1063
1064     code = cmd_Parse(argc, argv, &ts);
1065     if (code)
1066         return code;
1067
1068     /*
1069      * Before calling the beforeProc and afterProc and all the implications
1070      * from those calls, check if the help procedure was called and call it
1071      * now.
1072      */
1073     if ((ts->proc == HelpProc) || (ts->proc == AproposProc)) {
1074         code = (*ts->proc) (ts, ts->rock);
1075         goto out;
1076     }
1077
1078     /* Now, we just call the procedure and return */
1079     if (beforeProc)
1080         code = (*beforeProc) (ts, beforeRock);
1081
1082     if (code)
1083         goto out;
1084
1085     code = (*ts->proc) (ts, ts->rock);
1086
1087     if (afterProc)
1088         (*afterProc) (ts, afterRock);
1089 out:
1090     cmd_FreeOptions(&ts);
1091     return code;
1092 }
1093
1094 void
1095 cmd_FreeOptions(struct cmd_syndesc **ts)
1096 {
1097     if (*ts != NULL) {
1098         ResetSyntax(*ts);
1099         *ts = NULL;
1100     }
1101 }
1102
1103 /* free token list returned by parseLine */
1104 static int
1105 FreeTokens(struct cmd_token *alist)
1106 {
1107     struct cmd_token *nlist;
1108     for (; alist; alist = nlist) {
1109         nlist = alist->next;
1110         free(alist->key);
1111         free(alist);
1112     }
1113     return 0;
1114 }
1115
1116 /* free an argv list returned by parseline */
1117 int
1118 cmd_FreeArgv(char **argv)
1119 {
1120     char *tp;
1121     for (tp = *argv; tp; argv++, tp = *argv)
1122         free(tp);
1123     return 0;
1124 }
1125
1126 /* copy back the arg list to the argv array, freeing the cmd_tokens as you go;
1127  * the actual data is still malloc'd, and will be freed when the caller calls
1128  * cmd_FreeArgv later on
1129  */
1130 #define INITSTR ""
1131 static int
1132 CopyBackArgs(struct cmd_token *alist, char **argv,
1133              afs_int32 * an, afs_int32 amaxn)
1134 {
1135     struct cmd_token *next;
1136     afs_int32 count;
1137
1138     count = 0;
1139     if (amaxn <= 1)
1140         return CMD_TOOMANY;
1141     *argv = (char *)malloc(strlen(INITSTR) + 1);
1142     assert(*argv);
1143     strcpy(*argv, INITSTR);
1144     amaxn--;
1145     argv++;
1146     count++;
1147     while (alist) {
1148         if (amaxn <= 1)
1149             return CMD_TOOMANY; /* argv is too small for his many parms. */
1150         *argv = alist->key;
1151         next = alist->next;
1152         free(alist);
1153         alist = next;
1154         amaxn--;
1155         argv++;
1156         count++;
1157     }
1158     *(argv++) = 0;              /* use last slot for terminating null */
1159     /* don't count terminating null */
1160     *an = count;
1161     return 0;
1162 }
1163
1164 static int
1165 quote(int x)
1166 {
1167     if (x == '"' || x == 39 /* single quote */ )
1168         return 1;
1169     else
1170         return 0;
1171 }
1172
1173 static int
1174 space(int x)
1175 {
1176     if (x == 0 || x == ' ' || x == '\t' || x == '\n')
1177         return 1;
1178     else
1179         return 0;
1180 }
1181
1182 int
1183 cmd_ParseLine(char *aline, char **argv, afs_int32 * an, afs_int32 amaxn)
1184 {
1185     char tbuffer[256];
1186     char *tptr = 0;
1187     int inToken, inQuote;
1188     struct cmd_token *first, *last;
1189     struct cmd_token *ttok;
1190     int tc;
1191
1192     inToken = 0;                /* not copying token chars at start */
1193     first = NULL;
1194     last = NULL;
1195     inQuote = 0;                /* not in a quoted string */
1196     while (1) {
1197         tc = *aline++;
1198         if (tc == 0 || (!inQuote && space(tc))) {       /* terminating null gets us in here, too */
1199             if (inToken) {
1200                 inToken = 0;    /* end of this token */
1201                 if (!tptr)
1202                     return -1;  /* should never get here */
1203                 else
1204                     *tptr++ = 0;
1205                 ttok = malloc(sizeof(struct cmd_token));
1206                 assert(ttok);
1207                 ttok->next = NULL;
1208                 ttok->key = malloc(strlen(tbuffer) + 1);
1209                 assert(ttok->key);
1210                 strcpy(ttok->key, tbuffer);
1211                 if (last) {
1212                     last->next = ttok;
1213                     last = ttok;
1214                 } else
1215                     last = ttok;
1216                 if (!first)
1217                     first = ttok;
1218             }
1219         } else {
1220             /* an alpha character */
1221             if (!inToken) {
1222                 tptr = tbuffer;
1223                 inToken = 1;
1224             }
1225             if (tptr - tbuffer >= sizeof(tbuffer)) {
1226                 FreeTokens(first);
1227                 return CMD_TOOBIG;      /* token too long */
1228             }
1229             if (quote(tc)) {
1230                 /* hit a quote, toggle inQuote flag but don't insert character */
1231                 inQuote = !inQuote;
1232             } else {
1233                 /* insert character */
1234                 *tptr++ = tc;
1235             }
1236         }
1237         if (tc == 0) {
1238             /* last token flushed 'cause space(0) --> true */
1239             if (last)
1240                 last->next = NULL;
1241             return CopyBackArgs(first, argv, an, amaxn);
1242         }
1243     }
1244 }
1245
1246 int
1247 cmd_OptionAsInt(struct cmd_syndesc *syn, int pos, int *value)
1248 {
1249     if (pos > syn->nParms)
1250         return CMD_EXCESSPARMS;
1251     if (syn->parms[pos].items == NULL ||
1252         syn->parms[pos].items->data == NULL)
1253         return CMD_MISSING;
1254     if (syn->parms[pos].items == &dummy)
1255         return 0;
1256
1257     *value = strtol(syn->parms[pos].items->data, NULL, 10);
1258
1259     return 0;
1260 }
1261
1262 int
1263 cmd_OptionAsString(struct cmd_syndesc *syn, int pos, char **value)
1264 {
1265     if (pos > syn->nParms)
1266         return CMD_EXCESSPARMS;
1267     if (syn->parms[pos].items == NULL || syn->parms[pos].items->data == NULL)
1268         return CMD_MISSING;
1269     if (syn->parms[pos].items == &dummy)
1270         return 0;
1271
1272     if (*value)
1273         free(*value);
1274
1275     *value = strdup(syn->parms[pos].items->data);
1276
1277     return 0;
1278 }
1279
1280 int
1281 cmd_OptionAsList(struct cmd_syndesc *syn, int pos, struct cmd_item **value)
1282 {
1283     if (pos > syn->nParms)
1284         return CMD_EXCESSPARMS;
1285     if (syn->parms[pos].items == NULL)
1286         return CMD_MISSING;
1287
1288     *value = syn->parms[pos].items;
1289     return 0;
1290 }
1291
1292 int
1293 cmd_OptionAsFlag(struct cmd_syndesc *syn, int pos, int *value)
1294 {
1295     if (pos > syn->nParms)
1296         return CMD_EXCESSPARMS;
1297     if (syn->parms[pos].items == NULL)
1298         return CMD_MISSING;
1299
1300     *value = 1;
1301     return 0;
1302 }
1303
1304 int
1305 cmd_OptionPresent(struct cmd_syndesc *syn, int pos)
1306 {
1307     if (pos > syn->nParms || syn->parms[pos].items == NULL)
1308         return 0;
1309
1310     return 1;
1311 }