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