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 
to_uchar(char ch)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 = = = */
41 static void
42 emit_var_text(char const * prog, char const * var, int fdin);
43 
44 static void
45 text_to_var(tOptions * opts, teTextTo which, tOptDesc * od);
46 
47 static void
48 emit_usage(tOptions * opts);
49 
50 static void
51 emit_wrapup(tOptions * opts);
52 
53 static void
54 emit_setup(tOptions * opts);
55 
56 static void
57 emit_action(tOptions * opts, tOptDesc * od);
58 
59 static void
60 emit_inaction(tOptions * opts, tOptDesc * od);
61 
62 static void
63 emit_flag(tOptions * opts);
64 
65 static void
66 emit_match_expr(char const * name, tOptDesc * cod, tOptions * opts);
67 
68 static void
69 emit_long(tOptions * opts);
70 
71 static char *
72 load_old_output(char const * fname, char const * pname);
73 
74 static void
75 open_out(char const * fname, char const * pname);
76 /* = = = END-STATIC-FORWARD = = = */
77 
78 LOCAL noreturn void
option_exits(int exit_code)79 option_exits(int exit_code)
80 {
81     if (print_exit)
82         printf("\nexit %d\n", exit_code);
83     exit(exit_code);
84 }
85 
86 LOCAL noreturn void
ao_bug(char const * msg)87 ao_bug(char const * msg)
88 {
89     fprintf(stderr, zao_bug_msg, msg);
90     option_exits(EX_SOFTWARE);
91 }
92 
93 LOCAL void
fserr_warn(char const * prog,char const * op,char const * fname)94 fserr_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 
100 LOCAL noreturn void
fserr_exit(char const * prog,char const * op,char const * fname)101 fserr_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 =*/
116 void
optionParseShell(tOptions * opts)117 optionParseShell(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  */
231 static void
emit_var_text(char const * prog,char const * var,int fdin)232 emit_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  */
289 static void
text_to_var(tOptions * opts,teTextTo which,tOptDesc * od)290 text_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  */
360 static void
emit_usage(tOptions * opts)361 emit_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 
436 static void
emit_wrapup(tOptions * opts)437 emit_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 
470 static void
emit_setup(tOptions * opts)471 emit_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 
535 static void
emit_action(tOptions * opts,tOptDesc * od)536 emit_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 
585 static void
emit_inaction(tOptions * opts,tOptDesc * od)586 emit_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  */
608 static void
emit_flag(tOptions * opts)609 emit_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  */
635 static void
emit_match_expr(char const * name,tOptDesc * cod,tOptions * opts)636 emit_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 
705 leave:
706     printf(zOptionFullName, name);
707 }
708 
709 /**
710  *  Emit GNU-standard long option handling code.
711  *
712  * @param[in] opts  the program options
713  */
714 static void
emit_long(tOptions * opts)715 emit_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  */
753 static char *
load_old_output(char const * fname,char const * pname)754 load_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  */
811 static void
open_out(char const * fname,char const * pname)812 open_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 =*/
865 void
genshelloptUsage(tOptions * opts,int exit_cd)866 genshelloptUsage(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