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