/*      $NetBSD: makeshell.c,v 1.10 2024/08/18 20:47:24 christos Exp $  */


/**
* \file makeshell.c
*
*  This module will interpret the options set in the tOptions
*  structure and create a Bourne shell script capable of parsing them.
*
* @addtogroup autoopts
* @{
*/
/*
*  This file is part of AutoOpts, a companion to AutoGen.
*  AutoOpts is free software.
*  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
*
*  AutoOpts is available under any one of two licenses.  The license
*  in use must be one of these two and the choice is under the control
*  of the user of the license.
*
*   The GNU Lesser General Public License, version 3 or later
*      See the files "COPYING.lgplv3" and "COPYING.gplv3"
*
*   The Modified Berkeley Software Distribution License
*      See the file "COPYING.mbsd"
*
*  These files have the following sha256 sums:
*
*  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
*  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
*  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
*/

static inline unsigned char to_uchar (char ch) { return ch; }

#define UPPER(_c) (toupper(to_uchar(_c)))
#define LOWER(_c) (tolower(to_uchar(_c)))

lo_noreturn static void
option_exits(int exit_code)
{
   if (print_exit)
       printf("\nexit %d\n", exit_code);
   exit(exit_code);
}

lo_noreturn static void
ao_bug(char const * msg)
{
   fprintf(stderr, zao_bug_msg, msg);
   option_exits(EX_SOFTWARE);
}

static void
fserr_warn(char const * prog, char const * op, char const * fname)
{
   fprintf(stderr, zfserr_fmt, prog, errno, strerror(errno),
           op, fname);
}

lo_noreturn static void
fserr_exit(char const * prog, char const * op, char const * fname)
{
   fserr_warn(prog, op, fname);
   option_exits(EXIT_FAILURE);
}

/*=export_func  optionParseShell
* private:
*
* what:  Decipher a boolean value
* arg:   + tOptions * + pOpts    + program options descriptor +
*
* doc:
*  Emit a shell script that will parse the command line options.
=*/
void
optionParseShell(tOptions * opts)
{
   /*
    *  Check for our SHELL option now.
    *  IF the output file contains the "#!" magic marker,
    *  it will override anything we do here.
    */
   if (HAVE_GENSHELL_OPT(SHELL))
       shell_prog = GENSHELL_OPT_ARG(SHELL);

   else if (! ENABLED_GENSHELL_OPT(SHELL))
       shell_prog = NULL;

   else if ((shell_prog = getenv("SHELL")),
            shell_prog == NULL)

       shell_prog = POSIX_SHELL;

   /*
    *  Check for a specified output file
    */
   if (HAVE_GENSHELL_OPT(SCRIPT))
       open_out(GENSHELL_OPT_ARG(SCRIPT), opts->pzProgName);

   emit_usage(opts);
   emit_setup(opts);

   /*
    *  There are four modes of option processing.
    */
   switch (opts->fOptSet & (OPTPROC_LONGOPT|OPTPROC_SHORTOPT)) {
   case OPTPROC_LONGOPT:
       fputs(LOOP_STR,         stdout);

       fputs(LONG_OPT_MARK,    stdout);
       fputs(INIT_LOPT_STR,    stdout);
       emit_long(opts);
       printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
       fputs(END_OPT_SEL_STR,  stdout);

       fputs(NOT_FOUND_STR,    stdout);
       break;

   case 0:
       fputs(ONLY_OPTS_LOOP,   stdout);
       fputs(INIT_LOPT_STR,    stdout);
       emit_long(opts);
       printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
       break;

   case OPTPROC_SHORTOPT:
       fputs(LOOP_STR,         stdout);

       fputs(FLAG_OPT_MARK,    stdout);
       fputs(INIT_OPT_STR,     stdout);
       emit_flag(opts);
       printf(OPT_ARG_FMT,     opts->pzPROGNAME);
       fputs(END_OPT_SEL_STR,  stdout);

       fputs(NOT_FOUND_STR,    stdout);
       break;

   case OPTPROC_LONGOPT|OPTPROC_SHORTOPT:
       fputs(LOOP_STR,         stdout);

       fputs(LONG_OPT_MARK,    stdout);
       fputs(INIT_LOPT_STR,    stdout);
       emit_long(opts);
       printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
       fputs(END_OPT_SEL_STR,  stdout);

       fputs(FLAG_OPT_MARK,    stdout);
       fputs(INIT_OPT_STR,     stdout);
       emit_flag(opts);
       printf(OPT_ARG_FMT,     opts->pzPROGNAME);
       fputs(END_OPT_SEL_STR,  stdout);

       fputs(NOT_FOUND_STR,    stdout);
       break;
   }

   emit_wrapup(opts);
   if ((script_trailer != NULL) && (*script_trailer != NUL))
       fputs(script_trailer, stdout);
   else if (ENABLED_GENSHELL_OPT(SHELL))
       printf(SHOW_PROG_ENV, opts->pzPROGNAME);

#ifdef HAVE_FCHMOD
   fchmod(STDOUT_FILENO, 0755);
#endif
   fclose(stdout);

   if (ferror(stdout))
       fserr_exit(opts->pzProgName, zwriting, zstdout_name);

   AGFREE(script_text);
   script_leader    = NULL;
   script_trailer   = NULL;
   script_text      = NULL;
}

