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