1 /***************************************************************************
2 
3 getarg.c - routines to grab the parameters from the command line:
4 
5 Names of all the routines except the main one start with GA (Get
6 Arguments) to prevent conflicts.
7 
8 The following routines are available in this module:
9 
10 1. int GAGetArgs(argc, argv, CtrlStr, Variables...)
11 where argc, argv are received on entry.
12 CtrlStr is the contrl string (see below)
13 Variables are all the variables to be set according to CtrlStr.
14 Note that all the variables MUST be transfered by address.
15 Return 0 on correct parsing, otherwise error number (see GetArg.h).
16 
17 2. GAPrintHowTo(CtrlStr)
18 Print the control string to stderr, in the correct format.
19 This feature is very useful in case of an error during GetArgs parsing.
20 Chars equal to SPACE_CHAR are not printed (regular spaces are NOT
21 allowed, and so using SPACE_CHAR you can create space in PrintHowTo).
22 
23 3. GAPrintErrMsg(Error)
24 Describe the error to stderr, according to Error (usually returned by
25 GAGetArgs).
26 
27 CtrlStr format:
28 
29 The control string passed to GetArgs controls the way argv (argc) are
30 parsed. Each entry in this string must not have any spaces in it. The
31 First Entry is the name of the program, which is usually ignored except
32 when GAPrintHowTo is called. All the other entries (except the last one
33 which we will come back to later) must have the following format:
34 
35 1. One letter which sets the option letter.
36 2. '!' or '%' to determines if this option is really optional ('%') or
37    required ('!')...
38 3. '-' must always be given.
39 4. Alphanumeric string, usually ignored, but used by GAPrintHowTo to
40    print the meaning of this option.
41 5. Sequences starts with '!' or '%'. Again if '!' then this sequence
42    must exist (only if its option flag is given of course), and if '%'
43    it is optional. Each sequence will continue with one or two
44    characters which defines the kind of the input:
45 a: d, x, o, u - integer is expected (decimal, hex, octal base or unsigned).
46 b: D, X, O, U - long integer is expected (same as above).
47 c: f - float number is expected.
48 d: F - double number is expected.
49 e: s - string is expected.
50 f: *? - any number of '?' kind (d, x, o, u, D, X, O, U, f, F, s)
51    will match this one. If '?' is numeric, it scans until
52    non-numeric input is given. If '?' is 's' then it scans
53    up to the next option or end of argv.
54 
55 If the last parameter given in the CtrlStr, is not an option (i.e. the
56 second char is not in ['!', '%'] and the third one is not '-'), all what
57 remained from argv is linked to it.
58 
59 The variables passed to GAGetArgs (starting from 4th parameter) MUST
60 match the order of the CtrlStr:
61 
62 For each option, one integer address must be passed. This integer must
63 be initialized with 0. If that option is given in the command line, it will
64 be set.
65 
66 In addition, the sequences that might follow an option require the
67 following parameters to pass:
68 
69 1. d, x, o, u - pointer to integer (int *).
70 2. D, X, O, U - pointer to long (long *).
71 3. f - pointer to float (float *).
72 4. F - pointer to double (double *).
73 5. s - pointer to char (char *). NO allocation is needed!
74 6. *? - TWO variables are passed for each wild request. the first
75    one is (address of) integer, and it will return number of
76    parameters actually matched this sequence, and the second
77    one is a pointer to pointer to ? (? **), and will return an
78    address to a block of pointers to ? kind, terminated with
79    NULL pointer. NO pre-allocation is required. The caller is
80    responsible for freeing this memory, including the pointed to
81    memory.
82 
83 Note that these two variables are pretty like the argv/argc pair...
84 
85 Examples:
86 
87 "Example1 i%-OneInteger!d s%-Strings!*s j%- k!-Float!f Files"
88 
89 Will match: Example1 -i 77 -s String1 String2 String3 -k 88.2 File1 File2
90 or: Example1 -s String1 -k 88.3 -i 999 -j
91 but not: Example1 -i 77 78 (option i expects one integer, k must be).
92 
93 Note the option k must exist, and that the order of the options is not
94 important. In the first examples File1 & File2 will match the Files
95 in the command line.
96 
97 A call to GAPrintHowTo with this CtrlStr will print to stderr:
98 Example1 [-i OneIngeter] [-s Strings...] [-j] -k Float Files...
99 
100 Notes:
101 
102 1. This module assumes that all the pointers to all kind of data types
103 have the same length and format, i.e. sizeof(int *) == sizeof(char *).
104 
105 SPDX-License-Identifier: MIT
106 
107 **************************************************************************/
108 
109 #include <stdlib.h>
110 #include <stdio.h>
111 #include <string.h>
112 #include <stdbool.h>
113 #include <stdarg.h>
114 
115 #include "getarg.h"
116 
117 #define MAX_PARAM           100    /* maximum number of parameters allowed. */
118 #define CTRL_STR_MAX_LEN    1024
119 
120 #define SPACE_CHAR '|'  /* The character not to print using HowTo. */
121 
122 #define ARG_OK false
123 
124 #define ISSPACE(x) ((x) <= ' ') /* Not conventional - but works fine! */
125 
126 /* The two characters '%' and '!' are used in the control string: */
127 #define ISCTRLCHAR(x) (((x) == '%') || ((x) == '!'))
128 
129 static char *GAErrorToken;  /* On error, ErrorToken is set to point to it. */
130 static int GATestAllSatis(char *CtrlStrCopy, char *CtrlStr, char **argv_end,
131                           char ***argv, void *Parameters[MAX_PARAM],
132                           int *ParamCount);
133 static bool GAUpdateParameters(void *Parameters[], int *ParamCount,
134                               char *Option, char *CtrlStrCopy, char *CtrlStr,
135                               char **argv_end, char ***argv);
136 static int GAGetParmeters(void *Parameters[], int *ParamCount,
137                           char *CtrlStrCopy, char *Option, char **argv_end,
138                           char ***argv);
139 static int GAGetMultiParmeters(void *Parameters[], int *ParamCount,
140                                char *CtrlStrCopy, char **argv_end, char ***argv);
141 static void GASetParamCount(char *CtrlStr, int Max, int *ParamCount);
142 static bool GAOptionExists(char **argv_end, char **argv);
143 
144 /***************************************************************************
145  Allocate or die
146 ***************************************************************************/
147 static void *
xmalloc(unsigned size)148 xmalloc(unsigned size) {
149 
150     void *p;
151 
152     if ((p = malloc(size)) != NULL)
153         return p;
154 
155     fprintf(stderr, "Not enough memory, exit.\n");
156     exit(2);
157 
158     return NULL;    /* Makes warning silent. */
159 }
160 /***************************************************************************
161  Routine to access the command line argument and interpret them:
162  Return ARG_OK (0) is case of successful parsing, error code else...
163 ***************************************************************************/
164 bool
GAGetArgs(int argc,char ** argv,char * CtrlStr,...)165 GAGetArgs(int argc,
166         char **argv,
167         char *CtrlStr, ...) {
168 
169     int i, ParamCount = 0;
170     void *Parameters[MAX_PARAM];     /* Save here parameter addresses. */
171     char CtrlStrCopy[CTRL_STR_MAX_LEN];
172     char **argv_end = argv + argc;
173     va_list ap;
174 
175     strncpy(CtrlStrCopy, CtrlStr, sizeof(CtrlStrCopy)-1);
176     GASetParamCount(CtrlStr, strlen(CtrlStr), &ParamCount);
177     va_start(ap, CtrlStr);
178     for (i = 1; i <= ParamCount; i++)
179         Parameters[i - 1] = va_arg(ap, void *);
180     va_end(ap);
181 
182     argv++;    /* Skip the program name (first in argv/c list). */
183     while (argv < argv_end) {
184 	bool Error = false;
185         if (!GAOptionExists(argv_end, argv))
186             break;    /* The loop. */
187         char *Option = *argv++;
188         if ((Error = GAUpdateParameters(Parameters, &ParamCount, Option,
189                                         CtrlStrCopy, CtrlStr, argv_end,
190                                         &argv)) != false)
191             return Error;
192     }
193     /* Check for results and update trail of command line: */
194     return GATestAllSatis(CtrlStrCopy, CtrlStr, argv_end, &argv, Parameters,
195                           &ParamCount) != ARG_OK;
196 }
197 
198 /***************************************************************************
199  Routine to search for unsatisfied flags - simply scan the list for !-
200  sequence. Before this scan, this routine updates the rest of the command
201  line into the last two parameters if it is requested by the CtrlStr
202  (last item in CtrlStr is NOT an option).
203  Return ARG_OK if all satisfied, CMD_ERR_AllSatis error else.
204 ***************************************************************************/
205 static int
GATestAllSatis(char * CtrlStrCopy,char * CtrlStr,char ** argv_end,char *** argv,void * Parameters[MAX_PARAM],int * ParamCount)206 GATestAllSatis(char *CtrlStrCopy,
207                char *CtrlStr,
208                char **argv_end,
209                char ***argv,
210                void *Parameters[MAX_PARAM],
211                int *ParamCount) {
212 
213     int i;
214     static char *LocalToken = NULL;
215 
216     /* If LocalToken is not initialized - do it now. Note that this string
217      * should be writable as well so we can not assign it directly.
218      */
219     if (LocalToken == NULL) {
220         LocalToken = (char *)malloc(3);
221         strcpy(LocalToken, "-?");
222     }
223 
224     /* Check if last item is an option. If not then copy rest of command
225      * line into it as 1. NumOfprm, 2. pointer to block of pointers.
226      */
227     for (i = strlen(CtrlStr) - 1; i > 0 && !ISSPACE(CtrlStr[i]); i--) ;
228     if (!ISCTRLCHAR(CtrlStr[i + 2])) {
229         GASetParamCount(CtrlStr, i, ParamCount); /* Point in correct param. */
230         *(int *)Parameters[(*ParamCount)++] = argv_end - *argv;
231 	*(char ***)Parameters[(*ParamCount)++] = *argv;
232     }
233 
234     i = 0;
235     while (++i < (int)strlen(CtrlStrCopy))
236         if ((CtrlStrCopy[i] == '-') && (CtrlStrCopy[i - 1] == '!')) {
237             GAErrorToken = LocalToken;
238             LocalToken[1] = CtrlStrCopy[i - 2];    /* Set the correct flag. */
239             return CMD_ERR_AllSatis;
240         }
241 
242     return ARG_OK;
243 }
244 
245 /***************************************************************************
246  Routine to update the parameters according to the given Option:
247  **************************************************************************/
248 static bool
GAUpdateParameters(void * Parameters[],int * ParamCount,char * Option,char * CtrlStrCopy,char * CtrlStr,char ** argv_end,char *** argv)249 GAUpdateParameters(void *Parameters[],
250                    int *ParamCount,
251                    char *Option,
252                    char *CtrlStrCopy,
253                    char *CtrlStr,
254                    char **argv_end,
255                    char ***argv) {
256 
257     int i;
258     bool BooleanTrue = Option[2] != '-';
259 
260     if (Option[0] != '-') {
261         GAErrorToken = Option;
262         return CMD_ERR_NotAnOpt;
263     }
264     i = 0;    /* Scan the CtrlStrCopy for that option: */
265     while (i + 2 < (int)strlen(CtrlStrCopy)) {
266         if ((CtrlStrCopy[i] == Option[1]) && (ISCTRLCHAR(CtrlStrCopy[i + 1]))
267             && (CtrlStrCopy[i + 2] == '-')) {
268             /* We found that option! */
269             break;
270         }
271         i++;
272     }
273     if (i + 2 >= (int)strlen(CtrlStrCopy)) {
274         GAErrorToken = Option;
275         return CMD_ERR_NoSuchOpt;
276     }
277 
278     /* If we are here, then we found that option in CtrlStr - Strip it off: */
279     CtrlStrCopy[i] = CtrlStrCopy[i + 1] = CtrlStrCopy[i + 2] = (char)' ';
280     GASetParamCount(CtrlStr, i, ParamCount); /* Set it to point in
281                                                 correct prm. */
282     i += 3;
283     /* Set boolean flag for that option. */
284     *(bool *)Parameters[(*ParamCount)++] = BooleanTrue;
285     if (ISSPACE(CtrlStrCopy[i]))
286         return ARG_OK;    /* Only a boolean flag is needed. */
287 
288     /* Skip the text between the boolean option and data follows: */
289     while (!ISCTRLCHAR(CtrlStrCopy[i]))
290         i++;
291     /* Get the parameters and return the appropriete return code: */
292     return GAGetParmeters(Parameters, ParamCount, &CtrlStrCopy[i],
293                           Option, argv_end, argv);
294 }
295 
296 /***************************************************************************
297  Routine to get parameters according to the CtrlStr given from argv/argc
298 ***************************************************************************/
299 static int
GAGetParmeters(void * Parameters[],int * ParamCount,char * CtrlStrCopy,char * Option,char ** argv_end,char *** argv)300 GAGetParmeters(void *Parameters[],
301                int *ParamCount,
302                char *CtrlStrCopy,
303                char *Option,
304                char **argv_end,
305                char ***argv) {
306 
307     int i = 0, ScanRes;
308 
309     while (!(ISSPACE(CtrlStrCopy[i]))) {
310         switch (CtrlStrCopy[i + 1]) {
311           case 'd':    /* Get signed integers. */
312               ScanRes = sscanf(*((*argv)++), "%d",
313                                (int *)Parameters[(*ParamCount)++]);
314               break;
315           case 'u':    /* Get unsigned integers. */
316               ScanRes = sscanf(*((*argv)++), "%u",
317                                (unsigned *)Parameters[(*ParamCount)++]);
318               break;
319           case 'x':    /* Get hex integers. */
320               ScanRes = sscanf(*((*argv)++), "%x",
321                                (unsigned int *)Parameters[(*ParamCount)++]);
322               break;
323           case 'o':    /* Get octal integers. */
324               ScanRes = sscanf(*((*argv)++), "%o",
325                                (unsigned int *)Parameters[(*ParamCount)++]);
326               break;
327           case 'D':    /* Get signed long integers. */
328               ScanRes = sscanf(*((*argv)++), "%ld",
329                                (long *)Parameters[(*ParamCount)++]);
330               break;
331           case 'U':    /* Get unsigned long integers. */
332               ScanRes = sscanf(*((*argv)++), "%lu",
333                                (unsigned long *)Parameters[(*ParamCount)++]);
334               break;
335           case 'X':    /* Get hex long integers. */
336               ScanRes = sscanf(*((*argv)++), "%lx",
337                                (unsigned long *)Parameters[(*ParamCount)++]);
338               break;
339           case 'O':    /* Get octal long integers. */
340               ScanRes = sscanf(*((*argv)++), "%lo",
341                                (unsigned long *)Parameters[(*ParamCount)++]);
342               break;
343           case 'f':    /* Get float number. */
344               ScanRes = sscanf(*((*argv)++), "%f",
345                                (float *)Parameters[(*ParamCount)++]);
346 	      break;
347           case 'F':    /* Get double float number. */
348               ScanRes = sscanf(*((*argv)++), "%lf",
349                                (double *)Parameters[(*ParamCount)++]);
350               break;
351           case 's':    /* It as a string. */
352               ScanRes = 1;    /* Allways O.K. */
353 	      *(char **)Parameters[(*ParamCount)++] = *((*argv)++);
354               break;
355           case '*':    /* Get few parameters into one: */
356               ScanRes = GAGetMultiParmeters(Parameters, ParamCount,
357                                             &CtrlStrCopy[i], argv_end, argv);
358               if ((ScanRes == 0) && (CtrlStrCopy[i] == '!')) {
359                   GAErrorToken = Option;
360                   return CMD_ERR_WildEmpty;
361               }
362               break;
363           default:
364               ScanRes = 0;    /* Make optimizer warning silent. */
365         }
366         /* If reading fails and this number is a must (!) then error: */
367         if ((ScanRes == 0) && (CtrlStrCopy[i] == '!')) {
368             GAErrorToken = Option;
369             return CMD_ERR_NumRead;
370         }
371         if (CtrlStrCopy[i + 1] != '*') {
372             i += 2;    /* Skip to next parameter (if any). */
373         } else
374             i += 3;    /* Skip the '*' also! */
375     }
376 
377     return ARG_OK;
378 }
379 
380 /***************************************************************************
381  Routine to get a few parameters into one pointer such that the returned
382  pointer actually points on a block of pointers to the parameters...
383  For example *F means a pointer to pointers on floats.
384  Returns number of parameters actually read.
385  This routine assumes that all pointers (on any kind of scalar) has the
386  same size (and the union below is totally ovelapped bteween dif. arrays)
387 ***************************************************************************/
388 static int
GAGetMultiParmeters(void * Parameters[],int * ParamCount,char * CtrlStrCopy,char ** argv_end,char *** argv)389 GAGetMultiParmeters(void *Parameters[],
390                     int *ParamCount,
391                     char *CtrlStrCopy,
392                     char **argv_end,
393                     char ***argv) {
394 
395     int i = 0, ScanRes, NumOfPrm = 0;
396     void **Pmain, **Ptemp;
397     union TmpArray {    /* Save here the temporary data before copying it to */
398         void *VoidArray[MAX_PARAM];    /* the returned pointer block. */
399         int *IntArray[MAX_PARAM];
400         long *LngArray[MAX_PARAM];
401         float *FltArray[MAX_PARAM];
402         double *DblArray[MAX_PARAM];
403         char *ChrArray[MAX_PARAM];
404     } TmpArray;
405 
406     do {
407         switch (CtrlStrCopy[2]) { /* CtrlStr == '!*?' or '%*?' where ? is. */
408           case 'd':    /* Format to read the parameters: */
409               TmpArray.IntArray[NumOfPrm] = xmalloc(sizeof(int));
410               ScanRes = sscanf(*((*argv)++), "%d",
411                                (int *)TmpArray.IntArray[NumOfPrm++]);
412               break;
413           case 'u':
414               TmpArray.IntArray[NumOfPrm] = xmalloc(sizeof(int));
415               ScanRes = sscanf(*((*argv)++), "%u",
416                                (unsigned int *)TmpArray.IntArray[NumOfPrm++]);
417               break;
418           case 'o':
419               TmpArray.IntArray[NumOfPrm] = xmalloc(sizeof(int));
420               ScanRes = sscanf(*((*argv)++), "%o",
421                                (unsigned int *)TmpArray.IntArray[NumOfPrm++]);
422               break;
423           case 'x':
424               TmpArray.IntArray[NumOfPrm] = xmalloc(sizeof(int));
425               ScanRes = sscanf(*((*argv)++), "%x",
426                                (unsigned int *)TmpArray.IntArray[NumOfPrm++]);
427               break;
428           case 'D':
429               TmpArray.LngArray[NumOfPrm] = xmalloc(sizeof(long));
430               ScanRes = sscanf(*((*argv)++), "%ld",
431                                (long *)TmpArray.IntArray[NumOfPrm++]);
432               break;
433           case 'U':
434               TmpArray.LngArray[NumOfPrm] = xmalloc(sizeof(long));
435               ScanRes = sscanf(*((*argv)++), "%lu",
436                                (unsigned long *)TmpArray.
437                                IntArray[NumOfPrm++]);
438               break;
439           case 'O':
440               TmpArray.LngArray[NumOfPrm] = xmalloc(sizeof(long));
441               ScanRes = sscanf(*((*argv)++), "%lo",
442                                (unsigned long *)TmpArray.
443                                IntArray[NumOfPrm++]);
444               break;
445           case 'X':
446               TmpArray.LngArray[NumOfPrm] = xmalloc(sizeof(long));
447               ScanRes = sscanf(*((*argv)++), "%lx",
448                                (unsigned long *)TmpArray.
449                                IntArray[NumOfPrm++]);
450               break;
451           case 'f':
452               TmpArray.FltArray[NumOfPrm] = xmalloc(sizeof(float));
453               ScanRes = sscanf(*((*argv)++), "%f",
454 			       // cppcheck-suppress invalidPointerCast
455                                (float *)TmpArray.LngArray[NumOfPrm++]);
456               break;
457           case 'F':
458               TmpArray.DblArray[NumOfPrm] = xmalloc(sizeof(double));
459               ScanRes = sscanf(*((*argv)++), "%lf",
460 			       // cppcheck-suppress invalidPointerCast
461                                (double *)TmpArray.LngArray[NumOfPrm++]);
462               break;
463           case 's':
464               while ((*argv < argv_end) && ((**argv)[0] != '-')) {
465                   TmpArray.ChrArray[NumOfPrm++] = *((*argv)++);
466               }
467               ScanRes = 0;    /* Force quit from do - loop. */
468               NumOfPrm++;    /* Updated again immediately after loop! */
469               (*argv)++;    /* "" */
470               break;
471           default:
472               ScanRes = 0;    /* Make optimizer warning silent. */
473         }
474     }
475     while (ScanRes == 1);    /* Exactly one parameter was read. */
476     (*argv)--;
477     NumOfPrm--;
478 
479     /* Now allocate the block with the exact size, and set it: */
480     Ptemp = Pmain = xmalloc((unsigned)(NumOfPrm + 1) * sizeof(void *));
481     /* And here we use the assumption that all pointers are the same: */
482     for (i = 0; i < NumOfPrm; i++)
483         *Ptemp++ = TmpArray.VoidArray[i];
484     *Ptemp = NULL;    /* Close the block with NULL pointer. */
485 
486     /* That it save the number of parameters read as first parameter to
487      * return and the pointer to the block as second, and return: */
488     *(int *)Parameters[(*ParamCount)++] = NumOfPrm;
489     *(void ***)Parameters[(*ParamCount)++] = Pmain;
490     /* free(Pmain); -- can not free here as caller needs to access memory */
491     return NumOfPrm;
492 }
493 
494 /***************************************************************************
495  Routine to scan the CtrlStr, up to Max and count the number of parameters
496  to that point:
497  1. Each option is counted as one parameter - boolean variable (int)
498  2. Within an option, each %? or !? is counted once - pointer to something
499  3. Within an option, %*? or !*? is counted twice - one for item count
500  and one for pointer to block pointers.
501  Note ALL variables are passed by address and so of fixed size (address).
502 ***************************************************************************/
503 static void
GASetParamCount(char * CtrlStr,int Max,int * ParamCount)504 GASetParamCount(char *CtrlStr,
505                 int Max,
506                 int *ParamCount) {
507     int i;
508 
509     *ParamCount = 0;
510     for (i = 0; i < Max; i++)
511         if (ISCTRLCHAR(CtrlStr[i])) {
512             if (CtrlStr[i + 1] == '*')
513                 *ParamCount += 2;
514             else
515                 (*ParamCount)++;
516         }
517 }
518 
519 /***************************************************************************
520  Routine to check if more option (i.e. first char == '-') exists in the
521  given list argc, argv:
522 ***************************************************************************/
523 static bool
GAOptionExists(char ** argv_end,char ** argv)524 GAOptionExists(char **argv_end,
525                char **argv) {
526 
527     while (argv < argv_end)
528         if ((*argv++)[0] == '-')
529             return true;
530     return false;
531 }
532 
533 /***************************************************************************
534  Routine to print some error messages, for this module:
535 ***************************************************************************/
536 void
GAPrintErrMsg(int Error)537 GAPrintErrMsg(int Error) {
538 
539     fprintf(stderr, "Error in command line parsing - ");
540     switch (Error) {
541       case 0:;
542           fprintf(stderr, "Undefined error");
543           break;
544       case CMD_ERR_NotAnOpt:
545           fprintf(stderr, "None option Found");
546           break;
547       case CMD_ERR_NoSuchOpt:
548           fprintf(stderr, "Undefined option Found");
549           break;
550       case CMD_ERR_WildEmpty:
551           fprintf(stderr, "Empty input for '!*?' seq.");
552           break;
553       case CMD_ERR_NumRead:
554           fprintf(stderr, "Failed on reading number");
555           break;
556       case CMD_ERR_AllSatis:
557           fprintf(stderr, "Fail to satisfy");
558           break;
559     }
560     fprintf(stderr, " - '%s'.\n", GAErrorToken);
561 }
562 
563 /***************************************************************************
564  Routine to print correct format of command line allowed:
565 ***************************************************************************/
566 void
GAPrintHowTo(char * CtrlStr)567 GAPrintHowTo(char *CtrlStr) {
568 
569     int i = 0;
570     bool SpaceFlag;
571 
572     fprintf(stderr, "Usage: ");
573     /* Print program name - first word in ctrl. str. (optional): */
574     while (!(ISSPACE(CtrlStr[i])) && (!ISCTRLCHAR(CtrlStr[i + 1])))
575         fprintf(stderr, "%c", CtrlStr[i++]);
576 
577     while (i < (int)strlen(CtrlStr)) {
578 	// cppcheck-suppress arrayIndexThenCheck
579         while ((ISSPACE(CtrlStr[i])) && (i < (int)strlen(CtrlStr)))
580             i++;
581         switch (CtrlStr[i + 1]) {
582           case '%':
583               fprintf(stderr, " [-%c", CtrlStr[i++]);
584               i += 2;    /* Skip the '%-' or '!- after the char! */
585               SpaceFlag = true;
586               while (!ISCTRLCHAR(CtrlStr[i]) && (i < (int)strlen(CtrlStr)) &&
587                      (!ISSPACE(CtrlStr[i])))
588                   if (SpaceFlag) {
589                       if (CtrlStr[i++] == SPACE_CHAR)
590                           fprintf(stderr, " ");
591                       else
592                           fprintf(stderr, " %c", CtrlStr[i - 1]);
593                       SpaceFlag = false;
594                   } else if (CtrlStr[i++] == SPACE_CHAR)
595                       fprintf(stderr, " ");
596                   else
597                       fprintf(stderr, "%c", CtrlStr[i - 1]);
598               while (!ISSPACE(CtrlStr[i]) && (i < (int)strlen(CtrlStr))) {
599                   if (CtrlStr[i] == '*')
600                       fprintf(stderr, "...");
601                   i++;    /* Skip the rest of it. */
602               }
603               fprintf(stderr, "]");
604               break;
605           case '!':
606               fprintf(stderr, " -%c", CtrlStr[i++]);
607               i += 2;    /* Skip the '%-' or '!- after the char! */
608               SpaceFlag = true;
609               while (!ISCTRLCHAR(CtrlStr[i]) && (i < (int)strlen(CtrlStr)) &&
610                      (!ISSPACE(CtrlStr[i])))
611                   if (SpaceFlag) {
612                       if (CtrlStr[i++] == SPACE_CHAR)
613                           fprintf(stderr, " ");
614                       else
615                           fprintf(stderr, " %c", CtrlStr[i - 1]);
616                       SpaceFlag = false;
617                   } else if (CtrlStr[i++] == SPACE_CHAR)
618                       fprintf(stderr, " ");
619                   else
620                       fprintf(stderr, "%c", CtrlStr[i - 1]);
621               while (!ISSPACE(CtrlStr[i]) && (i < (int)strlen(CtrlStr))) {
622                   if (CtrlStr[i] == '*')
623                       fprintf(stderr, "...");
624                   i++;    /* Skip the rest of it. */
625               }
626               break;
627           default:    /* Not checked, but must be last one! */
628               fprintf(stderr, " ");
629               while (!ISSPACE(CtrlStr[i]) && (i < (int)strlen(CtrlStr)) &&
630                      !ISCTRLCHAR(CtrlStr[i]))
631                   fprintf(stderr, "%c", CtrlStr[i++]);
632               fprintf(stderr, "\n");
633               return;
634         }
635     }
636     fprintf(stderr, "\n");
637 }
638 
639 /* end */
640