xref: /freebsd/contrib/ntp/sntp/libopts/putshell.c (revision 4e8d558c)
1 
2 /**
3  * \file putshell.c
4  *
5  *  This module will interpret the options set in the tOptions
6  *  structure and print them to standard out in a fashion that
7  *  will allow them to be interpreted by the Bourne or Korn shells.
8  *
9  * @addtogroup autoopts
10  * @{
11  */
12 /*
13  *  This file is part of AutoOpts, a companion to AutoGen.
14  *  AutoOpts is free software.
15  *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
16  *
17  *  AutoOpts is available under any one of two licenses.  The license
18  *  in use must be one of these two and the choice is under the control
19  *  of the user of the license.
20  *
21  *   The GNU Lesser General Public License, version 3 or later
22  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
23  *
24  *   The Modified Berkeley Software Distribution License
25  *      See the file "COPYING.mbsd"
26  *
27  *  These files have the following sha256 sums:
28  *
29  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
30  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
31  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
32  */
33 
34 /**
35  * Count the number of bytes required to represent a string as a
36  * compilable string.
37  *
38  * @param[in] scan    the text to be rewritten as a C program text string.
39  * @param[in] nl_len  the number of bytes used for each embedded newline.
40  *
41  * @returns the count, including the terminating NUL byte.
42  */
43 static size_t
44 string_size(char const * scan, size_t nl_len)
45 {
46     /*
47      *  Start by counting the start and end quotes, plus the NUL.
48      */
49     size_t res_ln = 3;
50 
51     for (;;) {
52         char ch = *(scan++);
53         if ((ch >= ' ') && (ch <= '~')) {
54 
55             /*
56              * a backslash allowance for double quotes and baskslashes
57              */
58             res_ln += ((ch == '"') || (ch == '\\')) ? 2 : 1;
59         }
60 
61         /*
62          *  When not a normal character, then count the characters
63          *  required to represent whatever it is.
64          */
65         else switch (ch) {
66         case NUL:
67             return res_ln;
68 
69         case NL:
70             res_ln += nl_len;
71             break;
72 
73         case HT:
74         case BEL:
75         case BS:
76         case FF:
77         case CR:
78         case VT:
79             res_ln += 2;
80             break;
81 
82         default:
83             res_ln += 4; /* text len for \xNN */
84         }
85     }
86 }
87 
88 /*=export_func  optionQuoteString
89  * private:
90  *
91  * what:  Print a string as quoted text suitable for a C compiler.
92  * arg:   + char const * + text  + a block of text to quote +
93  * arg:   + char const * + nl    + line splice text         +
94  *
95  * ret_type:  char const *
96  * ret_desc:  the allocated input string as a quoted string
97  *
98  * doc:
99  *  This is for internal use by autogen and autoopts.
100  *  It takes an input string and produces text the C compiler can process
101  *  to produce an exact copy of the original string.
102  *  The caller must deallocate the result.  Standard C strings and
103  *  K&R strings are distinguished by the "nl" string.
104 =*/
105 char const *
106 optionQuoteString(char const * text, char const * nl)
107 {
108     size_t   nl_len = strlen(nl);
109     size_t   out_sz = string_size(text, nl_len);
110     char *   out;
111     char *   res    = out = AGALOC(out_sz, "quot str");
112 
113     *(out++) = '"';
114 
115     for (;;) {
116         unsigned char ch = (unsigned char)*text;
117         if ((ch >= ' ') && (ch <= '~')) {
118             if ((ch == '"') || (ch == '\\'))
119                 /*
120                  *  We must escape these characters in the output string
121                  */
122                 *(out++) = '\\';
123             *(out++) = (char)ch;
124 
125         } else switch (ch) {
126 #       define   add_esc_ch(_ch)  { *(out++) = '\\'; *(out++) = (_ch); }
127         case BEL: add_esc_ch('a'); break;
128         case BS:  add_esc_ch('b'); break;
129         case HT:  add_esc_ch('t'); break;
130         case VT:  add_esc_ch('v'); break;
131         case FF:  add_esc_ch('f'); break;
132         case CR:  add_esc_ch('r'); break;
133 
134         case LF:
135             /*
136              *  Place contiguous new-lines on a single line.
137              *  The current character is a NL, check the next one.
138              */
139             while (*++text == NL)
140                 add_esc_ch('n');
141 
142             /*
143              *  Insert a splice before starting next line
144              */
145             if (*text != NUL) {
146                 memcpy(out, nl, nl_len);
147                 out += nl_len;
148 
149                 continue; /* text is already at the next character */
150             }
151 
152             add_esc_ch('n');
153             /* FALLTHROUGH */
154 
155         case NUL:
156             /*
157              *  End of string.  Terminate the quoted output.  If necessary,
158              *  deallocate the text string.  Return the scan resumption point.
159              */
160             *(out++) = '"';
161             *(out++) = NUL;
162 #ifndef NDEBUG
163             if ((size_t)(out - res) > out_sz) {
164                 fputs(misguess_len, stderr);
165                 option_exits(EXIT_FAILURE);
166             }
167 #endif
168             return res;
169 
170         default:
171             /*
172              *  sprintf is safe here, because we already computed
173              *  the amount of space we will be using.  Assertion is above.
174              */
175             out += sprintf(out, MK_STR_OCT_FMT, ch);
176         }
177 
178         text++;
179 #       undef add_esc_ch
180     }
181 }
182 
183 /**
184  *  Print out escaped apostorophes.
185  *
186  *  @param[in] str  the apostrophies to print
187  */
188 static char const *
189 print_quoted_apostrophes(char const * str)
190 {
191     while (*str == APOSTROPHE) {
192         fputs(QUOT_APOS, stdout);
193         str++;
194     }
195     return str;
196 }
197 
198 /**
199  *  Print a single quote (apostrophe quoted) string.
200  *  Other than somersaults for apostrophes, nothing else needs quoting.
201  *
202  *  @param[in] str  the string to print
203  */
204 static void
205 print_quot_str(char const * str)
206 {
207     /*
208      *  Handle empty strings to make the rest of the logic simpler.
209      */
210     if ((str == NULL) || (*str == NUL)) {
211         fputs(EMPTY_ARG, stdout);
212         return;
213     }
214 
215     /*
216      *  Emit any single quotes/apostrophes at the start of the string and
217      *  bail if that is all we need to do.
218      */
219     str = print_quoted_apostrophes(str);
220     if (*str == NUL)
221         return;
222 
223     /*
224      *  Start the single quote string
225      */
226     fputc(APOSTROPHE, stdout);
227     for (;;) {
228         char const * pz = strchr(str, APOSTROPHE);
229         if (pz == NULL)
230             break;
231 
232         /*
233          *  Emit the string up to the single quote (apostrophe) we just found.
234          */
235         (void)fwrite(str, (size_t)(pz - str), (size_t)1, stdout);
236 
237         /*
238          * Close the current string, emit the apostrophes and re-open the
239          * string (IFF there is more text to print).
240          */
241         fputc(APOSTROPHE, stdout);
242         str = print_quoted_apostrophes(pz);
243         if (*str == NUL)
244             return;
245 
246         fputc(APOSTROPHE, stdout);
247     }
248 
249     /*
250      *  If we broke out of the loop, we must still emit the remaining text
251      *  and then close the single quote string.
252      */
253     fputs(str, stdout);
254     fputc(APOSTROPHE, stdout);
255 }
256 
257 static void
258 print_enumeration(tOptions * pOpts, tOptDesc * pOD)
259 {
260     uintptr_t e_val = pOD->optArg.argEnum;
261     printf(OPT_VAL_FMT, pOpts->pzPROGNAME, pOD->pz_NAME);
262 
263     /*
264      *  Convert value to string, print that and restore numeric value.
265      */
266     (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD);
267     printf(QUOT_ARG_FMT, pOD->optArg.argString);
268     if (pOD->fOptState & OPTST_ALLOC_ARG)
269         AGFREE(pOD->optArg.argString);
270     pOD->optArg.argEnum = e_val;
271 
272     printf(OPT_END_FMT, pOpts->pzPROGNAME, pOD->pz_NAME);
273 }
274 
275 static void
276 print_membership(tOptions * pOpts, tOptDesc * pOD)
277 {
278     char const * svstr = pOD->optArg.argString;
279     char const * pz;
280     uintptr_t val = 1;
281     printf(zOptNumFmt, pOpts->pzPROGNAME, pOD->pz_NAME,
282            (int)(uintptr_t)(pOD->optCookie));
283     pOD->optCookie = VOIDP(~0UL);
284     (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD);
285 
286     pz = pOD->optArg.argString;
287     while (*pz != NUL) {
288         printf("readonly %s_", pOD->pz_NAME);
289         pz = SPN_PLUS_N_SPACE_CHARS(pz);
290 
291         for (;;) {
292             int ch = *(pz++);
293             if (IS_LOWER_CASE_CHAR(ch))   fputc(toupper(ch), stdout);
294             else if (IS_UPPER_CASE_CHAR(ch))   fputc(ch, stdout);
295             else if (IS_PLUS_N_SPACE_CHAR(ch)) goto name_done;
296             else if (ch == NUL)        { pz--; goto name_done; }
297             else fputc('_', stdout);
298         } name_done:;
299         printf(SHOW_VAL_FMT, (unsigned long)val);
300         val <<= 1;
301     }
302 
303     AGFREE(pOD->optArg.argString);
304     pOD->optArg.argString = svstr;
305 }
306 
307 static void
308 print_stacked_arg(tOptions * pOpts, tOptDesc * pOD)
309 {
310     tArgList *      pAL = (tArgList *)pOD->optCookie;
311     char const **   ppz = pAL->apzArgs;
312     int             ct  = pAL->useCt;
313 
314     printf(zOptCookieCt, pOpts->pzPROGNAME, pOD->pz_NAME, ct);
315 
316     while (--ct >= 0) {
317         printf(ARG_BY_NUM_FMT, pOpts->pzPROGNAME, pOD->pz_NAME,
318                pAL->useCt - ct);
319         print_quot_str(*(ppz++));
320         printf(EXPORT_ARG_FMT, pOpts->pzPROGNAME, pOD->pz_NAME,
321                pAL->useCt - ct);
322     }
323 }
324 
325 /**
326  * emit the arguments as readily parsed text.
327  * The program options are set by emitting the shell "set" command.
328  *
329  * @param[in] opts  the program options structure
330  */
331 static void
332 print_reordering(tOptions * opts)
333 {
334     unsigned int ix;
335 
336     fputs(set_dash, stdout);
337 
338     for (ix = opts->curOptIdx;
339          ix < opts->origArgCt;
340          ix++) {
341         fputc(' ', stdout);
342         print_quot_str(opts->origArgVect[ ix ]);
343     }
344     fputs(init_optct, stdout);
345 }
346 
347 /*=export_func  optionPutShell
348  * what:  write a portable shell script to parse options
349  * private:
350  * arg:   tOptions *, pOpts, the program options descriptor
351  * doc:   This routine will emit portable shell script text for parsing
352  *        the options described in the option definitions.
353 =*/
354 void
355 optionPutShell(tOptions * pOpts)
356 {
357     int  optIx = 0;
358 
359     printf(zOptCtFmt, pOpts->curOptIdx-1);
360 
361     do  {
362         tOptDesc * pOD = pOpts->pOptDesc + optIx;
363 
364         if ((pOD->fOptState & OPTST_NO_OUTPUT_MASK) != 0)
365             continue;
366 
367         /*
368          *  Equivalence classes are hard to deal with.  Where the
369          *  option data wind up kind of squishes around.  For the purposes
370          *  of emitting shell state, they are not recommended, but we'll
371          *  do something.  I guess we'll emit the equivalenced-to option
372          *  at the point in time when the base option is found.
373          */
374         if (pOD->optEquivIndex != NO_EQUIVALENT)
375             continue; /* equivalence to a different option */
376 
377         /*
378          *  Equivalenced to a different option.  Process the current option
379          *  as the equivalenced-to option.  Keep the persistent state bits,
380          *  but copy over the set-state bits.
381          */
382         if (pOD->optActualIndex != optIx) {
383             tOptDesc * p  = pOpts->pOptDesc + pOD->optActualIndex;
384             p->optArg     = pOD->optArg;
385             p->fOptState &= OPTST_PERSISTENT_MASK;
386             p->fOptState |= pOD->fOptState & ~OPTST_PERSISTENT_MASK;
387             printf(zEquivMode, pOpts->pzPROGNAME, pOD->pz_NAME, p->pz_NAME);
388             pOD = p;
389         }
390 
391         /*
392          *  If the argument type is a set membership bitmask, then we always
393          *  emit the thing.  We do this because it will always have some sort
394          *  of bitmask value and we need to emit the bit values.
395          */
396         if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_MEMBERSHIP) {
397             print_membership(pOpts, pOD);
398             continue;
399         }
400 
401         /*
402          *  IF the option was either specified or it wakes up enabled,
403          *  then we will emit information.  Otherwise, skip it.
404          *  The idea is that if someone defines an option to initialize
405          *  enabled, we should tell our shell script that it is enabled.
406          */
407         if (UNUSED_OPT(pOD) && DISABLED_OPT(pOD))
408             continue;
409 
410         /*
411          *  Handle stacked arguments
412          */
413         if (  (pOD->fOptState & OPTST_STACKED)
414            && (pOD->optCookie != NULL) )  {
415             print_stacked_arg(pOpts, pOD);
416             continue;
417         }
418 
419         /*
420          *  If the argument has been disabled,
421          *  Then set its value to the disablement string
422          */
423         if ((pOD->fOptState & OPTST_DISABLED) != 0) {
424             printf(zOptDisabl, pOpts->pzPROGNAME, pOD->pz_NAME,
425                    (pOD->pz_DisablePfx != NULL)
426                    ? pOD->pz_DisablePfx : "false");
427             continue;
428         }
429 
430         /*
431          *  If the argument type is numeric, the last arg pointer
432          *  is really the VALUE of the string that was pointed to.
433          */
434         if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_NUMERIC) {
435             printf(zOptNumFmt, pOpts->pzPROGNAME, pOD->pz_NAME,
436                    (int)pOD->optArg.argInt);
437             continue;
438         }
439 
440         /*
441          *  If the argument type is an enumeration, then it is much
442          *  like a text value, except we call the callback function
443          *  to emit the value corresponding to the "optArg" number.
444          */
445         if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_ENUMERATION) {
446             print_enumeration(pOpts, pOD);
447             continue;
448         }
449 
450         /*
451          *  If the argument type is numeric, the last arg pointer
452          *  is really the VALUE of the string that was pointed to.
453          */
454         if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_BOOLEAN) {
455             printf(zFullOptFmt, pOpts->pzPROGNAME, pOD->pz_NAME,
456                    (pOD->optArg.argBool == 0) ? "false" : "true");
457             continue;
458         }
459 
460         /*
461          *  IF the option has an empty value,
462          *  THEN we set the argument to the occurrence count.
463          */
464         if (  (pOD->optArg.argString == NULL)
465            || (pOD->optArg.argString[0] == NUL) ) {
466 
467             printf(zOptNumFmt, pOpts->pzPROGNAME, pOD->pz_NAME,
468                    pOD->optOccCt);
469             continue;
470         }
471 
472         /*
473          *  This option has a text value
474          */
475         printf(OPT_VAL_FMT, pOpts->pzPROGNAME, pOD->pz_NAME);
476         print_quot_str(pOD->optArg.argString);
477         printf(OPT_END_FMT, pOpts->pzPROGNAME, pOD->pz_NAME);
478 
479     } while (++optIx < pOpts->presetOptCt );
480 
481     if (  ((pOpts->fOptSet & OPTPROC_REORDER) != 0)
482        && (pOpts->curOptIdx < pOpts->origArgCt))
483         print_reordering(pOpts);
484 
485     fflush(stdout);
486 }
487 
488 /** @}
489  *
490  * Local Variables:
491  * mode: C
492  * c-file-style: "stroustrup"
493  * indent-tabs-mode: nil
494  * End:
495  * end of autoopts/putshell.c */
496