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
string_size(char const * scan,size_t nl_len)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 *
optionQuoteString(char const * text,char const * nl)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 *
print_quoted_apostrophes(char const * str)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
print_quot_str(char const * str)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
print_enumeration(tOptions * pOpts,tOptDesc * pOD)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
print_membership(tOptions * pOpts,tOptDesc * pOD)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
print_stacked_arg(tOptions * pOpts,tOptDesc * pOD)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
print_reordering(tOptions * opts)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
optionPutShell(tOptions * pOpts)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