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