#ifdef HAVE_WORKING_FORK
/**
* Print the value of "var" to a file descriptor.
* The "fdin" is the read end of a pipe to a forked process that
* is writing usage text to it.  We read that text in and re-emit
* to standard out, formatting it so that it is assigned to a
* shell variable.
*
* @param[in] prog  The capitalized, c-variable-formatted program name
* @param[in] var   a similarly formatted type name
*                  (LONGUSAGE, USAGE or VERSION)
* @param[in] fdin  the input end of a pipe
*/
static void
emit_var_text(char const * prog, char const * var, int fdin)
{
   FILE * fp   = fdopen(fdin, "r" FOPEN_BINARY_FLAG);
   int    nlct = 0; /* defer newlines and skip trailing ones */

   printf(SET_TEXT_FMT, prog, var);
   if (fp == NULL)
       goto skip_text;

   for (;;) {
       int  ch = fgetc(fp);
       switch (ch) {

       case NL:
           nlct++;
           break;

       case '\'':
           while (nlct > 0) {
               fputc(NL, stdout);
               nlct--;
           }
           fputs(apostrophe, stdout);
           break;

       case EOF:
           goto done;

       default:
           while (nlct > 0) {
               fputc(NL, stdout);
               nlct--;
           }
           fputc(ch, stdout);
           break;
       }
   } done:;

   fclose(fp);

skip_text:

   fputs(END_SET_TEXT, stdout);
}
#endif

/**
*  The purpose of this function is to assign "long usage", short usage
*  and version information to a shell variable.  Rather than wind our
*  way through all the logic necessary to emit the text directly, we
*  fork(), have our child process emit the text the normal way and
*  capture the output in the parent process.
*
* @param[in] opts  the program options
* @param[in] which what to print: long usage, usage or version
* @param[in] od    for TT_VERSION, it is the version option
*/
static void
text_to_var(tOptions * opts, teTextTo which, tOptDesc * od)
{
#   define _TT_(n) static char const z ## n [] = #n;
   TEXTTO_TABLE
#   undef _TT_
#   define _TT_(n) z ## n ,
     static char const * ttnames[] = { TEXTTO_TABLE };
#   undef _TT_

#if ! defined(HAVE_WORKING_FORK)
   printf(SET_NO_TEXT_FMT, opts->pzPROGNAME, ttnames[which]);
#else
   int  fdpair[2];

   fflush(stdout);
   fflush(stderr);

   if (pipe(fdpair) != 0)
       fserr_exit(opts->pzProgName, "pipe", zinter_proc_pipe);

   switch (fork()) {
   case -1:
       fserr_exit(opts->pzProgName, "fork", opts->pzProgName);
       /* NOTREACHED */

   case 0:
       /*
        * Send both stderr and stdout to the pipe.  No matter which
        * descriptor is used, we capture the output on the read end.
        */
       dup2(fdpair[1], STDERR_FILENO);
       dup2(fdpair[1], STDOUT_FILENO);
       close(fdpair[0]);

       switch (which) {
       case TT_LONGUSAGE:
           (*(opts->pUsageProc))(opts, EXIT_SUCCESS);
           /* FALLTHROUGH */ /* NOTREACHED */

       case TT_USAGE:
           (*(opts->pUsageProc))(opts, EXIT_FAILURE);
           /* FALLTHROUGH */ /* NOTREACHED */

       case TT_VERSION:
           if (od->fOptState & OPTST_ALLOC_ARG) {
               AGFREE(od->optArg.argString);
               od->fOptState &= ~OPTST_ALLOC_ARG;
           }
           od->optArg.argString = "c";
           optionPrintVersion(opts, od);
           /* FALLTHROUGH */ /* NOTREACHED */

       default:
           option_exits(EXIT_FAILURE);
           /* FALLTHROUGH */ /* NOTREACHED */
       }
       /* FALLTHROUGH */ /* NOTREACHED */

   default:
       close(fdpair[1]);
   }

   emit_var_text(opts->pzPROGNAME, ttnames[which], fdpair[0]);
#endif
}

