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