xref: /freebsd/contrib/ntp/sntp/libopts/enum.c (revision a466cc55373fc3cf86837f09da729535b57e69a1)
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
enum_err(tOptions * pOpts,tOptDesc * pOD,char const * const * paz_names,int name_ct)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
find_name(char const * name,tOptions * pOpts,tOptDesc * pOD,char const * const * paz_names,unsigned int name_ct)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 *
optionKeywordName(tOptDesc * pOD,unsigned int enum_val)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
optionEnumerationVal(tOptions * pOpts,tOptDesc * pOD,char const * const * paz_names,unsigned int name_ct)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
set_memb_shell(tOptions * pOpts,tOptDesc * pOD,char const * const * paz_names,unsigned int name_ct)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
set_memb_names(tOptions * opts,tOptDesc * od,char const * const * nm_list,unsigned int nm_ct)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
check_membership_start(tOptDesc * od,char const ** argp,bool * invert)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
find_member_bit(tOptions * opts,tOptDesc * od,char const * pz,int len,char const * const * nm_list,unsigned int nm_ct)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 *
optionMemberList(tOptDesc * od)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
optionSetMembers(tOptions * opts,tOptDesc * od,char const * const * nm_list,unsigned int nm_ct)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