Home | History | Annotate | Line # | Download | only in libopts
      1 /*	$NetBSD: makeshell.c,v 1.10 2024/08/18 20:47:24 christos Exp $	*/
      2 
      3 
      4 /**
      5  * \file makeshell.c
      6  *
      7  *  This module will interpret the options set in the tOptions
      8  *  structure and create a Bourne shell script capable of parsing them.
      9  *
     10  * @addtogroup autoopts
     11  * @{
     12  */
     13 /*
     14  *  This file is part of AutoOpts, a companion to AutoGen.
     15  *  AutoOpts is free software.
     16  *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
     17  *
     18  *  AutoOpts is available under any one of two licenses.  The license
     19  *  in use must be one of these two and the choice is under the control
     20  *  of the user of the license.
     21  *
     22  *   The GNU Lesser General Public License, version 3 or later
     23  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
     24  *
     25  *   The Modified Berkeley Software Distribution License
     26  *      See the file "COPYING.mbsd"
     27  *
     28  *  These files have the following sha256 sums:
     29  *
     30  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
     31  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
     32  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
     33  */
     34 
     35  static inline unsigned char to_uchar (char ch) { return ch; }
     36 
     37 #define UPPER(_c) (toupper(to_uchar(_c)))
     38 #define LOWER(_c) (tolower(to_uchar(_c)))
     39 
     40 lo_noreturn static void
     41 option_exits(int exit_code)
     42 {
     43     if (print_exit)
     44         printf("\nexit %d\n", exit_code);
     45     exit(exit_code);
     46 }
     47 
     48 lo_noreturn static void
     49 ao_bug(char const * msg)
     50 {
     51     fprintf(stderr, zao_bug_msg, msg);
     52     option_exits(EX_SOFTWARE);
     53 }
     54 
     55 static void
     56 fserr_warn(char const * prog, char const * op, char const * fname)
     57 {
     58     fprintf(stderr, zfserr_fmt, prog, errno, strerror(errno),
     59             op, fname);
     60 }
     61 
     62 lo_noreturn static void
     63 fserr_exit(char const * prog, char const * op, char const * fname)
     64 {
     65     fserr_warn(prog, op, fname);
     66     option_exits(EXIT_FAILURE);
     67 }
     68 
     69 /*=export_func  optionParseShell
     70  * private:
     71  *
     72  * what:  Decipher a boolean value
     73  * arg:   + tOptions * + pOpts    + program options descriptor +
     74  *
     75  * doc:
     76  *  Emit a shell script that will parse the command line options.
     77 =*/
     78 void
     79 optionParseShell(tOptions * opts)
     80 {
     81     /*
     82      *  Check for our SHELL option now.
     83      *  IF the output file contains the "#!" magic marker,
     84      *  it will override anything we do here.
     85      */
     86     if (HAVE_GENSHELL_OPT(SHELL))
     87         shell_prog = GENSHELL_OPT_ARG(SHELL);
     88 
     89     else if (! ENABLED_GENSHELL_OPT(SHELL))
     90         shell_prog = NULL;
     91 
     92     else if ((shell_prog = getenv("SHELL")),
     93              shell_prog == NULL)
     94 
     95         shell_prog = POSIX_SHELL;
     96 
     97     /*
     98      *  Check for a specified output file
     99      */
    100     if (HAVE_GENSHELL_OPT(SCRIPT))
    101         open_out(GENSHELL_OPT_ARG(SCRIPT), opts->pzProgName);
    102 
    103     emit_usage(opts);
    104     emit_setup(opts);
    105 
    106     /*
    107      *  There are four modes of option processing.
    108      */
    109     switch (opts->fOptSet & (OPTPROC_LONGOPT|OPTPROC_SHORTOPT)) {
    110     case OPTPROC_LONGOPT:
    111         fputs(LOOP_STR,         stdout);
    112 
    113         fputs(LONG_OPT_MARK,    stdout);
    114         fputs(INIT_LOPT_STR,    stdout);
    115         emit_long(opts);
    116         printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
    117         fputs(END_OPT_SEL_STR,  stdout);
    118 
    119         fputs(NOT_FOUND_STR,    stdout);
    120         break;
    121 
    122     case 0:
    123         fputs(ONLY_OPTS_LOOP,   stdout);
    124         fputs(INIT_LOPT_STR,    stdout);
    125         emit_long(opts);
    126         printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
    127         break;
    128 
    129     case OPTPROC_SHORTOPT:
    130         fputs(LOOP_STR,         stdout);
    131 
    132         fputs(FLAG_OPT_MARK,    stdout);
    133         fputs(INIT_OPT_STR,     stdout);
    134         emit_flag(opts);
    135         printf(OPT_ARG_FMT,     opts->pzPROGNAME);
    136         fputs(END_OPT_SEL_STR,  stdout);
    137 
    138         fputs(NOT_FOUND_STR,    stdout);
    139         break;
    140 
    141     case OPTPROC_LONGOPT|OPTPROC_SHORTOPT:
    142         fputs(LOOP_STR,         stdout);
    143 
    144         fputs(LONG_OPT_MARK,    stdout);
    145         fputs(INIT_LOPT_STR,    stdout);
    146         emit_long(opts);
    147         printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
    148         fputs(END_OPT_SEL_STR,  stdout);
    149 
    150         fputs(FLAG_OPT_MARK,    stdout);
    151         fputs(INIT_OPT_STR,     stdout);
    152         emit_flag(opts);
    153         printf(OPT_ARG_FMT,     opts->pzPROGNAME);
    154         fputs(END_OPT_SEL_STR,  stdout);
    155 
    156         fputs(NOT_FOUND_STR,    stdout);
    157         break;
    158     }
    159 
    160     emit_wrapup(opts);
    161     if ((script_trailer != NULL) && (*script_trailer != NUL))
    162         fputs(script_trailer, stdout);
    163     else if (ENABLED_GENSHELL_OPT(SHELL))
    164         printf(SHOW_PROG_ENV, opts->pzPROGNAME);
    165 
    166 #ifdef HAVE_FCHMOD
    167     fchmod(STDOUT_FILENO, 0755);
    168 #endif
    169     fclose(stdout);
    170 
    171     if (ferror(stdout))
    172         fserr_exit(opts->pzProgName, zwriting, zstdout_name);
    173 
    174     AGFREE(script_text);
    175     script_leader    = NULL;
    176     script_trailer   = NULL;
    177     script_text      = NULL;
    178 }
    179 
    180 #ifdef HAVE_WORKING_FORK
    181 /**
    182  * Print the value of "var" to a file descriptor.
    183  * The "fdin" is the read end of a pipe to a forked process that
    184  * is writing usage text to it.  We read that text in and re-emit
    185  * to standard out, formatting it so that it is assigned to a
    186  * shell variable.
    187  *
    188  * @param[in] prog  The capitalized, c-variable-formatted program name
    189  * @param[in] var   a similarly formatted type name
    190  *                  (LONGUSAGE, USAGE or VERSION)
    191  * @param[in] fdin  the input end of a pipe
    192  */
    193 static void
    194 emit_var_text(char const * prog, char const * var, int fdin)
    195 {
    196     FILE * fp   = fdopen(fdin, "r" FOPEN_BINARY_FLAG);
    197     int    nlct = 0; /* defer newlines and skip trailing ones */
    198 
    199     printf(SET_TEXT_FMT, prog, var);
    200     if (fp == NULL)
    201         goto skip_text;
    202 
    203     for (;;) {
    204         int  ch = fgetc(fp);
    205         switch (ch) {
    206 
    207         case NL:
    208             nlct++;
    209             break;
    210 
    211         case '\'':
    212             while (nlct > 0) {
    213                 fputc(NL, stdout);
    214                 nlct--;
    215             }
    216             fputs(apostrophe, stdout);
    217             break;
    218 
    219         case EOF:
    220             goto done;
    221 
    222         default:
    223             while (nlct > 0) {
    224                 fputc(NL, stdout);
    225                 nlct--;
    226             }
    227             fputc(ch, stdout);
    228             break;
    229         }
    230     } done:;
    231 
    232     fclose(fp);
    233 
    234  skip_text:
    235 
    236     fputs(END_SET_TEXT, stdout);
    237 }
    238 #endif
    239 
    240 /**
    241  *  The purpose of this function is to assign "long usage", short usage
    242  *  and version information to a shell variable.  Rather than wind our
    243  *  way through all the logic necessary to emit the text directly, we
    244  *  fork(), have our child process emit the text the normal way and
    245  *  capture the output in the parent process.
    246  *
    247  * @param[in] opts  the program options
    248  * @param[in] which what to print: long usage, usage or version
    249  * @param[in] od    for TT_VERSION, it is the version option
    250  */
    251 static void
    252 text_to_var(tOptions * opts, teTextTo which, tOptDesc * od)
    253 {
    254 #   define _TT_(n) static char const z ## n [] = #n;
    255     TEXTTO_TABLE
    256 #   undef _TT_
    257 #   define _TT_(n) z ## n ,
    258       static char const * ttnames[] = { TEXTTO_TABLE };
    259 #   undef _TT_
    260 
    261 #if ! defined(HAVE_WORKING_FORK)
    262     printf(SET_NO_TEXT_FMT, opts->pzPROGNAME, ttnames[which]);
    263 #else
    264     int  fdpair[2];
    265 
    266     fflush(stdout);
    267     fflush(stderr);
    268 
    269     if (pipe(fdpair) != 0)
    270         fserr_exit(opts->pzProgName, "pipe", zinter_proc_pipe);
    271 
    272     switch (fork()) {
    273     case -1:
    274         fserr_exit(opts->pzProgName, "fork", opts->pzProgName);
    275         /* NOTREACHED */
    276 
    277     case 0:
    278         /*
    279          * Send both stderr and stdout to the pipe.  No matter which
    280          * descriptor is used, we capture the output on the read end.
    281          */
    282         dup2(fdpair[1], STDERR_FILENO);
    283         dup2(fdpair[1], STDOUT_FILENO);
    284         close(fdpair[0]);
    285 
    286         switch (which) {
    287         case TT_LONGUSAGE:
    288             (*(opts->pUsageProc))(opts, EXIT_SUCCESS);
    289             /* FALLTHROUGH */ /* NOTREACHED */
    290 
    291         case TT_USAGE:
    292             (*(opts->pUsageProc))(opts, EXIT_FAILURE);
    293             /* FALLTHROUGH */ /* NOTREACHED */
    294 
    295         case TT_VERSION:
    296             if (od->fOptState & OPTST_ALLOC_ARG) {
    297                 AGFREE(od->optArg.argString);
    298                 od->fOptState &= ~OPTST_ALLOC_ARG;
    299             }
    300             od->optArg.argString = "c";
    301             optionPrintVersion(opts, od);
    302             /* FALLTHROUGH */ /* NOTREACHED */
    303 
    304         default:
    305             option_exits(EXIT_FAILURE);
    306             /* FALLTHROUGH */ /* NOTREACHED */
    307         }
    308         /* FALLTHROUGH */ /* NOTREACHED */
    309 
    310     default:
    311         close(fdpair[1]);
    312     }
    313 
    314     emit_var_text(opts->pzPROGNAME, ttnames[which], fdpair[0]);
    315 #endif
    316 }
    317 
    318 /**
    319  * capture usage text in shell variables.
    320  *
    321  */
    322 static void
    323 emit_usage(tOptions * opts)
    324 {
    325     char tm_nm_buf[AO_NAME_SIZE];
    326 
    327     /*
    328      *  First, switch stdout to the output file name.
    329      *  Then, change the program name to the one defined
    330      *  by the definitions (rather than the current
    331      *  executable name).  Down case the upper cased name.
    332      */
    333     if (script_leader != NULL)
    334         fputs(script_leader, stdout);
    335 
    336     {
    337         char const * out_nm;
    338 
    339         {
    340             time_t    c_tim = time(NULL);
    341             struct tm * ptm = localtime(&c_tim);
    342             strftime(tm_nm_buf, AO_NAME_SIZE, TIME_FMT, ptm );
    343         }
    344 
    345         if (HAVE_GENSHELL_OPT(SCRIPT))
    346              out_nm = GENSHELL_OPT_ARG(SCRIPT);
    347         else out_nm = STDOUT;
    348 
    349         if ((script_leader == NULL) && (shell_prog != NULL))
    350             printf(SHELL_MAGIC, shell_prog);
    351 
    352         printf(PREAMBLE_FMT, START_MARK, out_nm, tm_nm_buf);
    353     }
    354 
    355     printf(END_PRE_FMT, opts->pzPROGNAME);
    356 
    357     /*
    358      *  Get a copy of the original program name in lower case and
    359      *  fill in an approximation of the program name from it.
    360      */
    361     {
    362         char *       pzPN = tm_nm_buf;
    363         char const * pz   = opts->pzPROGNAME;
    364         char **      pp;
    365 
    366         /* Copy the program name into the time/name buffer */
    367         for (;;) {
    368             if ((*pzPN++ = (char)tolower((unsigned char)*pz++)) == NUL)
    369                 break;
    370         }
    371 
    372         pp  = VOIDP(&(opts->pzProgPath));
    373         *pp = tm_nm_buf;
    374         pp  = VOIDP(&(opts->pzProgName));
    375         *pp = tm_nm_buf;
    376     }
    377 
    378     text_to_var(opts, TT_LONGUSAGE, NULL);
    379     text_to_var(opts, TT_USAGE,     NULL);
    380 
    381     {
    382         tOptDesc * pOptDesc = opts->pOptDesc;
    383         int        optionCt = opts->optCt;
    384 
    385         for (;;) {
    386             if (pOptDesc->pOptProc == optionPrintVersion) {
    387                 text_to_var(opts, TT_VERSION, pOptDesc);
    388                 break;
    389             }
    390 
    391             if (--optionCt <= 0)
    392                 break;
    393             pOptDesc++;
    394         }
    395     }
    396 }
    397 
    398 static void
    399 emit_wrapup(tOptions * opts)
    400 {
    401     tOptDesc *   od     = opts->pOptDesc;
    402     int          opt_ct = opts->presetOptCt;
    403     char const * fmt;
    404 
    405     printf(FINISH_LOOP, opts->pzPROGNAME);
    406     for (;opt_ct > 0; od++, --opt_ct) {
    407         /*
    408          *  Options that are either usage documentation or are compiled out
    409          *  are not to be processed.
    410          */
    411         if (SKIP_OPT(od) || (od->pz_NAME == NULL))
    412             continue;
    413 
    414         /*
    415          *  do not presence check if there is no minimum/must-set
    416          */
    417         if ((od->optMinCt == 0) && ((od->fOptState & OPTST_MUST_SET) == 0))
    418             continue;
    419 
    420         if (od->optMaxCt > 1)
    421              fmt = CHK_MIN_COUNT;
    422         else fmt = CHK_ONE_REQUIRED;
    423 
    424         {
    425             int min = (od->optMinCt == 0) ? 1 : od->optMinCt;
    426             printf(fmt, opts->pzPROGNAME, od->pz_NAME, min);
    427         }
    428     }
    429     fputs(END_MARK, stdout);
    430 }
    431 
    432 static void
    433 emit_setup(tOptions * opts)
    434 {
    435     tOptDesc *   od     = opts->pOptDesc;
    436     int          opt_ct = opts->presetOptCt;
    437     char const * fmt;
    438     char const * def_val;
    439 
    440     for (;opt_ct > 0; od++, --opt_ct) {
    441         char int_val_buf[32];
    442 
    443         /*
    444          *  Options that are either usage documentation or are compiled out
    445          *  are not to be processed.
    446          */
    447         if (SKIP_OPT(od) || (od->pz_NAME == NULL))
    448             continue;
    449 
    450         if (od->optMaxCt > 1)
    451              fmt = MULTI_DEF_FMT;
    452         else fmt = SGL_DEF_FMT;
    453 
    454         /*
    455          *  IF this is an enumeration/bitmask option, then convert the value
    456          *  to a string before printing the default value.
    457          */
    458         switch (OPTST_GET_ARGTYPE(od->fOptState)) {
    459         case OPARG_TYPE_ENUMERATION:
    460             (*(od->pOptProc))(OPTPROC_EMIT_SHELL, od );
    461             def_val = od->optArg.argString;
    462             break;
    463 
    464         /*
    465          *  Numeric and membership bit options are just printed as a number.
    466          */
    467         case OPARG_TYPE_NUMERIC:
    468             snprintf(int_val_buf, sizeof(int_val_buf), "%d",
    469                      (int)od->optArg.argInt);
    470             def_val = int_val_buf;
    471             break;
    472 
    473         case OPARG_TYPE_MEMBERSHIP:
    474             snprintf(int_val_buf, sizeof(int_val_buf), "%lu",
    475                      (unsigned long)od->optArg.argIntptr);
    476             def_val = int_val_buf;
    477             break;
    478 
    479         case OPARG_TYPE_BOOLEAN:
    480             def_val = (od->optArg.argBool) ? TRUE_STR : FALSE_STR;
    481             break;
    482 
    483         default:
    484             if (od->optArg.argString == NULL) {
    485                 if (fmt == SGL_DEF_FMT)
    486                     fmt = SGL_NO_DEF_FMT;
    487                 def_val = NULL;
    488             }
    489             else
    490                 def_val = od->optArg.argString;
    491         }
    492 
    493         printf(fmt, opts->pzPROGNAME, od->pz_NAME, def_val);
    494     }
    495 }
    496 
    497 static void
    498 emit_action(tOptions * opts, tOptDesc * od)
    499 {
    500     if (od->pOptProc == optionPrintVersion)
    501         printf(ECHO_N_EXIT, opts->pzPROGNAME, VER_STR);
    502 
    503     else if (od->pOptProc == optionPagedUsage)
    504         printf(PAGE_USAGE_TEXT, opts->pzPROGNAME);
    505 
    506     else if (od->pOptProc == optionLoadOpt) {
    507         printf(LVL3_CMD, NO_LOAD_WARN);
    508         printf(LVL3_CMD, YES_NEED_OPT_ARG);
    509 
    510     } else if (od->pz_NAME == NULL) {
    511 
    512         if (od->pOptProc == NULL) {
    513             printf(LVL3_CMD, NO_SAVE_OPTS);
    514             printf(LVL3_CMD, OK_NEED_OPT_ARG);
    515         } else
    516             printf(ECHO_N_EXIT, opts->pzPROGNAME, LONG_USE_STR);
    517 
    518     } else {
    519         if (od->optMaxCt == 1)
    520             printf(SGL_ARG_FMT, opts->pzPROGNAME, od->pz_NAME);
    521         else {
    522             if ((unsigned)od->optMaxCt < NOLIMIT)
    523                 printf(CHK_MAX_COUNT, opts->pzPROGNAME,
    524                        od->pz_NAME, od->optMaxCt);
    525 
    526             printf(MULTI_ARG_FMT, opts->pzPROGNAME, od->pz_NAME);
    527         }
    528 
    529         /*
    530          *  Fix up the args.
    531          */
    532         if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NONE) {
    533             printf(SET_MULTI_ARG, opts->pzPROGNAME, od->pz_NAME);
    534             printf(LVL3_CMD, NO_ARG_NEEDED);
    535 
    536         } else if (od->fOptState & OPTST_ARG_OPTIONAL) {
    537             printf(SET_MULTI_ARG,  opts->pzPROGNAME, od->pz_NAME);
    538             printf(LVL3_CMD, OK_NEED_OPT_ARG);
    539 
    540         } else {
    541             printf(LVL3_CMD, YES_NEED_OPT_ARG);
    542         }
    543     }
    544     fputs(zOptionEndSelect, stdout);
    545 }
    546 
    547 static void
    548 emit_inaction(tOptions * opts, tOptDesc * od)
    549 {
    550     if (od->pOptProc == optionLoadOpt) {
    551         printf(LVL3_CMD, NO_SUPPRESS_LOAD);
    552 
    553     } else if (od->optMaxCt == 1)
    554         printf(NO_SGL_ARG_FMT, opts->pzPROGNAME,
    555                od->pz_NAME, od->pz_DisablePfx);
    556     else
    557         printf(NO_MULTI_ARG_FMT, opts->pzPROGNAME,
    558                od->pz_NAME, od->pz_DisablePfx);
    559 
    560     printf(LVL3_CMD, NO_ARG_NEEDED);
    561     fputs(zOptionEndSelect, stdout);
    562 }
    563 
    564 /**
    565  * recognize flag options.  These go at the end.
    566  * At the end, emit code to handle options we don't recognize.
    567  *
    568  * @param[in] opts  the program options
    569  */
    570 static void
    571 emit_flag(tOptions * opts)
    572 {
    573     tOptDesc * od = opts->pOptDesc;
    574     int        opt_ct = opts->optCt;
    575 
    576     fputs(zOptionCase, stdout);
    577 
    578     for (;opt_ct > 0; od++, --opt_ct) {
    579 
    580         if (SKIP_OPT(od) || ! IS_GRAPHIC_CHAR(od->optValue))
    581             continue;
    582 
    583         printf(zOptionFlag, od->optValue);
    584         emit_action(opts, od);
    585     }
    586     printf(UNK_OPT_FMT, FLAG_STR, opts->pzPROGNAME);
    587 }
    588 
    589 /**
    590  *  Emit the match text for a long option.  The passed in \a name may be
    591  *  either the enablement name or the disablement name.
    592  *
    593  * @param[in] name  The current name to check.
    594  * @param[in] cod   current option descriptor
    595  * @param[in] opts  the program options
    596  */
    597 static void
    598 emit_match_expr(char const * name, tOptDesc * cod, tOptions * opts)
    599 {
    600     char name_bf[32];
    601     unsigned int    min_match_ct = 2;
    602     unsigned int    max_match_ct = strlen(name) - 1;
    603 
    604     if (max_match_ct >= sizeof(name_bf) - 1)
    605         goto leave;
    606 
    607     {
    608         tOptDesc *  od = opts->pOptDesc;
    609         int         ct = opts->optCt;
    610 
    611         for (; ct-- > 0; od++) {
    612             unsigned int match_ct = 0;
    613 
    614             /*
    615              *  Omit the current option, Doc opts and compiled out opts.
    616              */
    617             if ((od == cod) || SKIP_OPT(od))
    618                 continue;
    619 
    620             /*
    621              *  Check each character of the name case insensitively.
    622              *  They must not be the same.  They cannot be, because it would
    623              *  not compile correctly if they were.
    624              */
    625             while (UPPER(od->pz_Name[match_ct]) == UPPER(name[match_ct]))
    626                 match_ct++;
    627 
    628             if (match_ct > min_match_ct)
    629                 min_match_ct = match_ct;
    630 
    631             /*
    632              *  Check the disablement name, too.
    633              */
    634             if (od->pz_DisableName == NULL)
    635                 continue;
    636 
    637             match_ct = 0;
    638             while (  toupper((unsigned char)od->pz_DisableName[match_ct])
    639                   == toupper((unsigned char)name[match_ct]))
    640                 match_ct++;
    641             if (match_ct > min_match_ct)
    642                 min_match_ct = match_ct;
    643         }
    644     }
    645 
    646     /*
    647      *  Don't bother emitting partial matches if there is only one possible
    648      *  partial match.
    649      */
    650     if (min_match_ct < max_match_ct) {
    651         char *  pz    = name_bf + min_match_ct;
    652         int     nm_ix = min_match_ct;
    653 
    654         memcpy(name_bf, name, min_match_ct);
    655 
    656         for (;;) {
    657             *pz = NUL;
    658             printf(zOptionPartName, name_bf);
    659             *pz++ = name[nm_ix++];
    660             if (name[nm_ix] == NUL) {
    661                 *pz = NUL;
    662                 break;
    663             }
    664         }
    665     }
    666 
    667 leave:
    668     printf(zOptionFullName, name);
    669 }
    670 
    671 /**
    672  *  Emit GNU-standard long option handling code.
    673  *
    674  * @param[in] opts  the program options
    675  */
    676 static void
    677 emit_long(tOptions * opts)
    678 {
    679     tOptDesc * od = opts->pOptDesc;
    680     int        ct  = opts->optCt;
    681 
    682     fputs(zOptionCase, stdout);
    683 
    684     /*
    685      *  do each option, ...
    686      */
    687     do  {
    688         /*
    689          *  Documentation & compiled-out options
    690          */
    691         if (SKIP_OPT(od))
    692             continue;
    693 
    694         emit_match_expr(od->pz_Name, od, opts);
    695         emit_action(opts, od);
    696 
    697         /*
    698          *  Now, do the same thing for the disablement version of the option.
    699          */
    700         if (od->pz_DisableName != NULL) {
    701             emit_match_expr(od->pz_DisableName, od, opts);
    702             emit_inaction(opts, od);
    703         }
    704     } while (od++, --ct > 0);
    705 
    706     printf(UNK_OPT_FMT, OPTION_STR, opts->pzPROGNAME);
    707 }
    708 
    709 /**
    710  * Load the previous shell script output file.  We need to preserve any
    711  * hand-edited additions outside of the START_MARK and END_MARKs.
    712  *
    713  * @param[in] fname  the output file name
    714  */
    715 static char *
    716 load_old_output(char const * fname, char const * pname)
    717 {
    718     /*
    719      *  IF we cannot stat the file,
    720      *  THEN assume we are creating a new file.
    721      *       Skip the loading of the old data.
    722      */
    723     FILE * fp = fopen(fname, "r" FOPEN_BINARY_FLAG);
    724     struct stat stbf;
    725     char * text;
    726     char * scan;
    727 
    728     if (fp == NULL)
    729         return NULL;
    730 
    731     /*
    732      * If we opened it, we should be able to stat it and it needs
    733      * to be a regular file
    734      */
    735     if ((fstat(fileno(fp), &stbf) != 0) || (! S_ISREG(stbf.st_mode)))
    736         fserr_exit(pname, "fstat", fname);
    737 
    738     scan = text = AGALOC(stbf.st_size + 1, "f data");
    739 
    740     /*
    741      *  Read in all the data as fast as our OS will let us.
    742      */
    743     for (;;) {
    744         size_t inct = fread(VOIDP(scan), 1, (size_t)stbf.st_size, fp);
    745         if (inct == 0)
    746             break;
    747 
    748         stbf.st_size -= (ssize_t)inct;
    749 
    750         if (stbf.st_size == 0)
    751             break;
    752 
    753         scan += inct;
    754     }
    755 
    756     *scan = NUL;
    757     fclose(fp);
    758 
    759     return text;
    760 }
    761 
    762 /**
    763  * Open the specified output file.  If it already exists, load its
    764  * contents and save the non-generated (hand edited) portions.
    765  * If a "start mark" is found, everything before it is preserved leader.
    766  * If not, the entire thing is a trailer.  Assuming the start is found,
    767  * then everything after the end marker is the trailer.  If the end
    768  * mark is not found, the file is actually corrupt, but we take the
    769  * remainder to be the trailer.
    770  *
    771  * @param[in] fname  the output file name
    772  */
    773 static void
    774 open_out(char const * fname, char const * pname)
    775 {
    776 
    777     do  {
    778         char * txt = script_text = load_old_output(fname, pname);
    779         char * scn;
    780 
    781         if (txt == NULL)
    782             break;
    783 
    784         scn = strstr(txt, START_MARK);
    785         if (scn == NULL) {
    786             script_trailer = txt;
    787             break;
    788         }
    789 
    790         *(scn++) = NUL;
    791         scn = strstr(scn, END_MARK);
    792         if (scn == NULL) {
    793             /*
    794              * The file is corrupt.  Set the trailer to be everything
    795              * after the start mark. The user will need to fix it up.
    796              */
    797             script_trailer = txt + strlen(txt) + START_MARK_LEN + 1;
    798             break;
    799         }
    800 
    801         /*
    802          *  Check to see if the data contains our marker.
    803          *  If it does, then we will skip over it
    804          */
    805         script_trailer = scn + END_MARK_LEN;
    806         script_leader  = txt;
    807     } while (false);
    808 
    809     if (freopen(fname, "w" FOPEN_BINARY_FLAG, stdout) != stdout)
    810         fserr_exit(pname, "freopen", fname);
    811 }
    812 
    813 /*=export_func genshelloptUsage
    814  * private:
    815  * what: The usage function for the genshellopt generated program
    816  *
    817  * arg:  + tOptions * + opts    + program options descriptor +
    818  * arg:  + int        + exit_cd + usage text type to produce +
    819  *
    820  * doc:
    821  *  This function is used to create the usage strings for the option
    822  *  processing shell script code.  Two child processes are spawned
    823  *  each emitting the usage text in either the short (error exit)
    824  *  style or the long style.  The generated program will capture this
    825  *  and create shell script variables containing the two types of text.
    826 =*/
    827 void
    828 genshelloptUsage(tOptions * opts, int exit_cd)
    829 {
    830 #if ! defined(HAVE_WORKING_FORK)
    831     optionUsage(opts, exit_cd);
    832 #else
    833     /*
    834      *  IF not EXIT_SUCCESS,
    835      *  THEN emit the short form of usage.
    836      */
    837     if (exit_cd != EXIT_SUCCESS)
    838         optionUsage(opts, exit_cd);
    839     fflush(stderr);
    840     fflush(stdout);
    841     if (ferror(stdout) || ferror(stderr))
    842         option_exits(EXIT_FAILURE);
    843 
    844     option_usage_fp = stdout;
    845 
    846     /*
    847      *  First, print our usage
    848      */
    849     switch (fork()) {
    850     case -1:
    851         optionUsage(opts, EXIT_FAILURE);
    852         /* FALLTHROUGH */ /* NOTREACHED */
    853 
    854     case 0:
    855         pagerState = PAGER_STATE_CHILD;
    856         optionUsage(opts, EXIT_SUCCESS);
    857         /* FALLTHROUGH */ /* NOTREACHED */
    858 
    859     default:
    860     {
    861         int  sts;
    862         wait(&sts);
    863     }
    864     }
    865 
    866     /*
    867      *  Generate the pzProgName, since optionProcess() normally
    868      *  gets it from the command line
    869      */
    870     {
    871         char *  pz;
    872         char ** pp = VOIDP(&(optionParseShellOptions->pzProgName));
    873         AGDUPSTR(pz, optionParseShellOptions->pzPROGNAME, "prog name");
    874         *pp = pz;
    875         while (*pz != NUL) {
    876             *pz = (char)LOWER(*pz);
    877             pz++;
    878         }
    879     }
    880 
    881     /*
    882      *  Separate the makeshell usage from the client usage
    883      */
    884     fprintf(option_usage_fp, zGenshell, optionParseShellOptions->pzProgName);
    885     fflush(option_usage_fp);
    886 
    887     /*
    888      *  Now, print the client usage.
    889      */
    890     switch (fork()) {
    891     case 0:
    892         pagerState = PAGER_STATE_CHILD;
    893         /*FALLTHROUGH*/
    894     case -1:
    895         optionUsage(optionParseShellOptions, EXIT_FAILURE);
    896         /* FALLTHROUGH */ /* NOTREACHED */
    897 
    898     default:
    899     {
    900         int  sts;
    901         wait(&sts);
    902     }
    903     }
    904 
    905     fflush(stdout);
    906     if (ferror(stdout))
    907         fserr_exit(opts->pzProgName, zwriting, zstdout_name);
    908 
    909     option_exits(EXIT_SUCCESS);
    910 #endif
    911 }
    912 
    913 /** @}
    914  *
    915  * Local Variables:
    916  * mode: C
    917  * c-file-style: "stroustrup"
    918  * indent-tabs-mode: nil
    919  * End:
    920  * end of autoopts/makeshell.c */
    921