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