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