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