cmd: Add function to disable positional commands
[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 void *beforeRock, *afterRock;
33 static char initcmd_opcode[] = "initcmd";       /*Name of initcmd opcode */
34
35 /* take name and string, and return null string if name is empty, otherwise return
36    the concatenation of the two strings */
37 static char *
38 NName(char *a1, char *a2)
39 {
40     static char tbuffer[300];
41     if (strlen(a1) == 0) {
42         return "";
43     } else {
44         strncpy(tbuffer, a1, sizeof(tbuffer));
45         strncat(tbuffer, a2, sizeof(tbuffer));
46         tbuffer[sizeof(tbuffer)-1]='\0';
47         return tbuffer;
48     }
49 }
50
51 /* return true if asub is a substring of amain */
52 static int
53 SubString(char *amain, char *asub)
54 {
55     int mlen, slen;
56     int i, j;
57     mlen = (int) strlen(amain);
58     slen = (int) strlen(asub);
59     j = mlen - slen;
60     if (j < 0)
61         return 0;               /* not a substring */
62     for (i = 0; i <= j; i++) {
63         if (strncmp(amain, asub, slen) == 0)
64             return 1;
65         amain++;
66     }
67     return 0;                   /* didn't find it */
68 }
69
70 static int
71 FindType(struct cmd_syndesc *as, char *aname)
72 {
73     int i;
74     size_t cmdlen;
75     int ambig;
76     int best;
77
78     /* Allow --long-style options. */
79     if (aname[0] == '-' && aname[1] == '-' && aname[2] && aname[3]) {
80         aname++;
81     }
82
83     cmdlen = strlen(aname);
84     ambig = 0;
85     best = -1;
86     for (i = 0; i < CMD_MAXPARMS; i++) {
87         if (as->parms[i].type == 0)
88             continue;           /* this slot not set (seeked over) */
89         if (strcmp(as->parms[i].name, aname) == 0)
90             return i;
91         if (strlen(as->parms[i].name) < cmdlen)
92             continue;
93         /* A hidden option must be a full match (no best matches) */
94         if (as->parms[i].flags & CMD_HIDE)
95             continue;
96
97         if (strncmp(as->parms[i].name, aname, cmdlen) == 0) {
98             if (best != -1)
99                 ambig = 1;
100             else
101                 best = i;
102         }
103     }
104     return (ambig ? -1 : best);
105 }
106
107 static struct cmd_syndesc *
108 FindSyntax(char *aname, int *aambig)
109 {
110     struct cmd_syndesc *ts;
111     struct cmd_syndesc *best;
112     size_t cmdLen;
113     int ambig;
114
115     cmdLen = strlen(aname);
116     best = (struct cmd_syndesc *)0;
117     ambig = 0;
118     if (aambig)
119         *aambig = 0;            /* initialize to unambiguous */
120     for (ts = allSyntax; ts; ts = ts->next) {
121         if (strcmp(aname, ts->name) == 0)
122             return (ts);
123         if (strlen(ts->name) < cmdLen)
124             continue;           /* we typed more than item has */
125         /* A hidden command must be a full match (no best matches) */
126         if (ts->flags & CMD_HIDDEN)
127             continue;
128
129         /* This is just an alias for *best, or *best is just an alias for us.
130          * If we don't make this check explicitly, then an alias which is just a
131          * short prefix of the real command's name might make things ambiguous
132          * for no apparent reason.
133          */
134         if (best && ts->aliasOf == best->aliasOf)
135             continue;
136         if (strncmp(ts->name, aname, cmdLen) == 0) {
137             if (best)
138                 ambig = 1;      /* ambiguous name */
139             else
140                 best = ts;
141         }
142     }
143     if (ambig) {
144         if (aambig)
145             *aambig = ambig;    /* if ambiguous and they care, tell them */
146         return (struct cmd_syndesc *)0; /* fails */
147     } else
148         return best;            /* otherwise its not ambiguous, and they know */
149 }
150
151 /* print the help for a single parameter */
152 static void
153 PrintParmHelp(struct cmd_parmdesc *aparm)
154 {
155     if (aparm->type == CMD_FLAG) {
156 #ifdef notdef
157         /* doc people don't like seeing this information */
158         if (aparm->help)
159             printf(" (%s)", aparm->help);
160 #endif
161     } else if (aparm->help) {
162         printf(" <%s>", aparm->help);
163         if (aparm->type == CMD_LIST)
164             printf("+");
165     } else if (aparm->type == CMD_SINGLE)
166         printf(" <arg>");
167     else if (aparm->type == CMD_LIST)
168         printf(" <arg>+");
169 }
170
171 extern char *AFSVersion;
172
173 static int
174 VersionProc(struct cmd_syndesc *as, void *arock)
175 {
176     printf("%s\n", AFSVersion);
177     return 0;
178 }
179
180 void
181 PrintSyntax(struct cmd_syndesc *as)
182 {
183     int i;
184     struct cmd_parmdesc *tp;
185
186     /* now print usage, from syntax table */
187     if (noOpcodes)
188         printf("Usage: %s", as->a0name);
189     else {
190         if (!strcmp(as->name, initcmd_opcode))
191             printf("Usage: %s[%s]", NName(as->a0name, " "), as->name);
192         else
193             printf("Usage: %s%s", NName(as->a0name, " "), as->name);
194     }
195
196     for (i = 0; i < CMD_MAXPARMS; i++) {
197         tp = &as->parms[i];
198         if (tp->type == 0)
199             continue;           /* seeked over slot */
200         if (tp->flags & CMD_HIDE)
201             continue;           /* skip hidden options */
202         printf(" ");
203         if (tp->flags & CMD_OPTIONAL)
204             printf("[");
205         printf("%s", tp->name);
206         PrintParmHelp(tp);
207         if (tp->flags & CMD_OPTIONAL)
208             printf("]");
209     }
210     printf("\n");
211 }
212
213 /* must print newline in any case, to terminate preceding line */
214 static void
215 PrintAliases(struct cmd_syndesc *as)
216 {
217     struct cmd_syndesc *ts;
218
219     if (as->flags & CMD_ALIAS) {
220         ts = as->aliasOf;
221         printf("(alias for %s)\n", ts->name);
222     } else {
223         printf("\n");
224         if (!as->nextAlias)
225             return;             /* none, print nothing */
226         printf("aliases: ");
227         for (as = as->nextAlias; as; as = as->nextAlias) {
228             printf("%s ", as->name);
229         }
230         printf("\n");
231     }
232 }
233
234 void
235 PrintFlagHelp(struct cmd_syndesc *as)
236 {
237     int i;
238     struct cmd_parmdesc *tp;
239     int flag_width;
240     char *flag_prefix;
241
242     /* find flag name length */
243     flag_width = 0;
244     for (i = 0; i < CMD_MAXPARMS; i++) {
245         if (i == CMD_HELPPARM)
246             continue;
247         tp = &as->parms[i];
248         if (tp->type != CMD_FLAG)
249             continue;
250         if (tp->flags & CMD_HIDE)
251             continue;           /* skip hidden options */
252         if (!tp->help)
253             continue;
254
255         if (strlen(tp->name) > flag_width)
256             flag_width = strlen(tp->name);
257     }
258
259     /* print flag help */
260     flag_prefix = "Where:";
261     for (i = 0; i < CMD_MAXPARMS; i++) {
262         if (i == CMD_HELPPARM)
263             continue;
264         tp = &as->parms[i];
265         if (tp->type != CMD_FLAG)
266             continue;
267         if (tp->flags & CMD_HIDE)
268             continue;           /* skip hidden options */
269         if (!tp->help)
270             continue;
271
272         printf("%-7s%-*s  %s\n", flag_prefix, flag_width, tp->name, tp->help);
273         flag_prefix = "";
274     }
275 }
276
277 static int
278 AproposProc(struct cmd_syndesc *as, void *arock)
279 {
280     struct cmd_syndesc *ts;
281     char *tsub;
282     int didAny;
283
284     didAny = 0;
285     tsub = as->parms[0].items->data;
286     for (ts = allSyntax; ts; ts = ts->next) {
287         if ((ts->flags & CMD_ALIAS) || (ts->flags & CMD_HIDDEN))
288             continue;
289         if (SubString(ts->help, tsub)) {
290             printf("%s: %s\n", ts->name, ts->help);
291             didAny = 1;
292         } else if (SubString(ts->name, tsub)) {
293             printf("%s: %s\n", ts->name, ts->help);
294             didAny = 1;
295         }
296     }
297     if (!didAny)
298         printf("Sorry, no commands found\n");
299     return 0;
300 }
301
302 static int
303 HelpProc(struct cmd_syndesc *as, void *arock)
304 {
305     struct cmd_syndesc *ts;
306     struct cmd_item *ti;
307     int ambig;
308     int code = 0;
309
310     if (as->parms[0].items == 0) {
311         printf("%sCommands are:\n", NName(as->a0name, ": "));
312         for (ts = allSyntax; ts; ts = ts->next) {
313             if ((ts->flags & CMD_ALIAS) || (ts->flags & CMD_HIDDEN))
314                 continue;
315             printf("%-15s %s\n", ts->name, (ts->help ? ts->help : ""));
316         }
317     } else {
318         /* print out individual help topics */
319         for (ti = as->parms[0].items; ti; ti = ti->next) {
320             code = 0;
321             ts = FindSyntax(ti->data, &ambig);
322             if (ts && (ts->flags & CMD_HIDDEN))
323                 ts = 0;         /* no hidden commands */
324             if (ts) {
325                 /* print out command name and help */
326                 printf("%s%s: %s ", NName(as->a0name, " "), ts->name,
327                        (ts->help ? ts->help : ""));
328                 ts->a0name = as->a0name;
329                 PrintAliases(ts);
330                 PrintSyntax(ts);
331                 PrintFlagHelp(ts);
332             } else {
333                 if (!ambig)
334                     fprintf(stderr, "%sUnknown topic '%s'\n",
335                             NName(as->a0name, ": "), ti->data);
336                 else {
337                     /* ambiguous, list 'em all */
338                     fprintf(stderr,
339                             "%sAmbiguous topic '%s'; use 'apropos' to list\n",
340                             NName(as->a0name, ": "), ti->data);
341                 }
342                 code = CMD_UNKNOWNCMD;
343             }
344         }
345     }
346     return (code);
347 }
348
349 int
350 cmd_SetBeforeProc(int (*aproc) (struct cmd_syndesc * ts, void *beforeRock),
351                   void *arock)
352 {
353     beforeProc = aproc;
354     beforeRock = arock;
355     return 0;
356 }
357
358 int
359 cmd_SetAfterProc(int (*aproc) (struct cmd_syndesc * ts, void *afterRock),
360                  void *arock)
361 {
362     afterProc = aproc;
363     afterRock = arock;
364     return 0;
365 }
366
367 /* thread on list in alphabetical order */
368 static int
369 SortSyntax(struct cmd_syndesc *as)
370 {
371     struct cmd_syndesc **ld, *ud;
372
373     for (ld = &allSyntax, ud = *ld; ud; ld = &ud->next, ud = *ld) {
374         if (strcmp(ud->name, as->name) > 0) {   /* next guy is bigger than us */
375             break;
376         }
377     }
378     /* thread us on the list now */
379     *ld = as;
380     as->next = ud;
381     return 0;
382 }
383
384 struct cmd_syndesc *
385 cmd_CreateSyntax(char *aname,
386                  int (*aproc) (struct cmd_syndesc * ts, void *arock),
387                  void *arock, char *ahelp)
388 {
389     struct cmd_syndesc *td;
390
391     /* can't have two cmds in no opcode mode */
392     if (noOpcodes)
393         return NULL;
394
395     td = calloc(1, sizeof(struct cmd_syndesc));
396     assert(td);
397     td->aliasOf = td;           /* treat aliasOf as pointer to real command, no matter what */
398
399     /* copy in name, etc */
400     if (aname) {
401         td->name = malloc(strlen(aname) + 1);
402         assert(td->name);
403         strcpy(td->name, aname);
404     } else {
405         td->name = NULL;
406         noOpcodes = 1;
407     }
408     if (ahelp) {
409         /* Piggy-back the hidden option onto the help string */
410         if (ahelp == (char *)CMD_HIDDEN) {
411             td->flags |= CMD_HIDDEN;
412         } else {
413             td->help = malloc(strlen(ahelp) + 1);
414             assert(td->help);
415             strcpy(td->help, ahelp);
416         }
417     } else
418         td->help = NULL;
419     td->proc = aproc;
420     td->rock = arock;
421
422     SortSyntax(td);
423
424     cmd_Seek(td, CMD_HELPPARM);
425     cmd_AddParm(td, "-help", CMD_FLAG, CMD_OPTIONAL, "get detailed help");
426     cmd_Seek(td, 0);
427
428     return td;
429 }
430
431 int
432 cmd_CreateAlias(struct cmd_syndesc *as, char *aname)
433 {
434     struct cmd_syndesc *td;
435
436     td = malloc(sizeof(struct cmd_syndesc));
437     assert(td);
438     memcpy(td, as, sizeof(struct cmd_syndesc));
439     td->name = malloc(strlen(aname) + 1);
440     assert(td->name);
441     strcpy(td->name, aname);
442     td->flags |= CMD_ALIAS;
443     /* if ever free things, make copy of help string, too */
444
445     /* thread on list */
446     SortSyntax(td);
447
448     /* thread on alias lists */
449     td->nextAlias = as->nextAlias;
450     as->nextAlias = td;
451     td->aliasOf = as;
452
453     return 0;                   /* all done */
454 }
455
456 void
457 cmd_DisablePositionalCommands(void)
458 {
459     enablePositional = 0;
460 }
461
462 int
463 cmd_IsAdministratorCommand(struct cmd_syndesc *as)
464 {
465     as->flags |= CMD_ADMIN;
466     return 0;
467 }
468
469 int
470 cmd_Seek(struct cmd_syndesc *as, int apos)
471 {
472     if (apos >= CMD_MAXPARMS)
473         return CMD_EXCESSPARMS;
474     as->nParms = apos;
475     return 0;
476 }
477
478 int
479 cmd_AddParm(struct cmd_syndesc *as, char *aname, int atype,
480             afs_int32 aflags, char *ahelp)
481 {
482     struct cmd_parmdesc *tp;
483
484     if (as->nParms >= CMD_MAXPARMS)
485         return CMD_EXCESSPARMS;
486     tp = &as->parms[as->nParms++];
487
488     tp->name = malloc(strlen(aname) + 1);
489     assert(tp->name);
490     strcpy(tp->name, aname);
491     tp->type = atype;
492     tp->flags = aflags;
493     tp->items = NULL;
494     if (ahelp) {
495         tp->help = malloc(strlen(ahelp) + 1);
496         assert(tp->help);
497         strcpy(tp->help, ahelp);
498     } else
499         tp->help = NULL;
500     return 0;
501 }
502
503 /* add a text item to the end of the parameter list */
504 static int
505 AddItem(struct cmd_parmdesc *aparm, char *aval)
506 {
507     struct cmd_item *ti, *ni;
508     ti = calloc(1, sizeof(struct cmd_item));
509     assert(ti);
510     ti->data = malloc(strlen(aval) + 1);
511     assert(ti->data);
512     strcpy(ti->data, aval);
513     /* now put ti at the *end* of the list */
514     if ((ni = aparm->items)) {
515         for (; ni; ni = ni->next)
516             if (ni->next == 0)
517                 break;          /* skip to last one */
518         ni->next = ti;
519     } else
520         aparm->items = ti;      /* we're first */
521     return 0;
522 }
523
524 /* skip to next non-flag item, if any */
525 static int
526 AdvanceType(struct cmd_syndesc *as, afs_int32 aval)
527 {
528     afs_int32 next;
529     struct cmd_parmdesc *tp;
530
531     /* first see if we should try to grab rest of line for this dude */
532     if (as->parms[aval].flags & CMD_EXPANDS)
533         return aval;
534
535     /* if not, find next non-flag used slot */
536     for (next = aval + 1; next < CMD_MAXPARMS; next++) {
537         tp = &as->parms[next];
538         if (tp->type != 0 && tp->type != CMD_FLAG)
539             return next;
540     }
541     return aval;
542 }
543
544 /* discard parameters filled in by dispatch */
545 static void
546 ResetSyntax(struct cmd_syndesc *as)
547 {
548     int i;
549     struct cmd_parmdesc *tp;
550     struct cmd_item *ti, *ni;
551
552     tp = as->parms;
553     for (i = 0; i < CMD_MAXPARMS; i++, tp++) {
554         switch (tp->type) {
555         case CMD_SINGLE:
556         case CMD_LIST:
557             /* free whole list in both cases, just for fun */
558             for (ti = tp->items; ti; ti = ni) {
559                 ni = ti->next;
560                 free(ti->data);
561                 free(ti);
562             }
563             break;
564
565         default:
566             break;
567         }
568         tp->items = NULL;
569     }
570 }
571
572 /* move the expands flag to the last one in the list */
573 static int
574 SetupExpandsFlag(struct cmd_syndesc *as)
575 {
576     struct cmd_parmdesc *tp;
577     int last, i;
578
579     last = -1;
580     /* find last CMD_LIST type parameter, optional or not, and make it expandable
581      * if no other dude is expandable */
582     for (i = 0; i < CMD_MAXPARMS; i++) {
583         tp = &as->parms[i];
584         if (tp->type == CMD_LIST) {
585             if (tp->flags & CMD_EXPANDS)
586                 return 0;       /* done if already specified */
587             last = i;
588         }
589     }
590     if (last >= 0)
591         as->parms[last].flags |= CMD_EXPANDS;
592     return 0;
593 }
594
595 /* Take the current argv & argc and alter them so that the initialization
596  * opcode is made to appear.  This is used in cases where the initialization
597  * opcode is implicitly invoked.*/
598 static char **
599 InsertInitOpcode(int *aargc, char **aargv)
600 {
601     char **newargv;             /*Ptr to new, expanded argv space */
602     char *pinitopcode;          /*Ptr to space for name of init opcode */
603     int i;                      /*Loop counter */
604
605     /* Allocate the new argv array, plus one for the new opcode, plus one
606      * more for the trailing null pointer */
607     newargv = malloc(((*aargc) + 2) * sizeof(char *));
608     if (!newargv) {
609         fprintf(stderr, "%s: Can't create new argv array with %d+2 slots\n",
610                 aargv[0], *aargc);
611         return (NULL);
612     }
613
614     /* Create space for the initial opcode & fill it in */
615     pinitopcode = malloc(sizeof(initcmd_opcode));
616     if (!pinitopcode) {
617         fprintf(stderr, "%s: Can't malloc initial opcode space\n", aargv[0]);
618         free(newargv);
619         return (NULL);
620     }
621     strcpy(pinitopcode, initcmd_opcode);
622
623     /* Move all the items in the old argv into the new argv, in their
624      * proper places */
625     for (i = *aargc; i > 1; i--)
626         newargv[i] = aargv[i - 1];
627
628     /* Slip in the opcode and the trailing null pointer, and bump the
629      * argument count up by one for the new opcode */
630     newargv[0] = aargv[0];
631     newargv[1] = pinitopcode;
632     (*aargc)++;
633     newargv[*aargc] = NULL;
634
635     /* Return the happy news */
636     return (newargv);
637
638 }                               /*InsertInitOpcode */
639
640 static int
641 NoParmsOK(struct cmd_syndesc *as)
642 {
643     int i;
644     struct cmd_parmdesc *td;
645
646     for (i = 0; i < CMD_MAXPARMS; i++) {
647         td = &as->parms[i];
648         if (td->type != 0 && !(td->flags & CMD_OPTIONAL)) {
649             /* found a non-optional (e.g. required) parm, so NoParmsOK
650              * is false (some parms are required) */
651             return 0;
652         }
653     }
654     return 1;
655 }
656
657 /* Call the appropriate function, or return syntax error code.  Note: if
658  * no opcode is specified, an initialization routine exists, and it has
659  * NOT been called before, we invoke the special initialization opcode
660  */
661 int
662 cmd_Dispatch(int argc, char **argv)
663 {
664     char *pname;
665     struct cmd_syndesc *ts;
666     struct cmd_parmdesc *tparm;
667     afs_int32 i, j;
668     int curType;
669     int positional;
670     int ambig;
671     static int initd = 0;       /*Is this the first time this routine has been called? */
672     static int initcmdpossible = 1;     /*Should be consider parsing the initial command? */
673
674     if (!initd) {
675         initd = 1;
676         /* Add help, apropos commands once */
677         if (!noOpcodes) {
678             ts = cmd_CreateSyntax("help", HelpProc, NULL,
679                                   "get help on commands");
680             cmd_AddParm(ts, "-topic", CMD_LIST, CMD_OPTIONAL, "help string");
681             cmd_AddParm(ts, "-admin", CMD_FLAG, CMD_OPTIONAL, NULL);
682
683             ts = cmd_CreateSyntax("apropos", AproposProc, NULL,
684                                   "search by help text");
685             cmd_AddParm(ts, "-topic", CMD_SINGLE, CMD_REQUIRED,
686                         "help string");
687             ts = cmd_CreateSyntax("version", VersionProc, NULL,
688                                   (char *)CMD_HIDDEN);
689             ts = cmd_CreateSyntax("-version", VersionProc, NULL,
690                                   (char *)CMD_HIDDEN);
691             ts = cmd_CreateSyntax("-help", HelpProc, NULL,
692                                   (char *)CMD_HIDDEN);
693             ts = cmd_CreateSyntax("--version", VersionProc, NULL,
694                                   (char *)CMD_HIDDEN);
695             ts = cmd_CreateSyntax("--help", HelpProc, NULL,
696                                   (char *)CMD_HIDDEN);
697         }
698     }
699
700     /*Remember the program name */
701     pname = argv[0];
702
703     if (noOpcodes) {
704         if (argc == 1) {
705             if (!NoParmsOK(allSyntax)) {
706                 printf("%s: Type '%s -help' for help\n", pname, pname);
707                 return (CMD_USAGE);
708             }
709         }
710     } else {
711         if (argc < 2) {
712             /* if there is an initcmd, don't print an error message, just
713              * setup to use the initcmd below. */
714             if (!(initcmdpossible && FindSyntax(initcmd_opcode, (int *)0))) {
715                 printf("%s: Type '%s help' or '%s help <topic>' for help\n",
716                        pname, pname, pname);
717                 return (CMD_USAGE);
718             }
719         }
720     }
721
722     /* Find the syntax descriptor for this command, doing prefix matching properly */
723     if (noOpcodes) {
724         ts = allSyntax;
725     } else {
726         ts = (argc < 2 ? 0 : FindSyntax(argv[1], &ambig));
727         if (!ts) {
728             /*First token doesn't match a syntax descriptor */
729             if (initcmdpossible) {
730                 /*If initial command line handling hasn't been done yet,
731                  * see if there is a descriptor for the initialization opcode.
732                  * Only try this once. */
733                 initcmdpossible = 0;
734                 ts = FindSyntax(initcmd_opcode, (int *)0);
735                 if (!ts) {
736                     /*There is no initialization opcode available, so we declare
737                      * an error */
738                     if (ambig) {
739                         fprintf(stderr, "%s", NName(pname, ": "));
740                         fprintf(stderr,
741                                 "Ambiguous operation '%s'; type '%shelp' for list\n",
742                                 argv[1], NName(pname, " "));
743                     } else {
744                         fprintf(stderr, "%s", NName(pname, ": "));
745                         fprintf(stderr,
746                                 "Unrecognized operation '%s'; type '%shelp' for list\n",
747                                 argv[1], NName(pname, " "));
748                     }
749                     return (CMD_UNKNOWNCMD);
750                 } else {
751                     /*Found syntax structure for an initialization opcode.  Fix
752                      * up argv and argc to relect what the user
753                      * ``should have'' typed */
754                     if (!(argv = InsertInitOpcode(&argc, argv))) {
755                         fprintf(stderr,
756                                 "%sCan't insert implicit init opcode into command line\n",
757                                 NName(pname, ": "));
758                         return (CMD_INTERNALERROR);
759                     }
760                 }
761             } /*Initial opcode not yet attempted */
762             else {
763                 /* init cmd already run and no syntax entry found */
764                 if (ambig) {
765                     fprintf(stderr, "%s", NName(pname, ": "));
766                     fprintf(stderr,
767                             "Ambiguous operation '%s'; type '%shelp' for list\n",
768                             argv[1], NName(pname, " "));
769                 } else {
770                     fprintf(stderr, "%s", NName(pname, ": "));
771                     fprintf(stderr,
772                             "Unrecognized operation '%s'; type '%shelp' for list\n",
773                             argv[1], NName(pname, " "));
774                 }
775                 return CMD_UNKNOWNCMD;
776             }
777         }                       /*Argv[1] is not a valid opcode */
778     }                           /*Opcodes are defined */
779
780     /* Found the descriptor; start parsing.  curType is the type we're
781      * trying to parse */
782     curType = 0;
783
784     /* We start off parsing in "positional" mode, where tokens are put in
785      * slots positionally.  If we find a name that takes args, we go
786      * out of positional mode, and from that point on, expect a switch
787      * before any particular token. */
788
789     positional = enablePositional;      /* Accepting positional cmds ? */
790     i = noOpcodes ? 1 : 2;
791     SetupExpandsFlag(ts);
792     for (; i < argc; i++) {
793         /* Only tokens that start with a hyphen and are not followed by a digit
794          * are considered switches.  This allow negative numbers. */
795         if ((argv[i][0] == '-') && !isdigit(argv[i][1])) {
796             /* Find switch */
797             j = FindType(ts, argv[i]);
798             if (j < 0) {
799                 fprintf(stderr,
800                         "%sUnrecognized or ambiguous switch '%s'; type ",
801                         NName(pname, ": "), argv[i]);
802                 if (noOpcodes)
803                     fprintf(stderr, "'%s -help' for detailed help\n",
804                             argv[0]);
805                 else
806                     fprintf(stderr, "'%shelp %s' for detailed help\n",
807                             NName(argv[0], " "), ts->name);
808                 ResetSyntax(ts);
809                 return (CMD_UNKNOWNSWITCH);
810             }
811             if (j >= CMD_MAXPARMS) {
812                 fprintf(stderr, "%sInternal parsing error\n",
813                         NName(pname, ": "));
814                 ResetSyntax(ts);
815                 return (CMD_INTERNALERROR);
816             }
817             if (ts->parms[j].type == CMD_FLAG) {
818                 ts->parms[j].items = &dummy;
819             } else {
820                 positional = 0;
821                 curType = j;
822                 ts->parms[j].flags |= CMD_PROCESSED;
823             }
824         } else {
825             /* Try to fit in this descr */
826             if (curType >= CMD_MAXPARMS) {
827                 fprintf(stderr, "%sToo many arguments\n", NName(pname, ": "));
828                 ResetSyntax(ts);
829                 return (CMD_TOOMANY);
830             }
831             tparm = &ts->parms[curType];
832
833             if ((tparm->type == 0) ||   /* No option in this slot */
834                 (tparm->type == CMD_FLAG)) {    /* A flag (not an argument */
835                 /* skipped parm slot */
836                 curType++;      /* Skip this slot and reprocess this parm */
837                 i--;
838                 continue;
839             }
840
841             if (!(tparm->flags & CMD_PROCESSED) && (tparm->flags & CMD_HIDE)) {
842                 curType++;      /* Skip this slot and reprocess this parm */
843                 i--;
844                 continue;
845             }
846
847             if (tparm->type == CMD_SINGLE) {
848                 if (tparm->items) {
849                     fprintf(stderr, "%sToo many values after switch %s\n",
850                             NName(pname, ": "), tparm->name);
851                     ResetSyntax(ts);
852                     return (CMD_NOTLIST);
853                 }
854                 AddItem(tparm, argv[i]);        /* Add to end of list */
855             } else if (tparm->type == CMD_LIST) {
856                 AddItem(tparm, argv[i]);        /* Add to end of list */
857             }
858             /* Now, if we're in positional mode, advance to the next item */
859             if (positional)
860                 curType = AdvanceType(ts, curType);
861         }
862     }
863
864     /* keep track of this for messages */
865     ts->a0name = argv[0];
866
867     /* If we make it here, all the parameters are filled in.  Check to see if
868      * this is a -help version.  Must do this before checking for all
869      * required parms, otherwise it is a real nuisance */
870     if (ts->parms[CMD_HELPPARM].items) {
871         PrintSyntax(ts);
872         /* Display full help syntax if we don't have subcommands */
873         if (noOpcodes)
874             PrintFlagHelp(ts);
875         ResetSyntax(ts);
876         return 0;
877     }
878
879     /* Parsing done, see if we have all of our required parameters */
880     for (i = 0; i < CMD_MAXPARMS; i++) {
881         tparm = &ts->parms[i];
882         if (tparm->type == 0)
883             continue;           /* Skipped parm slot */
884         if ((tparm->flags & CMD_PROCESSED) && tparm->items == 0) {
885             fprintf(stderr, "%s The field '%s' isn't completed properly\n",
886                     NName(pname, ": "), tparm->name);
887             ResetSyntax(ts);
888             tparm->flags &= ~CMD_PROCESSED;
889             return (CMD_TOOFEW);
890         }
891         if (!(tparm->flags & CMD_OPTIONAL) && tparm->items == 0) {
892             fprintf(stderr, "%sMissing required parameter '%s'\n",
893                     NName(pname, ": "), tparm->name);
894             ResetSyntax(ts);
895             tparm->flags &= ~CMD_PROCESSED;
896             return (CMD_TOOFEW);
897         }
898         tparm->flags &= ~CMD_PROCESSED;
899     }
900
901     /*
902      * Before calling the beforeProc and afterProc and all the implications
903      * from those calls, check if the help procedure was called and call it
904      * now.
905      */
906     if ((ts->proc == HelpProc) || (ts->proc == AproposProc)) {
907         i = (*ts->proc) (ts, ts->rock);
908         ResetSyntax(ts);
909         return (i);
910     }
911
912     /* Now, we just call the procedure and return */
913     if (beforeProc)
914         i = (*beforeProc) (ts, beforeRock);
915     else
916         i = 0;
917     if (i) {
918         ResetSyntax(ts);
919         return (i);
920     }
921     i = (*ts->proc) (ts, ts->rock);
922     if (afterProc)
923         (*afterProc) (ts, afterRock);
924     ResetSyntax(ts);            /* Reset and free things */
925     return (i);
926 }
927
928 /* free token list returned by parseLine */
929 static int
930 FreeTokens(struct cmd_token *alist)
931 {
932     struct cmd_token *nlist;
933     for (; alist; alist = nlist) {
934         nlist = alist->next;
935         free(alist->key);
936         free(alist);
937     }
938     return 0;
939 }
940
941 /* free an argv list returned by parseline */
942 int
943 cmd_FreeArgv(char **argv)
944 {
945     char *tp;
946     for (tp = *argv; tp; argv++, tp = *argv)
947         free(tp);
948     return 0;
949 }
950
951 /* copy back the arg list to the argv array, freeing the cmd_tokens as you go;
952  * the actual data is still malloc'd, and will be freed when the caller calls
953  * cmd_FreeArgv later on
954  */
955 #define INITSTR ""
956 static int
957 CopyBackArgs(struct cmd_token *alist, char **argv,
958              afs_int32 * an, afs_int32 amaxn)
959 {
960     struct cmd_token *next;
961     afs_int32 count;
962
963     count = 0;
964     if (amaxn <= 1)
965         return CMD_TOOMANY;
966     *argv = (char *)malloc(strlen(INITSTR) + 1);
967     assert(*argv);
968     strcpy(*argv, INITSTR);
969     amaxn--;
970     argv++;
971     count++;
972     while (alist) {
973         if (amaxn <= 1)
974             return CMD_TOOMANY; /* argv is too small for his many parms. */
975         *argv = alist->key;
976         next = alist->next;
977         free(alist);
978         alist = next;
979         amaxn--;
980         argv++;
981         count++;
982     }
983     *(argv++) = 0;              /* use last slot for terminating null */
984     /* don't count terminating null */
985     *an = count;
986     return 0;
987 }
988
989 static int
990 quote(int x)
991 {
992     if (x == '"' || x == 39 /* single quote */ )
993         return 1;
994     else
995         return 0;
996 }
997
998 static int
999 space(int x)
1000 {
1001     if (x == 0 || x == ' ' || x == '\t' || x == '\n')
1002         return 1;
1003     else
1004         return 0;
1005 }
1006
1007 int
1008 cmd_ParseLine(char *aline, char **argv, afs_int32 * an, afs_int32 amaxn)
1009 {
1010     char tbuffer[256];
1011     char *tptr = 0;
1012     int inToken, inQuote;
1013     struct cmd_token *first, *last;
1014     struct cmd_token *ttok;
1015     int tc;
1016
1017     inToken = 0;                /* not copying token chars at start */
1018     first = NULL;
1019     last = NULL;
1020     inQuote = 0;                /* not in a quoted string */
1021     while (1) {
1022         tc = *aline++;
1023         if (tc == 0 || (!inQuote && space(tc))) {       /* terminating null gets us in here, too */
1024             if (inToken) {
1025                 inToken = 0;    /* end of this token */
1026                 if (!tptr)
1027                     return -1;  /* should never get here */
1028                 else
1029                     *tptr++ = 0;
1030                 ttok = malloc(sizeof(struct cmd_token));
1031                 assert(ttok);
1032                 ttok->next = NULL;
1033                 ttok->key = malloc(strlen(tbuffer) + 1);
1034                 assert(ttok->key);
1035                 strcpy(ttok->key, tbuffer);
1036                 if (last) {
1037                     last->next = ttok;
1038                     last = ttok;
1039                 } else
1040                     last = ttok;
1041                 if (!first)
1042                     first = ttok;
1043             }
1044         } else {
1045             /* an alpha character */
1046             if (!inToken) {
1047                 tptr = tbuffer;
1048                 inToken = 1;
1049             }
1050             if (tptr - tbuffer >= sizeof(tbuffer)) {
1051                 FreeTokens(first);
1052                 return CMD_TOOBIG;      /* token too long */
1053             }
1054             if (quote(tc)) {
1055                 /* hit a quote, toggle inQuote flag but don't insert character */
1056                 inQuote = !inQuote;
1057             } else {
1058                 /* insert character */
1059                 *tptr++ = tc;
1060             }
1061         }
1062         if (tc == 0) {
1063             /* last token flushed 'cause space(0) --> true */
1064             if (last)
1065                 last->next = NULL;
1066             return CopyBackArgs(first, argv, an, amaxn);
1067         }
1068     }
1069 }