1181834Sroberto
2285612Sdelphij/**
3285612Sdelphij * \file makeshell.c
4181834Sroberto *
5181834Sroberto *  This module will interpret the options set in the tOptions
6181834Sroberto *  structure and create a Bourne shell script capable of parsing them.
7285612Sdelphij *
8285612Sdelphij * @addtogroup autoopts
9285612Sdelphij * @{
10181834Sroberto */
11181834Sroberto/*
12285612Sdelphij *  This file is part of AutoOpts, a companion to AutoGen.
13285612Sdelphij *  AutoOpts is free software.
14285612Sdelphij *  AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
15181834Sroberto *
16285612Sdelphij *  AutoOpts is available under any one of two licenses.  The license
17285612Sdelphij *  in use must be one of these two and the choice is under the control
18285612Sdelphij *  of the user of the license.
19181834Sroberto *
20285612Sdelphij *   The GNU Lesser General Public License, version 3 or later
21285612Sdelphij *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
22181834Sroberto *
23285612Sdelphij *   The Modified Berkeley Software Distribution License
24285612Sdelphij *      See the file "COPYING.mbsd"
25181834Sroberto *
26285612Sdelphij *  These files have the following sha256 sums:
27181834Sroberto *
28285612Sdelphij *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
29285612Sdelphij *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
30285612Sdelphij *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
31181834Sroberto */
32181834Sroberto
33285612Sdelphij static inline unsigned char to_uchar (char ch) { return ch; }
34181834Sroberto
35285612Sdelphij#define UPPER(_c) (toupper(to_uchar(_c)))
36285612Sdelphij#define LOWER(_c) (tolower(to_uchar(_c)))
37181834Sroberto
38285612Sdelphij/* = = = START-STATIC-FORWARD = = = */
39285612Sdelphijstatic void
40285612Sdelphijemit_var_text(char const * prog, char const * var, int fdin);
41181834Sroberto
42285612Sdelphijstatic void
43285612Sdelphijtext_to_var(tOptions * opts, teTextTo which, tOptDesc * od);
44181834Sroberto
45181834Srobertostatic void
46285612Sdelphijemit_usage(tOptions * opts);
47181834Sroberto
48181834Srobertostatic void
49285612Sdelphijemit_wrapup(tOptions * opts);
50181834Sroberto
51181834Srobertostatic void
52285612Sdelphijemit_setup(tOptions * opts);
53181834Sroberto
54181834Srobertostatic void
55285612Sdelphijemit_action(tOptions * opts, tOptDesc * od);
56181834Sroberto
57181834Srobertostatic void
58285612Sdelphijemit_inaction(tOptions * opts, tOptDesc * od);
59181834Sroberto
60181834Srobertostatic void
61285612Sdelphijemit_flag(tOptions * opts);
62181834Sroberto
63181834Srobertostatic void
64285612Sdelphijemit_match_expr(char const * name, tOptDesc * cod, tOptions * opts);
65181834Sroberto
66181834Srobertostatic void
67285612Sdelphijemit_long(tOptions * opts);
68181834Sroberto
69285612Sdelphijstatic char *
70285612Sdelphijload_old_output(char const * fname, char const * pname);
71285612Sdelphij
72181834Srobertostatic void
73285612Sdelphijopen_out(char const * fname, char const * pname);
74181834Sroberto/* = = = END-STATIC-FORWARD = = = */
75181834Sroberto
76285612SdelphijLOCAL noreturn void
77285612Sdelphijoption_exits(int exit_code)
78285612Sdelphij{
79285612Sdelphij    if (print_exit)
80285612Sdelphij        printf("\nexit %d\n", exit_code);
81285612Sdelphij    exit(exit_code);
82285612Sdelphij}
83285612Sdelphij
84285612SdelphijLOCAL noreturn void
85285612Sdelphijao_bug(char const * msg)
86285612Sdelphij{
87285612Sdelphij    fprintf(stderr, zao_bug_msg, msg);
88285612Sdelphij    option_exits(EX_SOFTWARE);
89285612Sdelphij}
90285612Sdelphij
91285612SdelphijLOCAL void
92285612Sdelphijfserr_warn(char const * prog, char const * op, char const * fname)
93285612Sdelphij{
94285612Sdelphij    fprintf(stderr, zfserr_fmt, prog, errno, strerror(errno),
95285612Sdelphij            op, fname);
96285612Sdelphij}
97285612Sdelphij
98285612SdelphijLOCAL noreturn void
99285612Sdelphijfserr_exit(char const * prog, char const * op, char const * fname)
100285612Sdelphij{
101285612Sdelphij    fserr_warn(prog, op, fname);
102285612Sdelphij    option_exits(EXIT_FAILURE);
103285612Sdelphij}
104285612Sdelphij
105181834Sroberto/*=export_func  optionParseShell
106181834Sroberto * private:
107181834Sroberto *
108181834Sroberto * what:  Decipher a boolean value
109285612Sdelphij * arg:   + tOptions * + pOpts    + program options descriptor +
110181834Sroberto *
111181834Sroberto * doc:
112181834Sroberto *  Emit a shell script that will parse the command line options.
113181834Sroberto=*/
114181834Srobertovoid
115285612SdelphijoptionParseShell(tOptions * opts)
116181834Sroberto{
117181834Sroberto    /*
118181834Sroberto     *  Check for our SHELL option now.
119181834Sroberto     *  IF the output file contains the "#!" magic marker,
120181834Sroberto     *  it will override anything we do here.
121181834Sroberto     */
122285612Sdelphij    if (HAVE_GENSHELL_OPT(SHELL))
123285612Sdelphij        shell_prog = GENSHELL_OPT_ARG(SHELL);
124181834Sroberto
125285612Sdelphij    else if (! ENABLED_GENSHELL_OPT(SHELL))
126285612Sdelphij        shell_prog = NULL;
127181834Sroberto
128285612Sdelphij    else if ((shell_prog = getenv("SHELL")),
129285612Sdelphij             shell_prog == NULL)
130181834Sroberto
131285612Sdelphij        shell_prog = POSIX_SHELL;
132181834Sroberto
133181834Sroberto    /*
134181834Sroberto     *  Check for a specified output file
135181834Sroberto     */
136285612Sdelphij    if (HAVE_GENSHELL_OPT(SCRIPT))
137285612Sdelphij        open_out(GENSHELL_OPT_ARG(SCRIPT), opts->pzProgName);
138285612Sdelphij
139285612Sdelphij    emit_usage(opts);
140285612Sdelphij    emit_setup(opts);
141181834Sroberto
142181834Sroberto    /*
143181834Sroberto     *  There are four modes of option processing.
144181834Sroberto     */
145285612Sdelphij    switch (opts->fOptSet & (OPTPROC_LONGOPT|OPTPROC_SHORTOPT)) {
146181834Sroberto    case OPTPROC_LONGOPT:
147285612Sdelphij        fputs(LOOP_STR,         stdout);
148181834Sroberto
149285612Sdelphij        fputs(LONG_OPT_MARK,    stdout);
150285612Sdelphij        fputs(INIT_LOPT_STR,    stdout);
151285612Sdelphij        emit_long(opts);
152285612Sdelphij        printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
153285612Sdelphij        fputs(END_OPT_SEL_STR,  stdout);
154181834Sroberto
155285612Sdelphij        fputs(NOT_FOUND_STR,    stdout);
156181834Sroberto        break;
157181834Sroberto
158181834Sroberto    case 0:
159285612Sdelphij        fputs(ONLY_OPTS_LOOP,   stdout);
160285612Sdelphij        fputs(INIT_LOPT_STR,    stdout);
161285612Sdelphij        emit_long(opts);
162285612Sdelphij        printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
163181834Sroberto        break;
164181834Sroberto
165181834Sroberto    case OPTPROC_SHORTOPT:
166285612Sdelphij        fputs(LOOP_STR,         stdout);
167181834Sroberto
168285612Sdelphij        fputs(FLAG_OPT_MARK,    stdout);
169285612Sdelphij        fputs(INIT_OPT_STR,     stdout);
170285612Sdelphij        emit_flag(opts);
171285612Sdelphij        printf(OPT_ARG_FMT,     opts->pzPROGNAME);
172285612Sdelphij        fputs(END_OPT_SEL_STR,  stdout);
173181834Sroberto
174285612Sdelphij        fputs(NOT_FOUND_STR,    stdout);
175181834Sroberto        break;
176181834Sroberto
177181834Sroberto    case OPTPROC_LONGOPT|OPTPROC_SHORTOPT:
178285612Sdelphij        fputs(LOOP_STR,         stdout);
179181834Sroberto
180285612Sdelphij        fputs(LONG_OPT_MARK,    stdout);
181285612Sdelphij        fputs(INIT_LOPT_STR,    stdout);
182285612Sdelphij        emit_long(opts);
183285612Sdelphij        printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
184285612Sdelphij        fputs(END_OPT_SEL_STR,  stdout);
185181834Sroberto
186285612Sdelphij        fputs(FLAG_OPT_MARK,    stdout);
187285612Sdelphij        fputs(INIT_OPT_STR,     stdout);
188285612Sdelphij        emit_flag(opts);
189285612Sdelphij        printf(OPT_ARG_FMT,     opts->pzPROGNAME);
190285612Sdelphij        fputs(END_OPT_SEL_STR,  stdout);
191181834Sroberto
192285612Sdelphij        fputs(NOT_FOUND_STR,    stdout);
193181834Sroberto        break;
194181834Sroberto    }
195181834Sroberto
196285612Sdelphij    emit_wrapup(opts);
197285612Sdelphij    if ((script_trailer != NULL) && (*script_trailer != NUL))
198285612Sdelphij        fputs(script_trailer, stdout);
199285612Sdelphij    else if (ENABLED_GENSHELL_OPT(SHELL))
200285612Sdelphij        printf(SHOW_PROG_ENV, opts->pzPROGNAME);
201181834Sroberto
202285612Sdelphij#ifdef HAVE_FCHMOD
203285612Sdelphij    fchmod(STDOUT_FILENO, 0755);
204285612Sdelphij#endif
205285612Sdelphij    fclose(stdout);
206285612Sdelphij
207285612Sdelphij    if (ferror(stdout))
208285612Sdelphij        fserr_exit(opts->pzProgName, zwriting, zstdout_name);
209285612Sdelphij
210285612Sdelphij    AGFREE(script_text);
211285612Sdelphij    script_leader    = NULL;
212285612Sdelphij    script_trailer   = NULL;
213285612Sdelphij    script_text      = NULL;
214181834Sroberto}
215181834Sroberto
216285612Sdelphij#ifdef HAVE_WORKING_FORK
217285612Sdelphij/**
218285612Sdelphij * Print the value of "var" to a file descriptor.
219285612Sdelphij * The "fdin" is the read end of a pipe to a forked process that
220285612Sdelphij * is writing usage text to it.  We read that text in and re-emit
221285612Sdelphij * to standard out, formatting it so that it is assigned to a
222285612Sdelphij * shell variable.
223285612Sdelphij *
224285612Sdelphij * @param[in] prog  The capitalized, c-variable-formatted program name
225285612Sdelphij * @param[in] var   a similarly formatted type name
226285612Sdelphij *                  (LONGUSAGE, USAGE or VERSION)
227285612Sdelphij * @param[in] fdin  the input end of a pipe
228285612Sdelphij */
229285612Sdelphijstatic void
230285612Sdelphijemit_var_text(char const * prog, char const * var, int fdin)
231285612Sdelphij{
232285612Sdelphij    FILE * fp   = fdopen(fdin, "r" FOPEN_BINARY_FLAG);
233285612Sdelphij    int    nlct = 0; /* defer newlines and skip trailing ones */
234181834Sroberto
235285612Sdelphij    printf(SET_TEXT_FMT, prog, var);
236285612Sdelphij    if (fp == NULL)
237285612Sdelphij        goto skip_text;
238285612Sdelphij
239285612Sdelphij    for (;;) {
240285612Sdelphij        int  ch = fgetc(fp);
241285612Sdelphij        switch (ch) {
242285612Sdelphij
243285612Sdelphij        case NL:
244285612Sdelphij            nlct++;
245285612Sdelphij            break;
246285612Sdelphij
247285612Sdelphij        case '\'':
248285612Sdelphij            while (nlct > 0) {
249285612Sdelphij                fputc(NL, stdout);
250285612Sdelphij                nlct--;
251285612Sdelphij            }
252285612Sdelphij            fputs(apostrophe, stdout);
253285612Sdelphij            break;
254285612Sdelphij
255285612Sdelphij        case EOF:
256285612Sdelphij            goto done;
257285612Sdelphij
258285612Sdelphij        default:
259285612Sdelphij            while (nlct > 0) {
260285612Sdelphij                fputc(NL, stdout);
261285612Sdelphij                nlct--;
262285612Sdelphij            }
263285612Sdelphij            fputc(ch, stdout);
264285612Sdelphij            break;
265285612Sdelphij        }
266285612Sdelphij    } done:;
267285612Sdelphij
268285612Sdelphij    fclose(fp);
269285612Sdelphij
270285612Sdelphij skip_text:
271285612Sdelphij
272285612Sdelphij    fputs(END_SET_TEXT, stdout);
273285612Sdelphij}
274285612Sdelphij#endif
275285612Sdelphij
276285612Sdelphij/**
277285612Sdelphij *  The purpose of this function is to assign "long usage", short usage
278285612Sdelphij *  and version information to a shell variable.  Rather than wind our
279285612Sdelphij *  way through all the logic necessary to emit the text directly, we
280285612Sdelphij *  fork(), have our child process emit the text the normal way and
281285612Sdelphij *  capture the output in the parent process.
282285612Sdelphij *
283285612Sdelphij * @param[in] opts  the program options
284285612Sdelphij * @param[in] which what to print: long usage, usage or version
285285612Sdelphij * @param[in] od    for TT_VERSION, it is the version option
286285612Sdelphij */
287181834Srobertostatic void
288285612Sdelphijtext_to_var(tOptions * opts, teTextTo which, tOptDesc * od)
289181834Sroberto{
290285612Sdelphij#   define _TT_(n) static char const z ## n [] = #n;
291181834Sroberto    TEXTTO_TABLE
292181834Sroberto#   undef _TT_
293181834Sroberto#   define _TT_(n) z ## n ,
294285612Sdelphij      static char const * ttnames[] = { TEXTTO_TABLE };
295181834Sroberto#   undef _TT_
296181834Sroberto
297285612Sdelphij#if ! defined(HAVE_WORKING_FORK)
298285612Sdelphij    printf(SET_NO_TEXT_FMT, opts->pzPROGNAME, ttnames[which]);
299181834Sroberto#else
300285612Sdelphij    int  fdpair[2];
301181834Sroberto
302285612Sdelphij    fflush(stdout);
303285612Sdelphij    fflush(stderr);
304181834Sroberto
305285612Sdelphij    if (pipe(fdpair) != 0)
306285612Sdelphij        fserr_exit(opts->pzProgName, "pipe", zinter_proc_pipe);
307181834Sroberto
308181834Sroberto    switch (fork()) {
309181834Sroberto    case -1:
310285612Sdelphij        fserr_exit(opts->pzProgName, "fork", opts->pzProgName);
311285612Sdelphij        /* NOTREACHED */
312181834Sroberto
313181834Sroberto    case 0:
314285612Sdelphij        /*
315285612Sdelphij         * Send both stderr and stdout to the pipe.  No matter which
316285612Sdelphij         * descriptor is used, we capture the output on the read end.
317285612Sdelphij         */
318285612Sdelphij        dup2(fdpair[1], STDERR_FILENO);
319285612Sdelphij        dup2(fdpair[1], STDOUT_FILENO);
320285612Sdelphij        close(fdpair[0]);
321181834Sroberto
322285612Sdelphij        switch (which) {
323181834Sroberto        case TT_LONGUSAGE:
324285612Sdelphij            (*(opts->pUsageProc))(opts, EXIT_SUCCESS);
325181834Sroberto            /* NOTREACHED */
326181834Sroberto
327181834Sroberto        case TT_USAGE:
328285612Sdelphij            (*(opts->pUsageProc))(opts, EXIT_FAILURE);
329181834Sroberto            /* NOTREACHED */
330181834Sroberto
331181834Sroberto        case TT_VERSION:
332285612Sdelphij            if (od->fOptState & OPTST_ALLOC_ARG) {
333285612Sdelphij                AGFREE(od->optArg.argString);
334285612Sdelphij                od->fOptState &= ~OPTST_ALLOC_ARG;
335181834Sroberto            }
336285612Sdelphij            od->optArg.argString = "c";
337285612Sdelphij            optionPrintVersion(opts, od);
338181834Sroberto            /* NOTREACHED */
339181834Sroberto
340181834Sroberto        default:
341285612Sdelphij            option_exits(EXIT_FAILURE);
342285612Sdelphij            /* NOTREACHED */
343181834Sroberto        }
344285612Sdelphij        /* NOTREACHED */
345181834Sroberto
346181834Sroberto    default:
347285612Sdelphij        close(fdpair[1]);
348181834Sroberto    }
349181834Sroberto
350285612Sdelphij    emit_var_text(opts->pzPROGNAME, ttnames[which], fdpair[0]);
351181834Sroberto#endif
352181834Sroberto}
353181834Sroberto
354285612Sdelphij/**
355285612Sdelphij * capture usage text in shell variables.
356285612Sdelphij *
357285612Sdelphij */
358181834Srobertostatic void
359285612Sdelphijemit_usage(tOptions * opts)
360181834Sroberto{
361285612Sdelphij    char tm_nm_buf[AO_NAME_SIZE];
362181834Sroberto
363181834Sroberto    /*
364181834Sroberto     *  First, switch stdout to the output file name.
365181834Sroberto     *  Then, change the program name to the one defined
366181834Sroberto     *  by the definitions (rather than the current
367181834Sroberto     *  executable name).  Down case the upper cased name.
368181834Sroberto     */
369285612Sdelphij    if (script_leader != NULL)
370285612Sdelphij        fputs(script_leader, stdout);
371181834Sroberto
372181834Sroberto    {
373285612Sdelphij        char const * out_nm;
374181834Sroberto
375181834Sroberto        {
376285612Sdelphij            time_t    c_tim = time(NULL);
377285612Sdelphij            struct tm * ptm = localtime(&c_tim);
378285612Sdelphij            strftime(tm_nm_buf, AO_NAME_SIZE, TIME_FMT, ptm );
379181834Sroberto        }
380181834Sroberto
381285612Sdelphij        if (HAVE_GENSHELL_OPT(SCRIPT))
382285612Sdelphij             out_nm = GENSHELL_OPT_ARG(SCRIPT);
383285612Sdelphij        else out_nm = STDOUT;
384181834Sroberto
385285612Sdelphij        if ((script_leader == NULL) && (shell_prog != NULL))
386285612Sdelphij            printf(SHELL_MAGIC, shell_prog);
387181834Sroberto
388285612Sdelphij        printf(PREAMBLE_FMT, START_MARK, out_nm, tm_nm_buf);
389181834Sroberto    }
390181834Sroberto
391285612Sdelphij    printf(END_PRE_FMT, opts->pzPROGNAME);
392285612Sdelphij
393181834Sroberto    /*
394285612Sdelphij     *  Get a copy of the original program name in lower case and
395285612Sdelphij     *  fill in an approximation of the program name from it.
396181834Sroberto     */
397181834Sroberto    {
398285612Sdelphij        char *       pzPN = tm_nm_buf;
399285612Sdelphij        char const * pz   = opts->pzPROGNAME;
400285612Sdelphij        char **      pp;
401285612Sdelphij
402285612Sdelphij        /* Copy the program name into the time/name buffer */
403181834Sroberto        for (;;) {
404294569Sdelphij            if ((*pzPN++ = (char)tolower((unsigned char)*pz++)) == NUL)
405181834Sroberto                break;
406181834Sroberto        }
407285612Sdelphij
408285612Sdelphij        pp  = VOIDP(&(opts->pzProgPath));
409285612Sdelphij        *pp = tm_nm_buf;
410285612Sdelphij        pp  = VOIDP(&(opts->pzProgName));
411285612Sdelphij        *pp = tm_nm_buf;
412181834Sroberto    }
413181834Sroberto
414285612Sdelphij    text_to_var(opts, TT_LONGUSAGE, NULL);
415285612Sdelphij    text_to_var(opts, TT_USAGE,     NULL);
416181834Sroberto
417181834Sroberto    {
418285612Sdelphij        tOptDesc * pOptDesc = opts->pOptDesc;
419285612Sdelphij        int        optionCt = opts->optCt;
420181834Sroberto
421181834Sroberto        for (;;) {
422181834Sroberto            if (pOptDesc->pOptProc == optionPrintVersion) {
423285612Sdelphij                text_to_var(opts, TT_VERSION, pOptDesc);
424181834Sroberto                break;
425181834Sroberto            }
426181834Sroberto
427181834Sroberto            if (--optionCt <= 0)
428181834Sroberto                break;
429181834Sroberto            pOptDesc++;
430181834Sroberto        }
431181834Sroberto    }
432181834Sroberto}
433181834Sroberto
434285612Sdelphijstatic void
435285612Sdelphijemit_wrapup(tOptions * opts)
436285612Sdelphij{
437285612Sdelphij    tOptDesc *   od     = opts->pOptDesc;
438285612Sdelphij    int          opt_ct = opts->presetOptCt;
439285612Sdelphij    char const * fmt;
440181834Sroberto
441285612Sdelphij    printf(FINISH_LOOP, opts->pzPROGNAME);
442285612Sdelphij    for (;opt_ct > 0; od++, --opt_ct) {
443285612Sdelphij        /*
444285612Sdelphij         *  Options that are either usage documentation or are compiled out
445285612Sdelphij         *  are not to be processed.
446285612Sdelphij         */
447285612Sdelphij        if (SKIP_OPT(od) || (od->pz_NAME == NULL))
448285612Sdelphij            continue;
449285612Sdelphij
450285612Sdelphij        /*
451285612Sdelphij         *  do not presence check if there is no minimum/must-set
452285612Sdelphij         */
453285612Sdelphij        if ((od->optMinCt == 0) && ((od->fOptState & OPTST_MUST_SET) == 0))
454285612Sdelphij            continue;
455285612Sdelphij
456285612Sdelphij        if (od->optMaxCt > 1)
457285612Sdelphij             fmt = CHK_MIN_COUNT;
458285612Sdelphij        else fmt = CHK_ONE_REQUIRED;
459285612Sdelphij
460285612Sdelphij        {
461285612Sdelphij            int min = (od->optMinCt == 0) ? 1 : od->optMinCt;
462285612Sdelphij            printf(fmt, opts->pzPROGNAME, od->pz_NAME, min);
463285612Sdelphij        }
464285612Sdelphij    }
465285612Sdelphij    fputs(END_MARK, stdout);
466285612Sdelphij}
467285612Sdelphij
468181834Srobertostatic void
469285612Sdelphijemit_setup(tOptions * opts)
470181834Sroberto{
471285612Sdelphij    tOptDesc *   od     = opts->pOptDesc;
472285612Sdelphij    int          opt_ct = opts->presetOptCt;
473285612Sdelphij    char const * fmt;
474285612Sdelphij    char const * def_val;
475181834Sroberto
476285612Sdelphij    for (;opt_ct > 0; od++, --opt_ct) {
477285612Sdelphij        char int_val_buf[32];
478181834Sroberto
479181834Sroberto        /*
480181834Sroberto         *  Options that are either usage documentation or are compiled out
481181834Sroberto         *  are not to be processed.
482181834Sroberto         */
483285612Sdelphij        if (SKIP_OPT(od) || (od->pz_NAME == NULL))
484181834Sroberto            continue;
485181834Sroberto
486285612Sdelphij        if (od->optMaxCt > 1)
487285612Sdelphij             fmt = MULTI_DEF_FMT;
488285612Sdelphij        else fmt = SGL_DEF_FMT;
489181834Sroberto
490181834Sroberto        /*
491181834Sroberto         *  IF this is an enumeration/bitmask option, then convert the value
492181834Sroberto         *  to a string before printing the default value.
493181834Sroberto         */
494285612Sdelphij        switch (OPTST_GET_ARGTYPE(od->fOptState)) {
495181834Sroberto        case OPARG_TYPE_ENUMERATION:
496285612Sdelphij            (*(od->pOptProc))(OPTPROC_EMIT_SHELL, od );
497285612Sdelphij            def_val = od->optArg.argString;
498181834Sroberto            break;
499181834Sroberto
500181834Sroberto        /*
501181834Sroberto         *  Numeric and membership bit options are just printed as a number.
502181834Sroberto         */
503181834Sroberto        case OPARG_TYPE_NUMERIC:
504285612Sdelphij            snprintf(int_val_buf, sizeof(int_val_buf), "%d",
505285612Sdelphij                     (int)od->optArg.argInt);
506285612Sdelphij            def_val = int_val_buf;
507181834Sroberto            break;
508181834Sroberto
509181834Sroberto        case OPARG_TYPE_MEMBERSHIP:
510285612Sdelphij            snprintf(int_val_buf, sizeof(int_val_buf), "%lu",
511285612Sdelphij                     (unsigned long)od->optArg.argIntptr);
512285612Sdelphij            def_val = int_val_buf;
513181834Sroberto            break;
514181834Sroberto
515181834Sroberto        case OPARG_TYPE_BOOLEAN:
516285612Sdelphij            def_val = (od->optArg.argBool) ? TRUE_STR : FALSE_STR;
517181834Sroberto            break;
518181834Sroberto
519181834Sroberto        default:
520285612Sdelphij            if (od->optArg.argString == NULL) {
521285612Sdelphij                if (fmt == SGL_DEF_FMT)
522285612Sdelphij                    fmt = SGL_NO_DEF_FMT;
523285612Sdelphij                def_val = NULL;
524181834Sroberto            }
525181834Sroberto            else
526285612Sdelphij                def_val = od->optArg.argString;
527181834Sroberto        }
528181834Sroberto
529285612Sdelphij        printf(fmt, opts->pzPROGNAME, od->pz_NAME, def_val);
530181834Sroberto    }
531181834Sroberto}
532181834Sroberto
533181834Srobertostatic void
534285612Sdelphijemit_action(tOptions * opts, tOptDesc * od)
535181834Sroberto{
536285612Sdelphij    if (od->pOptProc == optionPrintVersion)
537285612Sdelphij        printf(ECHO_N_EXIT, opts->pzPROGNAME, VER_STR);
538181834Sroberto
539285612Sdelphij    else if (od->pOptProc == optionPagedUsage)
540285612Sdelphij        printf(PAGE_USAGE_TEXT, opts->pzPROGNAME);
541181834Sroberto
542285612Sdelphij    else if (od->pOptProc == optionLoadOpt) {
543285612Sdelphij        printf(LVL3_CMD, NO_LOAD_WARN);
544285612Sdelphij        printf(LVL3_CMD, YES_NEED_OPT_ARG);
545181834Sroberto
546285612Sdelphij    } else if (od->pz_NAME == NULL) {
547181834Sroberto
548285612Sdelphij        if (od->pOptProc == NULL) {
549285612Sdelphij            printf(LVL3_CMD, NO_SAVE_OPTS);
550285612Sdelphij            printf(LVL3_CMD, OK_NEED_OPT_ARG);
551181834Sroberto        } else
552285612Sdelphij            printf(ECHO_N_EXIT, opts->pzPROGNAME, LONG_USE_STR);
553181834Sroberto
554181834Sroberto    } else {
555285612Sdelphij        if (od->optMaxCt == 1)
556285612Sdelphij            printf(SGL_ARG_FMT, opts->pzPROGNAME, od->pz_NAME);
557181834Sroberto        else {
558285612Sdelphij            if ((unsigned)od->optMaxCt < NOLIMIT)
559285612Sdelphij                printf(CHK_MAX_COUNT, opts->pzPROGNAME,
560285612Sdelphij                       od->pz_NAME, od->optMaxCt);
561181834Sroberto
562285612Sdelphij            printf(MULTI_ARG_FMT, opts->pzPROGNAME, od->pz_NAME);
563181834Sroberto        }
564181834Sroberto
565181834Sroberto        /*
566181834Sroberto         *  Fix up the args.
567181834Sroberto         */
568285612Sdelphij        if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NONE) {
569285612Sdelphij            printf(SET_MULTI_ARG, opts->pzPROGNAME, od->pz_NAME);
570285612Sdelphij            printf(LVL3_CMD, NO_ARG_NEEDED);
571181834Sroberto
572285612Sdelphij        } else if (od->fOptState & OPTST_ARG_OPTIONAL) {
573285612Sdelphij            printf(SET_MULTI_ARG,  opts->pzPROGNAME, od->pz_NAME);
574285612Sdelphij            printf(LVL3_CMD, OK_NEED_OPT_ARG);
575181834Sroberto
576181834Sroberto        } else {
577285612Sdelphij            printf(LVL3_CMD, YES_NEED_OPT_ARG);
578181834Sroberto        }
579181834Sroberto    }
580285612Sdelphij    fputs(zOptionEndSelect, stdout);
581181834Sroberto}
582181834Sroberto
583181834Srobertostatic void
584285612Sdelphijemit_inaction(tOptions * opts, tOptDesc * od)
585181834Sroberto{
586285612Sdelphij    if (od->pOptProc == optionLoadOpt) {
587285612Sdelphij        printf(LVL3_CMD, NO_SUPPRESS_LOAD);
588181834Sroberto
589285612Sdelphij    } else if (od->optMaxCt == 1)
590285612Sdelphij        printf(NO_SGL_ARG_FMT, opts->pzPROGNAME,
591285612Sdelphij               od->pz_NAME, od->pz_DisablePfx);
592181834Sroberto    else
593285612Sdelphij        printf(NO_MULTI_ARG_FMT, opts->pzPROGNAME,
594285612Sdelphij               od->pz_NAME, od->pz_DisablePfx);
595181834Sroberto
596285612Sdelphij    printf(LVL3_CMD, NO_ARG_NEEDED);
597285612Sdelphij    fputs(zOptionEndSelect, stdout);
598181834Sroberto}
599181834Sroberto
600285612Sdelphij/**
601285612Sdelphij * recognize flag options.  These go at the end.
602285612Sdelphij * At the end, emit code to handle options we don't recognize.
603285612Sdelphij *
604285612Sdelphij * @param[in] opts  the program options
605285612Sdelphij */
606181834Srobertostatic void
607285612Sdelphijemit_flag(tOptions * opts)
608181834Sroberto{
609285612Sdelphij    tOptDesc * od = opts->pOptDesc;
610285612Sdelphij    int        opt_ct = opts->optCt;
611181834Sroberto
612285612Sdelphij    fputs(zOptionCase, stdout);
613181834Sroberto
614285612Sdelphij    for (;opt_ct > 0; od++, --opt_ct) {
615181834Sroberto
616285612Sdelphij        if (SKIP_OPT(od) || ! IS_GRAPHIC_CHAR(od->optValue))
617181834Sroberto            continue;
618181834Sroberto
619285612Sdelphij        printf(zOptionFlag, od->optValue);
620285612Sdelphij        emit_action(opts, od);
621181834Sroberto    }
622285612Sdelphij    printf(UNK_OPT_FMT, FLAG_STR, opts->pzPROGNAME);
623181834Sroberto}
624181834Sroberto
625285612Sdelphij/**
626285612Sdelphij *  Emit the match text for a long option.  The passed in \a name may be
627285612Sdelphij *  either the enablement name or the disablement name.
628285612Sdelphij *
629285612Sdelphij * @param[in] name  The current name to check.
630285612Sdelphij * @param[in] cod   current option descriptor
631285612Sdelphij * @param[in] opts  the program options
632181834Sroberto */
633181834Srobertostatic void
634285612Sdelphijemit_match_expr(char const * name, tOptDesc * cod, tOptions * opts)
635181834Sroberto{
636285612Sdelphij    char name_bf[32];
637285612Sdelphij    unsigned int    min_match_ct = 2;
638285612Sdelphij    unsigned int    max_match_ct = strlen(name) - 1;
639181834Sroberto
640285612Sdelphij    if (max_match_ct >= sizeof(name_bf) - 1)
641285612Sdelphij        goto leave;
642285612Sdelphij
643285612Sdelphij    {
644285612Sdelphij        tOptDesc *  od = opts->pOptDesc;
645285612Sdelphij        int         ct = opts->optCt;
646181834Sroberto
647285612Sdelphij        for (; ct-- > 0; od++) {
648285612Sdelphij            unsigned int match_ct = 0;
649181834Sroberto
650285612Sdelphij            /*
651285612Sdelphij             *  Omit the current option, Doc opts and compiled out opts.
652285612Sdelphij             */
653285612Sdelphij            if ((od == cod) || SKIP_OPT(od))
654285612Sdelphij                continue;
655181834Sroberto
656285612Sdelphij            /*
657285612Sdelphij             *  Check each character of the name case insensitively.
658285612Sdelphij             *  They must not be the same.  They cannot be, because it would
659285612Sdelphij             *  not compile correctly if they were.
660285612Sdelphij             */
661285612Sdelphij            while (UPPER(od->pz_Name[match_ct]) == UPPER(name[match_ct]))
662285612Sdelphij                match_ct++;
663181834Sroberto
664285612Sdelphij            if (match_ct > min_match_ct)
665285612Sdelphij                min_match_ct = match_ct;
666285612Sdelphij
667285612Sdelphij            /*
668285612Sdelphij             *  Check the disablement name, too.
669285612Sdelphij             */
670285612Sdelphij            if (od->pz_DisableName == NULL)
671285612Sdelphij                continue;
672285612Sdelphij
673285612Sdelphij            match_ct = 0;
674294569Sdelphij            while (  toupper((unsigned char)od->pz_DisableName[match_ct])
675294569Sdelphij                  == toupper((unsigned char)name[match_ct]))
676285612Sdelphij                match_ct++;
677285612Sdelphij            if (match_ct > min_match_ct)
678285612Sdelphij                min_match_ct = match_ct;
679181834Sroberto        }
680181834Sroberto    }
681181834Sroberto
682181834Sroberto    /*
683285612Sdelphij     *  Don't bother emitting partial matches if there is only one possible
684285612Sdelphij     *  partial match.
685181834Sroberto     */
686285612Sdelphij    if (min_match_ct < max_match_ct) {
687285612Sdelphij        char *  pz    = name_bf + min_match_ct;
688285612Sdelphij        int     nm_ix = min_match_ct;
689181834Sroberto
690285612Sdelphij        memcpy(name_bf, name, min_match_ct);
691181834Sroberto
692181834Sroberto        for (;;) {
693181834Sroberto            *pz = NUL;
694285612Sdelphij            printf(zOptionPartName, name_bf);
695285612Sdelphij            *pz++ = name[nm_ix++];
696285612Sdelphij            if (name[nm_ix] == NUL) {
697181834Sroberto                *pz = NUL;
698181834Sroberto                break;
699181834Sroberto            }
700181834Sroberto        }
701181834Sroberto    }
702285612Sdelphij
703285612Sdelphijleave:
704285612Sdelphij    printf(zOptionFullName, name);
705181834Sroberto}
706181834Sroberto
707285612Sdelphij/**
708285612Sdelphij *  Emit GNU-standard long option handling code.
709285612Sdelphij *
710285612Sdelphij * @param[in] opts  the program options
711181834Sroberto */
712181834Srobertostatic void
713285612Sdelphijemit_long(tOptions * opts)
714181834Sroberto{
715285612Sdelphij    tOptDesc * od = opts->pOptDesc;
716285612Sdelphij    int        ct  = opts->optCt;
717181834Sroberto
718285612Sdelphij    fputs(zOptionCase, stdout);
719181834Sroberto
720181834Sroberto    /*
721181834Sroberto     *  do each option, ...
722181834Sroberto     */
723181834Sroberto    do  {
724181834Sroberto        /*
725181834Sroberto         *  Documentation & compiled-out options
726181834Sroberto         */
727285612Sdelphij        if (SKIP_OPT(od))
728181834Sroberto            continue;
729181834Sroberto
730285612Sdelphij        emit_match_expr(od->pz_Name, od, opts);
731285612Sdelphij        emit_action(opts, od);
732181834Sroberto
733181834Sroberto        /*
734181834Sroberto         *  Now, do the same thing for the disablement version of the option.
735181834Sroberto         */
736285612Sdelphij        if (od->pz_DisableName != NULL) {
737285612Sdelphij            emit_match_expr(od->pz_DisableName, od, opts);
738285612Sdelphij            emit_inaction(opts, od);
739181834Sroberto        }
740285612Sdelphij    } while (od++, --ct > 0);
741181834Sroberto
742285612Sdelphij    printf(UNK_OPT_FMT, OPTION_STR, opts->pzPROGNAME);
743181834Sroberto}
744181834Sroberto
745285612Sdelphij/**
746285612Sdelphij * Load the previous shell script output file.  We need to preserve any
747285612Sdelphij * hand-edited additions outside of the START_MARK and END_MARKs.
748285612Sdelphij *
749285612Sdelphij * @param[in] fname  the output file name
750285612Sdelphij */
751285612Sdelphijstatic char *
752285612Sdelphijload_old_output(char const * fname, char const * pname)
753181834Sroberto{
754285612Sdelphij    /*
755285612Sdelphij     *  IF we cannot stat the file,
756285612Sdelphij     *  THEN assume we are creating a new file.
757285612Sdelphij     *       Skip the loading of the old data.
758285612Sdelphij     */
759285612Sdelphij    FILE * fp = fopen(fname, "r" FOPEN_BINARY_FLAG);
760181834Sroberto    struct stat stbf;
761285612Sdelphij    char * text;
762285612Sdelphij    char * scan;
763181834Sroberto
764285612Sdelphij    if (fp == NULL)
765285612Sdelphij        return NULL;
766181834Sroberto
767285612Sdelphij    /*
768285612Sdelphij     * If we opened it, we should be able to stat it and it needs
769285612Sdelphij     * to be a regular file
770285612Sdelphij     */
771285612Sdelphij    if ((fstat(fileno(fp), &stbf) != 0) || (! S_ISREG(stbf.st_mode)))
772285612Sdelphij        fserr_exit(pname, "fstat", fname);
773285612Sdelphij
774285612Sdelphij    scan = text = AGALOC(stbf.st_size + 1, "f data");
775285612Sdelphij
776285612Sdelphij    /*
777285612Sdelphij     *  Read in all the data as fast as our OS will let us.
778285612Sdelphij     */
779285612Sdelphij    for (;;) {
780285612Sdelphij        size_t inct = fread(VOIDP(scan), 1, (size_t)stbf.st_size, fp);
781285612Sdelphij        if (inct == 0)
782181834Sroberto            break;
783181834Sroberto
784285612Sdelphij        stbf.st_size -= (ssize_t)inct;
785181834Sroberto
786285612Sdelphij        if (stbf.st_size == 0)
787285612Sdelphij            break;
788181834Sroberto
789285612Sdelphij        scan += inct;
790285612Sdelphij    }
791181834Sroberto
792285612Sdelphij    *scan = NUL;
793285612Sdelphij    fclose(fp);
794181834Sroberto
795285612Sdelphij    return text;
796285612Sdelphij}
797181834Sroberto
798285612Sdelphij/**
799285612Sdelphij * Open the specified output file.  If it already exists, load its
800285612Sdelphij * contents and save the non-generated (hand edited) portions.
801285612Sdelphij * If a "start mark" is found, everything before it is preserved leader.
802285612Sdelphij * If not, the entire thing is a trailer.  Assuming the start is found,
803285612Sdelphij * then everything after the end marker is the trailer.  If the end
804285612Sdelphij * mark is not found, the file is actually corrupt, but we take the
805285612Sdelphij * remainder to be the trailer.
806285612Sdelphij *
807285612Sdelphij * @param[in] fname  the output file name
808285612Sdelphij */
809285612Sdelphijstatic void
810285612Sdelphijopen_out(char const * fname, char const * pname)
811285612Sdelphij{
812181834Sroberto
813285612Sdelphij    do  {
814285612Sdelphij        char * txt = script_text = load_old_output(fname, pname);
815285612Sdelphij        char * scn;
816285612Sdelphij
817285612Sdelphij        if (txt == NULL)
818181834Sroberto            break;
819285612Sdelphij
820285612Sdelphij        scn = strstr(txt, START_MARK);
821285612Sdelphij        if (scn == NULL) {
822285612Sdelphij            script_trailer = txt;
823285612Sdelphij            break;
824181834Sroberto        }
825181834Sroberto
826285612Sdelphij        *(scn++) = NUL;
827285612Sdelphij        scn = strstr(scn, END_MARK);
828285612Sdelphij        if (scn == NULL) {
829285612Sdelphij            /*
830285612Sdelphij             * The file is corrupt.  Set the trailer to be everything
831285612Sdelphij             * after the start mark. The user will need to fix it up.
832285612Sdelphij             */
833285612Sdelphij            script_trailer = txt + strlen(txt) + START_MARK_LEN + 1;
834181834Sroberto            break;
835181834Sroberto        }
836181834Sroberto
837181834Sroberto        /*
838285612Sdelphij         *  Check to see if the data contains our marker.
839285612Sdelphij         *  If it does, then we will skip over it
840181834Sroberto         */
841285612Sdelphij        script_trailer = scn + END_MARK_LEN;
842285612Sdelphij        script_leader  = txt;
843285612Sdelphij    } while (false);
844181834Sroberto
845285612Sdelphij    if (freopen(fname, "w" FOPEN_BINARY_FLAG, stdout) != stdout)
846285612Sdelphij        fserr_exit(pname, "freopen", fname);
847181834Sroberto}
848181834Sroberto
849181834Sroberto/*=export_func genshelloptUsage
850181834Sroberto * private:
851181834Sroberto * what: The usage function for the genshellopt generated program
852181834Sroberto *
853285612Sdelphij * arg:  + tOptions * + opts    + program options descriptor +
854285612Sdelphij * arg:  + int        + exit_cd + usage text type to produce +
855181834Sroberto *
856181834Sroberto * doc:
857181834Sroberto *  This function is used to create the usage strings for the option
858181834Sroberto *  processing shell script code.  Two child processes are spawned
859181834Sroberto *  each emitting the usage text in either the short (error exit)
860181834Sroberto *  style or the long style.  The generated program will capture this
861181834Sroberto *  and create shell script variables containing the two types of text.
862181834Sroberto=*/
863181834Srobertovoid
864285612SdelphijgenshelloptUsage(tOptions * opts, int exit_cd)
865181834Sroberto{
866285612Sdelphij#if ! defined(HAVE_WORKING_FORK)
867285612Sdelphij    optionUsage(opts, exit_cd);
868181834Sroberto#else
869181834Sroberto    /*
870181834Sroberto     *  IF not EXIT_SUCCESS,
871181834Sroberto     *  THEN emit the short form of usage.
872181834Sroberto     */
873285612Sdelphij    if (exit_cd != EXIT_SUCCESS)
874285612Sdelphij        optionUsage(opts, exit_cd);
875285612Sdelphij    fflush(stderr);
876285612Sdelphij    fflush(stdout);
877285612Sdelphij    if (ferror(stdout) || ferror(stderr))
878285612Sdelphij        option_exits(EXIT_FAILURE);
879181834Sroberto
880181834Sroberto    option_usage_fp = stdout;
881181834Sroberto
882181834Sroberto    /*
883181834Sroberto     *  First, print our usage
884181834Sroberto     */
885181834Sroberto    switch (fork()) {
886181834Sroberto    case -1:
887285612Sdelphij        optionUsage(opts, EXIT_FAILURE);
888285612Sdelphij        /* NOTREACHED */
889181834Sroberto
890181834Sroberto    case 0:
891181834Sroberto        pagerState = PAGER_STATE_CHILD;
892285612Sdelphij        optionUsage(opts, EXIT_SUCCESS);
893285612Sdelphij        /* NOTREACHED */
894285612Sdelphij        _exit(EXIT_FAILURE);
895181834Sroberto
896181834Sroberto    default:
897181834Sroberto    {
898181834Sroberto        int  sts;
899285612Sdelphij        wait(&sts);
900181834Sroberto    }
901181834Sroberto    }
902181834Sroberto
903181834Sroberto    /*
904181834Sroberto     *  Generate the pzProgName, since optionProcess() normally
905181834Sroberto     *  gets it from the command line
906181834Sroberto     */
907181834Sroberto    {
908285612Sdelphij        char *  pz;
909285612Sdelphij        char ** pp = VOIDP(&(optionParseShellOptions->pzProgName));
910285612Sdelphij        AGDUPSTR(pz, optionParseShellOptions->pzPROGNAME, "prog name");
911285612Sdelphij        *pp = pz;
912181834Sroberto        while (*pz != NUL) {
913285612Sdelphij            *pz = (char)LOWER(*pz);
914181834Sroberto            pz++;
915181834Sroberto        }
916181834Sroberto    }
917181834Sroberto
918181834Sroberto    /*
919181834Sroberto     *  Separate the makeshell usage from the client usage
920181834Sroberto     */
921285612Sdelphij    fprintf(option_usage_fp, zGenshell, optionParseShellOptions->pzProgName);
922285612Sdelphij    fflush(option_usage_fp);
923181834Sroberto
924181834Sroberto    /*
925181834Sroberto     *  Now, print the client usage.
926181834Sroberto     */
927181834Sroberto    switch (fork()) {
928181834Sroberto    case 0:
929181834Sroberto        pagerState = PAGER_STATE_CHILD;
930181834Sroberto        /*FALLTHROUGH*/
931181834Sroberto    case -1:
932285612Sdelphij        optionUsage(optionParseShellOptions, EXIT_FAILURE);
933181834Sroberto
934181834Sroberto    default:
935181834Sroberto    {
936181834Sroberto        int  sts;
937285612Sdelphij        wait(&sts);
938181834Sroberto    }
939181834Sroberto    }
940181834Sroberto
941285612Sdelphij    fflush(stdout);
942285612Sdelphij    if (ferror(stdout))
943285612Sdelphij        fserr_exit(opts->pzProgName, zwriting, zstdout_name);
944285612Sdelphij
945285612Sdelphij    option_exits(EXIT_SUCCESS);
946181834Sroberto#endif
947181834Sroberto}
948181834Sroberto
949285612Sdelphij/** @}
950285612Sdelphij *
951181834Sroberto * Local Variables:
952181834Sroberto * mode: C
953181834Sroberto * c-file-style: "stroustrup"
954181834Sroberto * indent-tabs-mode: nil
955181834Sroberto * End:
956181834Sroberto * end of autoopts/makeshell.c */
957