xref: /freebsd/contrib/ntp/sntp/libopts/enum.c (revision 5ca8e32633c4ffbbcd6762e5888b6a4ba0708c6c)
1 
2 /**
3  * \file enumeration.c
4  *
5  *  Handle options with enumeration names and bit mask bit names
6  *  for their arguments.
7  *
8  * @addtogroup autoopts
9  * @{
10  */
11 /*
12  *  This routine will run run-on options through a pager so the
13  *  user may examine, print or edit them at their leisure.
14  *
15  *  This file is part of AutoOpts, a companion to AutoGen.
16  *  AutoOpts is free software.
17  *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
18  *
19  *  AutoOpts is available under any one of two licenses.  The license
20  *  in use must be one of these two and the choice is under the control
21  *  of the user of the license.
22  *
23  *   The GNU Lesser General Public License, version 3 or later
24  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
25  *
26  *   The Modified Berkeley Software Distribution License
27  *      See the file "COPYING.mbsd"
28  *
29  *  These files have the following sha256 sums:
30  *
31  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
32  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
33  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
34  */
35 
36 static void
37 enum_err(tOptions * pOpts, tOptDesc * pOD,
38          char const * const * paz_names, int name_ct)
39 {
40     size_t max_len = 0;
41     size_t ttl_len = 0;
42     int    ct_down = name_ct;
43     int    hidden  = 0;
44 
45     /*
46      *  A real "pOpts" pointer means someone messed up.  Give a real error.
47      */
48     if (pOpts > OPTPROC_EMIT_LIMIT)
49         fprintf(option_usage_fp, pz_enum_err_fmt, pOpts->pzProgName,
50                 pOD->optArg.argString, pOD->pz_Name);
51 
52     fprintf(option_usage_fp, zValidKeys, pOD->pz_Name);
53 
54     /*
55      *  If the first name starts with this funny character, then we have
56      *  a first value with an unspellable name.  You cannot specify it.
57      *  So, we don't list it either.
58      */
59     if (**paz_names == 0x7F) {
60         paz_names++;
61         hidden  = 1;
62         ct_down = --name_ct;
63     }
64 
65     /*
66      *  Figure out the maximum length of any name, plus the total length
67      *  of all the names.
68      */
69     {
70         char const * const * paz = paz_names;
71 
72         do  {
73             size_t len = strlen(*(paz++)) + 1;
74             if (len > max_len)
75                 max_len = len;
76             ttl_len += len;
77         } while (--ct_down > 0);
78 
79         ct_down = name_ct;
80     }
81 
82     /*
83      *  IF any one entry is about 1/2 line or longer, print one per line
84      */
85     if (max_len > 35) {
86         do  {
87             fprintf(option_usage_fp, ENUM_ERR_LINE, *(paz_names++));
88         } while (--ct_down > 0);
89     }
90 
91     /*
92      *  ELSE IF they all fit on one line, then do so.
93      */
94     else if (ttl_len < 76) {
95         fputc(' ', option_usage_fp);
96         do  {
97             fputc(' ', option_usage_fp);
98             fputs(*(paz_names++), option_usage_fp);
99         } while (--ct_down > 0);
100         fputc(NL, option_usage_fp);
101     }
102 
103     /*
104      *  Otherwise, columnize the output
105      */
106     else {
107         unsigned int ent_no = 0;
108         char fmt[16];  /* format for all-but-last entries on a line */
109 
110         if (snprintf(fmt, 16, ENUM_ERR_WIDTH, (int)max_len) >= 16)
111             option_exits(EXIT_FAILURE);
112         max_len = 78 / max_len; /* max_len is now max entries on a line */
113         fputs(TWO_SPACES_STR, option_usage_fp);
114 
115         /*
116          *  Loop through all but the last entry
117          */
118         ct_down = name_ct;
119         while (--ct_down > 0) {
120             if (++ent_no == max_len) {
121                 /*
122                  *  Last entry on a line.  Start next line, too.
123                  */
124                 fprintf(option_usage_fp, NLSTR_SPACE_FMT, *(paz_names++));
125                 ent_no = 0;
126             }
127 
128             else
129                 fprintf(option_usage_fp, fmt, *(paz_names++) );
130         }
131         fprintf(option_usage_fp, NLSTR_FMT, *paz_names);
132     }
133 
134     if (pOpts > OPTPROC_EMIT_LIMIT) {
135         fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden);
136 
137         (*(pOpts->pUsageProc))(pOpts, EXIT_FAILURE);
138         /* NOTREACHED */
139     }
140 
141     if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_MEMBERSHIP) {
142         fprintf(option_usage_fp, zLowerBits, name_ct);
143         fputs(zSetMemberSettings, option_usage_fp);
144     } else {
145         fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden);
146     }
147 }
148 
149 /**
150  * Convert a name or number into a binary number.
151  * "~0" and "-1" will be converted to the largest value in the enumeration.
152  *
153  * @param name       the keyword name (number) to convert
154  * @param pOpts      the program's option descriptor
155  * @param pOD        the option descriptor for this option
156  * @param paz_names  the list of keywords for this option
157  * @param name_ct    the count of keywords
158  */
159 static uintptr_t
160 find_name(char const * name, tOptions * pOpts, tOptDesc * pOD,
161           char const * const *  paz_names, unsigned int name_ct)
162 {
163     /*
164      *  Return the matching index as a pointer sized integer.
165      *  The result gets stashed in a char * pointer.
166      */
167     uintptr_t   res = name_ct;
168     size_t      len = strlen((char *)name);
169     uintptr_t   idx;
170 
171     if (IS_DEC_DIGIT_CHAR(*name)) {
172         char * pz = VOIDP(name);
173         unsigned long val = strtoul(pz, &pz, 0);
174         if ((*pz == NUL) && (val < name_ct))
175             return (uintptr_t)val;
176         pz_enum_err_fmt = znum_too_large;
177         option_usage_fp = stderr;
178         enum_err(pOpts, pOD, paz_names, (int)name_ct);
179         return name_ct;
180     }
181 
182     if (IS_INVERSION_CHAR(*name) && (name[2] == NUL)) {
183         if (  ((name[0] == '~') && (name[1] == '0'))
184            || ((name[0] == '-') && (name[1] == '1')))
185         return (uintptr_t)(name_ct - 1);
186         goto oops;
187     }
188 
189     /*
190      *  Look for an exact match, but remember any partial matches.
191      *  Multiple partial matches means we have an ambiguous match.
192      */
193     for (idx = 0; idx < name_ct; idx++) {
194         if (strncmp((char *)paz_names[idx], (char *)name, len) == 0) {
195             if (paz_names[idx][len] == NUL)
196                 return idx;  /* full match */
197 
198             if (res == name_ct)
199                 res = idx; /* save partial match */
200             else
201                 res = (uintptr_t)~0;  /* may yet find full match */
202         }
203     }
204 
205     if (res < name_ct)
206         return res; /* partial match */
207 
208  oops:
209 
210     pz_enum_err_fmt = (res == name_ct) ? zNoKey : zambiguous_key;
211     option_usage_fp = stderr;
212     enum_err(pOpts, pOD, paz_names, (int)name_ct);
213     return name_ct;
214 }
215 
216 
217 /*=export_func  optionKeywordName
218  * what:  Convert between enumeration values and strings
219  * private:
220  *
221  * arg:   tOptDesc *,    pOD,       enumeration option description
222  * arg:   unsigned int,  enum_val,  the enumeration value to map
223  *
224  * ret_type:  char const *
225  * ret_desc:  the enumeration name from const memory
226  *
227  * doc:   This converts an enumeration value into the matching string.
228 =*/
229 char const *
230 optionKeywordName(tOptDesc * pOD, unsigned int enum_val)
231 {
232     tOptDesc od = { 0 };
233     od.optArg.argEnum = enum_val;
234 
235     (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, &od );
236     return od.optArg.argString;
237 }
238 
239 
240 /*=export_func  optionEnumerationVal
241  * what:  Convert from a string to an enumeration value
242  * private:
243  *
244  * arg:   tOptions *,    pOpts,     the program options descriptor
245  * arg:   tOptDesc *,    pOD,       enumeration option description
246  * arg:   char const * const *,  paz_names, list of enumeration names
247  * arg:   unsigned int,  name_ct,   number of names in list
248  *
249  * ret_type:  uintptr_t
250  * ret_desc:  the enumeration value
251  *
252  * doc:   This converts the optArg.argString string from the option description
253  *        into the index corresponding to an entry in the name list.
254  *        This will match the generated enumeration value.
255  *        Full matches are always accepted.  Partial matches are accepted
256  *        if there is only one partial match.
257 =*/
258 uintptr_t
259 optionEnumerationVal(tOptions * pOpts, tOptDesc * pOD,
260                      char const * const * paz_names, unsigned int name_ct)
261 {
262     uintptr_t res = 0UL;
263 
264     /*
265      *  IF the program option descriptor pointer is invalid,
266      *  then it is some sort of special request.
267      */
268     switch ((uintptr_t)pOpts) {
269     case (uintptr_t)OPTPROC_EMIT_USAGE:
270         /*
271          *  print the list of enumeration names.
272          */
273         enum_err(pOpts, pOD, paz_names, (int)name_ct);
274         break;
275 
276     case (uintptr_t)OPTPROC_EMIT_SHELL:
277     {
278         unsigned int ix = (unsigned int)pOD->optArg.argEnum;
279         /*
280          *  print the name string.
281          */
282         if (ix >= name_ct)
283             printf(INVALID_FMT, ix);
284         else
285             fputs(paz_names[ ix ], stdout);
286 
287         break;
288     }
289 
290     case (uintptr_t)OPTPROC_RETURN_VALNAME:
291     {
292         unsigned int ix = (unsigned int)pOD->optArg.argEnum;
293         /*
294          *  Replace the enumeration value with the name string.
295          */
296         if (ix >= name_ct)
297             return (uintptr_t)INVALID_STR;
298 
299         pOD->optArg.argString = paz_names[ix];
300         break;
301     }
302 
303     default:
304         if ((pOD->fOptState & OPTST_RESET) != 0)
305             break;
306 
307         res = find_name(pOD->optArg.argString, pOpts, pOD, paz_names, name_ct);
308 
309         if (pOD->fOptState & OPTST_ALLOC_ARG) {
310             AGFREE(pOD->optArg.argString);
311             pOD->fOptState &= ~OPTST_ALLOC_ARG;
312             pOD->optArg.argString = NULL;
313         }
314     }
315 
316     return res;
317 }
318 
319 static void
320 set_memb_shell(tOptions * pOpts, tOptDesc * pOD, char const * const * paz_names,
321                unsigned int name_ct)
322 {
323     /*
324      *  print the name string.
325      */
326     unsigned int ix =  0;
327     uintptr_t  bits = (uintptr_t)pOD->optCookie;
328     size_t     len  = 0;
329 
330     (void)pOpts;
331     bits &= ((uintptr_t)1 << (uintptr_t)name_ct) - (uintptr_t)1;
332 
333     while (bits != 0) {
334         if (bits & 1) {
335             if (len++ > 0) fputs(OR_STR, stdout);
336             fputs(paz_names[ix], stdout);
337         }
338         if (++ix >= name_ct) break;
339         bits >>= 1;
340     }
341 }
342 
343 static void
344 set_memb_names(tOptions * opts, tOptDesc * od, char const * const * nm_list,
345                unsigned int nm_ct)
346 {
347     char *     pz;
348     uintptr_t  mask = (1UL << (uintptr_t)nm_ct) - 1UL;
349     uintptr_t  bits = (uintptr_t)od->optCookie & mask;
350     unsigned int ix = 0;
351     size_t     len  = 1;
352 
353     /*
354      *  Replace the enumeration value with the name string.
355      *  First, determine the needed length, then allocate and fill in.
356      */
357     while (bits != 0) {
358         if (bits & 1)
359             len += strlen(nm_list[ix]) + PLUS_STR_LEN + 1;
360         if (++ix >= nm_ct) break;
361         bits >>= 1;
362     }
363 
364     od->optArg.argString = pz = AGALOC(len, "enum");
365     bits = (uintptr_t)od->optCookie & mask;
366     if (bits == 0) {
367         *pz = NUL;
368         return;
369     }
370 
371     for (ix = 0; ; ix++) {
372         size_t nln;
373         int    doit = bits & 1;
374 
375         bits >>= 1;
376         if (doit == 0)
377             continue;
378 
379         nln = strlen(nm_list[ix]);
380         memcpy(pz, nm_list[ix], nln);
381         pz += nln;
382         if (bits == 0)
383             break;
384         memcpy(pz, PLUS_STR, PLUS_STR_LEN);
385         pz += PLUS_STR_LEN;
386     }
387     *pz = NUL;
388     (void)opts;
389 }
390 
391 /**
392  * Check membership start conditions.  An equal character (@samp{=}) says to
393  * clear the result and not carry over any residual value.  A carat
394  * (@samp{^}), which may follow the equal character, says to invert the
395  * result.  The scanning pointer is advanced past these characters and any
396  * leading white space.  Invalid sequences are indicated by setting the
397  * scanning pointer to NULL.
398  *
399  * @param od      the set membership option description
400  * @param argp    a pointer to the string scanning pointer
401  * @param invert  a pointer to the boolean inversion indicator
402  *
403  * @returns either zero or the original value for the optCookie.
404  */
405 static uintptr_t
406 check_membership_start(tOptDesc * od, char const ** argp, bool * invert)
407 {
408     uintptr_t    res = (uintptr_t)od->optCookie;
409     char const * arg = SPN_WHITESPACE_CHARS(od->optArg.argString);
410     if ((arg == NULL) || (*arg == NUL))
411         goto member_start_fail;
412 
413     *invert = false;
414 
415     switch (*arg) {
416     case '=':
417         res = 0UL;
418         arg = SPN_WHITESPACE_CHARS(arg + 1);
419         switch (*arg) {
420         case '=': case ',':
421             goto member_start_fail;
422         case '^':
423             goto inversion;
424         default:
425             break;
426         }
427         break;
428 
429     case '^':
430     inversion:
431         *invert = true;
432         arg = SPN_WHITESPACE_CHARS(arg + 1);
433         if (*arg != ',')
434             break;
435         /* FALLTHROUGH */
436 
437     case ',':
438         goto member_start_fail;
439 
440     default:
441         break;
442     }
443 
444     *argp = arg;
445     return res;
446 
447 member_start_fail:
448     *argp = NULL;
449     return 0UL;
450 }
451 
452 /**
453  * convert a name to a bit.  Look up a name string to get a bit number
454  * and shift the value "1" left that number of bits.
455  *
456  * @param opts      program options descriptor
457  * @param od        the set membership option description
458  * @param pz        address of the start of the bit name
459  * @param nm_list   the list of names for this option
460  * @param nm_ct     the number of entries in this list
461  *
462  * @returns 0UL on error, other an unsigned long with the correct bit set.
463  */
464 static uintptr_t
465 find_member_bit(tOptions * opts, tOptDesc * od, char const * pz, int len,
466                 char const * const * nm_list, unsigned int nm_ct)
467 {
468     char nm_buf[ AO_NAME_SIZE ];
469 
470     memcpy(nm_buf, pz, len);
471     nm_buf[len] = NUL;
472 
473     {
474         unsigned int shift_ct = (unsigned int)
475             find_name(nm_buf, opts, od, nm_list, nm_ct);
476         if (shift_ct >= nm_ct)
477             return 0UL;
478 
479         return 1UL << shift_ct;
480     }
481 }
482 
483 /*=export_func  optionMemberList
484  * what:  Get the list of members of a bit mask set
485  *
486  * arg:   tOptDesc *,  od,   the set membership option description
487  *
488  * ret_type: char *
489  * ret_desc: the names of the set bits
490  *
491  * doc:   This converts the OPT_VALUE_name mask value to a allocated string.
492  *        It is the caller's responsibility to free the string.
493 =*/
494 char *
495 optionMemberList(tOptDesc * od)
496 {
497     uintptr_t    sv = od->optArg.argIntptr;
498     char * res;
499     (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od);
500     res = VOIDP(od->optArg.argString);
501     od->optArg.argIntptr = sv;
502     return res;
503 }
504 
505 /*=export_func  optionSetMembers
506  * what:  Convert between bit flag values and strings
507  * private:
508  *
509  * arg:   tOptions *,     opts,     the program options descriptor
510  * arg:   tOptDesc *,     od,       the set membership option description
511  * arg:   char const * const *,
512  *                       nm_list,  list of enumeration names
513  * arg:   unsigned int,  nm_ct,    number of names in list
514  *
515  * doc:   This converts the optArg.argString string from the option description
516  *        into the index corresponding to an entry in the name list.
517  *        This will match the generated enumeration value.
518  *        Full matches are always accepted.  Partial matches are accepted
519  *        if there is only one partial match.
520 =*/
521 void
522 optionSetMembers(tOptions * opts, tOptDesc * od,
523                  char const * const * nm_list, unsigned int nm_ct)
524 {
525     /*
526      *  IF the program option descriptor pointer is invalid,
527      *  then it is some sort of special request.
528      */
529     switch ((uintptr_t)opts) {
530     case (uintptr_t)OPTPROC_EMIT_USAGE:
531         enum_err(OPTPROC_EMIT_USAGE, od, nm_list, nm_ct);
532         return;
533 
534     case (uintptr_t)OPTPROC_EMIT_SHELL:
535         set_memb_shell(opts, od, nm_list, nm_ct);
536         return;
537 
538     case (uintptr_t)OPTPROC_RETURN_VALNAME:
539         set_memb_names(opts, od, nm_list, nm_ct);
540         return;
541 
542     default:
543         break;
544     }
545 
546     if ((od->fOptState & OPTST_RESET) != 0)
547         return;
548 
549     {
550         char const * arg;
551         bool         invert;
552         uintptr_t    res = check_membership_start(od, &arg, &invert);
553         if (arg == NULL)
554             goto fail_return;
555 
556         while (*arg != NUL) {
557             bool inv_val = false;
558             int  len;
559 
560             switch (*arg) {
561             case ',':
562                 arg = SPN_WHITESPACE_CHARS(arg+1);
563                 if ((*arg == ',') || (*arg == '|'))
564                     goto fail_return;
565                 continue;
566 
567             case '-':
568             case '!':
569                 inv_val = true;
570                 /* FALLTHROUGH */
571 
572             case '+':
573             case '|':
574                 arg = SPN_WHITESPACE_CHARS(arg+1);
575             }
576 
577             len = (int)(BRK_SET_SEPARATOR_CHARS(arg) - arg);
578             if (len == 0)
579                 break;
580 
581             if ((len == 3) && (strncmp(arg, zAll, 3) == 0)) {
582                 if (inv_val)
583                      res = 0;
584                 else res = ~0UL;
585             }
586             else if ((len == 4) && (strncmp(arg, zNone, 4) == 0)) {
587                 if (! inv_val)
588                     res = 0;
589             }
590             else do {
591                 char *    pz;
592                 uintptr_t bit = strtoul(arg, &pz, 0);
593 
594                 if (pz != arg + len) {
595                     bit = find_member_bit(opts, od, pz, len, nm_list, nm_ct);
596                     if (bit == 0UL)
597                         goto fail_return;
598                 }
599                 if (inv_val)
600                      res &= ~bit;
601                 else res |= bit;
602             } while (false);
603 
604             arg = SPN_WHITESPACE_CHARS(arg + len);
605         }
606 
607         if (invert)
608             res ^= ~0UL;
609 
610         if (nm_ct < (8 * sizeof(uintptr_t)))
611             res &= (1UL << nm_ct) - 1UL;
612 
613         od->optCookie = VOIDP(res);
614     }
615     return;
616 
617 fail_return:
618     od->optCookie = VOIDP(0);
619 }
620 
621 /** @}
622  *
623  * Local Variables:
624  * mode: C
625  * c-file-style: "stroustrup"
626  * indent-tabs-mode: nil
627  * End:
628  * end of autoopts/enum.c */
629