/**
* capture usage text in shell variables.
*
*/
static void
emit_usage(tOptions * opts)
{
   char tm_nm_buf[AO_NAME_SIZE];

   /*
    *  First, switch stdout to the output file name.
    *  Then, change the program name to the one defined
    *  by the definitions (rather than the current
    *  executable name).  Down case the upper cased name.
    */
   if (script_leader != NULL)
       fputs(script_leader, stdout);

   {
       char const * out_nm;

       {
           time_t    c_tim = time(NULL);
           struct tm * ptm = localtime(&c_tim);
           strftime(tm_nm_buf, AO_NAME_SIZE, TIME_FMT, ptm );
       }

       if (HAVE_GENSHELL_OPT(SCRIPT))
            out_nm = GENSHELL_OPT_ARG(SCRIPT);
       else out_nm = STDOUT;

       if ((script_leader == NULL) && (shell_prog != NULL))
           printf(SHELL_MAGIC, shell_prog);

       printf(PREAMBLE_FMT, START_MARK, out_nm, tm_nm_buf);
   }

   printf(END_PRE_FMT, opts->pzPROGNAME);

   /*
    *  Get a copy of the original program name in lower case and
    *  fill in an approximation of the program name from it.
    */
   {
       char *       pzPN = tm_nm_buf;
       char const * pz   = opts->pzPROGNAME;
       char **      pp;

       /* Copy the program name into the time/name buffer */
       for (;;) {
           if ((*pzPN++ = (char)tolower((unsigned char)*pz++)) == NUL)
               break;
       }

       pp  = VOIDP(&(opts->pzProgPath));
       *pp = tm_nm_buf;
       pp  = VOIDP(&(opts->pzProgName));
       *pp = tm_nm_buf;
   }

   text_to_var(opts, TT_LONGUSAGE, NULL);
   text_to_var(opts, TT_USAGE,     NULL);

   {
       tOptDesc * pOptDesc = opts->pOptDesc;
       int        optionCt = opts->optCt;

       for (;;) {
           if (pOptDesc->pOptProc == optionPrintVersion) {
               text_to_var(opts, TT_VERSION, pOptDesc);
               break;
           }

           if (--optionCt <= 0)
               break;
           pOptDesc++;
       }
   }
}

static void
emit_wrapup(tOptions * opts)
{
   tOptDesc *   od     = opts->pOptDesc;
   int          opt_ct = opts->presetOptCt;
   char const * fmt;

   printf(FINISH_LOOP, opts->pzPROGNAME);
   for (;opt_ct > 0; od++, --opt_ct) {
       /*
        *  Options that are either usage documentation or are compiled out
        *  are not to be processed.
        */
       if (SKIP_OPT(od) || (od->pz_NAME == NULL))
           continue;

       /*
        *  do not presence check if there is no minimum/must-set
        */
       if ((od->optMinCt == 0) && ((od->fOptState & OPTST_MUST_SET) == 0))
           continue;

       if (od->optMaxCt > 1)
            fmt = CHK_MIN_COUNT;
       else fmt = CHK_ONE_REQUIRED;

       {
           int min = (od->optMinCt == 0) ? 1 : od->optMinCt;
           printf(fmt, opts->pzPROGNAME, od->pz_NAME, min);
       }
   }
   fputs(END_MARK, stdout);
}

