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