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