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