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