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