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