static void
emit_setup(tOptions * opts)
{
   tOptDesc *   od     = opts->pOptDesc;
   int          opt_ct = opts->presetOptCt;
   char const * fmt;
   char const * def_val;

   for (;opt_ct > 0; od++, --opt_ct) {
       char int_val_buf[32];

       /*
        *  Options that are either usage documentation or are compiled out
        *  are not to be processed.
        */
       if (SKIP_OPT(od) || (od->pz_NAME == NULL))
           continue;

       if (od->optMaxCt > 1)
            fmt = MULTI_DEF_FMT;
       else fmt = SGL_DEF_FMT;

       /*
        *  IF this is an enumeration/bitmask option, then convert the value
        *  to a string before printing the default value.
        */
       switch (OPTST_GET_ARGTYPE(od->fOptState)) {
       case OPARG_TYPE_ENUMERATION:
           (*(od->pOptProc))(OPTPROC_EMIT_SHELL, od );
           def_val = od->optArg.argString;
           break;

       /*
        *  Numeric and membership bit options are just printed as a number.
        */
       case OPARG_TYPE_NUMERIC:
           snprintf(int_val_buf, sizeof(int_val_buf), "%d",
                    (int)od->optArg.argInt);
           def_val = int_val_buf;
           break;

       case OPARG_TYPE_MEMBERSHIP:
           snprintf(int_val_buf, sizeof(int_val_buf), "%lu",
                    (unsigned long)od->optArg.argIntptr);
           def_val = int_val_buf;
           break;

       case OPARG_TYPE_BOOLEAN:
           def_val = (od->optArg.argBool) ? TRUE_STR : FALSE_STR;
           break;

       default:
           if (od->optArg.argString == NULL) {
               if (fmt == SGL_DEF_FMT)
                   fmt = SGL_NO_DEF_FMT;
               def_val = NULL;
           }
           else
               def_val = od->optArg.argString;
       }

       printf(fmt, opts->pzPROGNAME, od->pz_NAME, def_val);
   }
}

static void
emit_action(tOptions * opts, tOptDesc * od)
{
   if (od->pOptProc == optionPrintVersion)
       printf(ECHO_N_EXIT, opts->pzPROGNAME, VER_STR);

   else if (od->pOptProc == optionPagedUsage)
       printf(PAGE_USAGE_TEXT, opts->pzPROGNAME);

   else if (od->pOptProc == optionLoadOpt) {
       printf(LVL3_CMD, NO_LOAD_WARN);
       printf(LVL3_CMD, YES_NEED_OPT_ARG);

   } else if (od->pz_NAME == NULL) {

       if (od->pOptProc == NULL) {
           printf(LVL3_CMD, NO_SAVE_OPTS);
           printf(LVL3_CMD, OK_NEED_OPT_ARG);
       } else
           printf(ECHO_N_EXIT, opts->pzPROGNAME, LONG_USE_STR);

   } else {
       if (od->optMaxCt == 1)
           printf(SGL_ARG_FMT, opts->pzPROGNAME, od->pz_NAME);
       else {
           if ((unsigned)od->optMaxCt < NOLIMIT)
               printf(CHK_MAX_COUNT, opts->pzPROGNAME,
                      od->pz_NAME, od->optMaxCt);

           printf(MULTI_ARG_FMT, opts->pzPROGNAME, od->pz_NAME);
       }

       /*
        *  Fix up the args.
        */
       if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NONE) {
           printf(SET_MULTI_ARG, opts->pzPROGNAME, od->pz_NAME);
           printf(LVL3_CMD, NO_ARG_NEEDED);

       } else if (od->fOptState & OPTST_ARG_OPTIONAL) {
           printf(SET_MULTI_ARG,  opts->pzPROGNAME, od->pz_NAME);
           printf(LVL3_CMD, OK_NEED_OPT_ARG);

       } else {
           printf(LVL3_CMD, YES_NEED_OPT_ARG);
       }
   }
   fputs(zOptionEndSelect, stdout);
}

static void
emit_inaction(tOptions * opts, tOptDesc * od)
{
   if (od->pOptProc == optionLoadOpt) {
       printf(LVL3_CMD, NO_SUPPRESS_LOAD);

   } else if (od->optMaxCt == 1)
       printf(NO_SGL_ARG_FMT, opts->pzPROGNAME,
              od->pz_NAME, od->pz_DisablePfx);
   else
       printf(NO_MULTI_ARG_FMT, opts->pzPROGNAME,
              od->pz_NAME, od->pz_DisablePfx);

   printf(LVL3_CMD, NO_ARG_NEEDED);
   fputs(zOptionEndSelect, stdout);
}

/**
* recognize flag options.  These go at the end.
* At the end, emit code to handle options we don't recognize.
*
* @param[in] opts  the program options
*/
static void
emit_flag(tOptions * opts)
{
   tOptDesc * od = opts->pOptDesc;
   int        opt_ct = opts->optCt;

   fputs(zOptionCase, stdout);

   for (;opt_ct > 0; od++, --opt_ct) {

       if (SKIP_OPT(od) || ! IS_GRAPHIC_CHAR(od->optValue))
           continue;

       printf(zOptionFlag, od->optValue);
       emit_action(opts, od);
   }
   printf(UNK_OPT_FMT, FLAG_STR, opts->pzPROGNAME);
}

