xref: /netbsd-src/external/bsd/ntp/dist/sntp/libopts/makeshell.c (revision 8e33eff89e26cf71871ead62f0d5063e1313c33a)
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