Home | History | Annotate | Line # | Download | only in libopts
      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