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