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