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