/**
*  Emit the match text for a long option.  The passed in \a name may be
*  either the enablement name or the disablement name.
*
* @param[in] name  The current name to check.
* @param[in] cod   current option descriptor
* @param[in] opts  the program options
*/
static void
emit_match_expr(char const * name, tOptDesc * cod, tOptions * opts)
{
   char name_bf[32];
   unsigned int    min_match_ct = 2;
   unsigned int    max_match_ct = strlen(name) - 1;

   if (max_match_ct >= sizeof(name_bf) - 1)
       goto leave;

   {
       tOptDesc *  od = opts->pOptDesc;
       int         ct = opts->optCt;

       for (; ct-- > 0; od++) {
           unsigned int match_ct = 0;

           /*
            *  Omit the current option, Doc opts and compiled out opts.
            */
           if ((od == cod) || SKIP_OPT(od))
               continue;

           /*
            *  Check each character of the name case insensitively.
            *  They must not be the same.  They cannot be, because it would
            *  not compile correctly if they were.
            */
           while (UPPER(od->pz_Name[match_ct]) == UPPER(name[match_ct]))
               match_ct++;

           if (match_ct > min_match_ct)
               min_match_ct = match_ct;

           /*
            *  Check the disablement name, too.
            */
           if (od->pz_DisableName == NULL)
               continue;

           match_ct = 0;
           while (  toupper((unsigned char)od->pz_DisableName[match_ct])
                 == toupper((unsigned char)name[match_ct]))
               match_ct++;
           if (match_ct > min_match_ct)
               min_match_ct = match_ct;
       }
   }

   /*
    *  Don't bother emitting partial matches if there is only one possible
    *  partial match.
    */
   if (min_match_ct < max_match_ct) {
       char *  pz    = name_bf + min_match_ct;
       int     nm_ix = min_match_ct;

       memcpy(name_bf, name, min_match_ct);

       for (;;) {
           *pz = NUL;
           printf(zOptionPartName, name_bf);
           *pz++ = name[nm_ix++];
           if (name[nm_ix] == NUL) {
               *pz = NUL;
               break;
           }
       }
   }

leave:
   printf(zOptionFullName, name);
}

/**
*  Emit GNU-standard long option handling code.
*
* @param[in] opts  the program options
*/
static void
emit_long(tOptions * opts)
{
   tOptDesc * od = opts->pOptDesc;
   int        ct  = opts->optCt;

   fputs(zOptionCase, stdout);

   /*
    *  do each option, ...
    */
   do  {
       /*
        *  Documentation & compiled-out options
        */
       if (SKIP_OPT(od))
           continue;

       emit_match_expr(od->pz_Name, od, opts);
       emit_action(opts, od);

       /*
        *  Now, do the same thing for the disablement version of the option.
        */
       if (od->pz_DisableName != NULL) {
           emit_match_expr(od->pz_DisableName, od, opts);
           emit_inaction(opts, od);
       }
   } while (od++, --ct > 0);

   printf(UNK_OPT_FMT, OPTION_STR, opts->pzPROGNAME);
}

/**
* Load the previous shell script output file.  We need to preserve any
* hand-edited additions outside of the START_MARK and END_MARKs.
*
* @param[in] fname  the output file name
*/
static char *
load_old_output(char const * fname, char const * pname)
{
   /*
    *  IF we cannot stat the file,
    *  THEN assume we are creating a new file.
    *       Skip the loading of the old data.
    */
   FILE * fp = fopen(fname, "r" FOPEN_BINARY_FLAG);
   struct stat stbf;
   char * text;
   char * scan;

   if (fp == NULL)
       return NULL;

   /*
    * If we opened it, we should be able to stat it and it needs
    * to be a regular file
    */
   if ((fstat(fileno(fp), &stbf) != 0) || (! S_ISREG(stbf.st_mode)))
       fserr_exit(pname, "fstat", fname);

   scan = text = AGALOC(stbf.st_size + 1, "f data");

   /*
    *  Read in all the data as fast as our OS will let us.
    */
   for (;;) {
       size_t inct = fread(VOIDP(scan), 1, (size_t)stbf.st_size, fp);
       if (inct == 0)
           break;

       stbf.st_size -= (ssize_t)inct;

       if (stbf.st_size == 0)
           break;

       scan += inct;
   }

   *scan = NUL;
   fclose(fp);

   return text;
}

/**
* Open the specified output file.  If it already exists, load its
* contents and save the non-generated (hand edited) portions.
* If a "start mark" is found, everything before it is preserved leader.
* If not, the entire thing is a trailer.  Assuming the start is found,
* then everything after the end marker is the trailer.  If the end
* mark is not found, the file is actually corrupt, but we take the
* remainder to be the trailer.
*
* @param[in] fname  the output file name
*/
static void
open_out(char const * fname, char const * pname)
{

   do  {
       char * txt = script_text = load_old_output(fname, pname);
       char * scn;

       if (txt == NULL)
           break;

       scn = strstr(txt, START_MARK);
       if (scn == NULL) {
           script_trailer = txt;
           break;
       }

       *(scn++) = NUL;
       scn = strstr(scn, END_MARK);
       if (scn == NULL) {
           /*
            * The file is corrupt.  Set the trailer to be everything
            * after the start mark. The user will need to fix it up.
            */
           script_trailer = txt + strlen(txt) + START_MARK_LEN + 1;
           break;
       }

       /*
        *  Check to see if the data contains our marker.
        *  If it does, then we will skip over it
        */
       script_trailer = scn + END_MARK_LEN;
       script_leader  = txt;
   } while (false);

   if (freopen(fname, "w" FOPEN_BINARY_FLAG, stdout) != stdout)
       fserr_exit(pname, "freopen", fname);
}

/*=export_func genshelloptUsage
* private:
* what: The usage function for the genshellopt generated program
*
* arg:  + tOptions * + opts    + program options descriptor +
* arg:  + int        + exit_cd + usage text type to produce +
*
* doc:
*  This function is used to create the usage strings for the option
*  processing shell script code.  Two child processes are spawned
*  each emitting the usage text in either the short (error exit)
*  style or the long style.  The generated program will capture this
*  and create shell script variables containing the two types of text.
=*/
void
genshelloptUsage(tOptions * opts, int exit_cd)
{
#if ! defined(HAVE_WORKING_FORK)
   optionUsage(opts, exit_cd);
#else
   /*
    *  IF not EXIT_SUCCESS,
    *  THEN emit the short form of usage.
    */
   if (exit_cd != EXIT_SUCCESS)
       optionUsage(opts, exit_cd);
   fflush(stderr);
   fflush(stdout);
   if (ferror(stdout) || ferror(stderr))
       option_exits(EXIT_FAILURE);

   option_usage_fp = stdout;

   /*
    *  First, print our usage
    */
   switch (fork()) {
   case -1:
       optionUsage(opts, EXIT_FAILURE);
       /* FALLTHROUGH */ /* NOTREACHED */

   case 0:
       pagerState = PAGER_STATE_CHILD;
       optionUsage(opts, EXIT_SUCCESS);
       /* FALLTHROUGH */ /* NOTREACHED */

   default:
   {
       int  sts;
       wait(&sts);
   }
   }

   /*
    *  Generate the pzProgName, since optionProcess() normally
    *  gets it from the command line
    */
   {
       char *  pz;
       char ** pp = VOIDP(&(optionParseShellOptions->pzProgName));
       AGDUPSTR(pz, optionParseShellOptions->pzPROGNAME, "prog name");
       *pp = pz;
       while (*pz != NUL) {
           *pz = (char)LOWER(*pz);
           pz++;
       }
   }

   /*
    *  Separate the makeshell usage from the client usage
    */
   fprintf(option_usage_fp, zGenshell, optionParseShellOptions->pzProgName);
   fflush(option_usage_fp);

   /*
    *  Now, print the client usage.
    */
   switch (fork()) {
   case 0:
       pagerState = PAGER_STATE_CHILD;
       /*FALLTHROUGH*/
   case -1:
       optionUsage(optionParseShellOptions, EXIT_FAILURE);
       /* FALLTHROUGH */ /* NOTREACHED */

   default:
   {
       int  sts;
       wait(&sts);
   }
   }

   fflush(stdout);
   if (ferror(stdout))
       fserr_exit(opts->pzProgName, zwriting, zstdout_name);

   option_exits(EXIT_SUCCESS);
#endif
}

/** @}
*
* Local Variables:
* mode: C
* c-file-style: "stroustrup"
* indent-tabs-mode: nil
* End:
* end of autoopts/makeshell.c */