1181834Sroberto
2290000Sglebius/**
3290000Sglebius * \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.
7290000Sglebius *
8290000Sglebius * @addtogroup autoopts
9290000Sglebius * @{
10181834Sroberto */
11181834Sroberto/*
12290000Sglebius *  This file is part of AutoOpts, a companion to AutoGen.
13290000Sglebius *  AutoOpts is free software.
14290000Sglebius *  AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
15181834Sroberto *
16290000Sglebius *  AutoOpts is available under any one of two licenses.  The license
17290000Sglebius *  in use must be one of these two and the choice is under the control
18290000Sglebius *  of the user of the license.
19181834Sroberto *
20290000Sglebius *   The GNU Lesser General Public License, version 3 or later
21290000Sglebius *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
22181834Sroberto *
23290000Sglebius *   The Modified Berkeley Software Distribution License
24290000Sglebius *      See the file "COPYING.mbsd"
25181834Sroberto *
26290000Sglebius *  These files have the following sha256 sums:
27181834Sroberto *
28290000Sglebius *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
29290000Sglebius *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
30290000Sglebius *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
31181834Sroberto */
32181834Sroberto
33290000Sglebius static inline unsigned char to_uchar (char ch) { return ch; }
34181834Sroberto
35290000Sglebius#define UPPER(_c) (toupper(to_uchar(_c)))
36290000Sglebius#define LOWER(_c) (tolower(to_uchar(_c)))
37181834Sroberto
38290000Sglebius/* = = = START-STATIC-FORWARD = = = */
39290000Sglebiusstatic void
40290000Sglebiusemit_var_text(char const * prog, char const * var, int fdin);
41181834Sroberto
42290000Sglebiusstatic void
43290000Sglebiustext_to_var(tOptions * opts, teTextTo which, tOptDesc * od);
44181834Sroberto
45181834Srobertostatic void
46290000Sglebiusemit_usage(tOptions * opts);
47181834Sroberto
48181834Srobertostatic void
49290000Sglebiusemit_wrapup(tOptions * opts);
50181834Sroberto
51181834Srobertostatic void
52290000Sglebiusemit_setup(tOptions * opts);
53181834Sroberto
54181834Srobertostatic void
55290000Sglebiusemit_action(tOptions * opts, tOptDesc * od);
56181834Sroberto
57181834Srobertostatic void
58290000Sglebiusemit_inaction(tOptions * opts, tOptDesc * od);
59181834Sroberto
60181834Srobertostatic void
61290000Sglebiusemit_flag(tOptions * opts);
62181834Sroberto
63181834Srobertostatic void
64290000Sglebiusemit_match_expr(char const * name, tOptDesc * cod, tOptions * opts);
65181834Sroberto
66181834Srobertostatic void
67290000Sglebiusemit_long(tOptions * opts);
68181834Sroberto
69290000Sglebiusstatic char *
70290000Sglebiusload_old_output(char const * fname, char const * pname);
71290000Sglebius
72181834Srobertostatic void
73290000Sglebiusopen_out(char const * fname, char const * pname);
74181834Sroberto/* = = = END-STATIC-FORWARD = = = */
75181834Sroberto
76290000SglebiusLOCAL noreturn void
77290000Sglebiusoption_exits(int exit_code)
78290000Sglebius{
79290000Sglebius    if (print_exit)
80290000Sglebius        printf("\nexit %d\n", exit_code);
81290000Sglebius    exit(exit_code);
82290000Sglebius}
83290000Sglebius
84290000SglebiusLOCAL noreturn void
85290000Sglebiusao_bug(char const * msg)
86290000Sglebius{
87290000Sglebius    fprintf(stderr, zao_bug_msg, msg);
88290000Sglebius    option_exits(EX_SOFTWARE);
89290000Sglebius}
90290000Sglebius
91290000SglebiusLOCAL void
92290000Sglebiusfserr_warn(char const * prog, char const * op, char const * fname)
93290000Sglebius{
94290000Sglebius    fprintf(stderr, zfserr_fmt, prog, errno, strerror(errno),
95290000Sglebius            op, fname);
96290000Sglebius}
97290000Sglebius
98290000SglebiusLOCAL noreturn void
99290000Sglebiusfserr_exit(char const * prog, char const * op, char const * fname)
100290000Sglebius{
101290000Sglebius    fserr_warn(prog, op, fname);
102290000Sglebius    option_exits(EXIT_FAILURE);
103290000Sglebius}
104290000Sglebius
105181834Sroberto/*=export_func  optionParseShell
106181834Sroberto * private:
107181834Sroberto *
108181834Sroberto * what:  Decipher a boolean value
109290000Sglebius * arg:   + tOptions * + pOpts    + program options descriptor +
110181834Sroberto *
111181834Sroberto * doc:
112181834Sroberto *  Emit a shell script that will parse the command line options.
113181834Sroberto=*/
114181834Srobertovoid
115290000SglebiusoptionParseShell(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     */
122290000Sglebius    if (HAVE_GENSHELL_OPT(SHELL))
123290000Sglebius        shell_prog = GENSHELL_OPT_ARG(SHELL);
124181834Sroberto
125290000Sglebius    else if (! ENABLED_GENSHELL_OPT(SHELL))
126290000Sglebius        shell_prog = NULL;
127181834Sroberto
128290000Sglebius    else if ((shell_prog = getenv("SHELL")),
129290000Sglebius             shell_prog == NULL)
130181834Sroberto
131290000Sglebius        shell_prog = POSIX_SHELL;
132181834Sroberto
133181834Sroberto    /*
134181834Sroberto     *  Check for a specified output file
135181834Sroberto     */
136290000Sglebius    if (HAVE_GENSHELL_OPT(SCRIPT))
137290000Sglebius        open_out(GENSHELL_OPT_ARG(SCRIPT), opts->pzProgName);
138290000Sglebius
139290000Sglebius    emit_usage(opts);
140290000Sglebius    emit_setup(opts);
141181834Sroberto
142181834Sroberto    /*
143181834Sroberto     *  There are four modes of option processing.
144181834Sroberto     */
145290000Sglebius    switch (opts->fOptSet & (OPTPROC_LONGOPT|OPTPROC_SHORTOPT)) {
146181834Sroberto    case OPTPROC_LONGOPT:
147290000Sglebius        fputs(LOOP_STR,         stdout);
148181834Sroberto
149290000Sglebius        fputs(LONG_OPT_MARK,    stdout);
150290000Sglebius        fputs(INIT_LOPT_STR,    stdout);
151290000Sglebius        emit_long(opts);
152290000Sglebius        printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
153290000Sglebius        fputs(END_OPT_SEL_STR,  stdout);
154181834Sroberto
155290000Sglebius        fputs(NOT_FOUND_STR,    stdout);
156181834Sroberto        break;
157181834Sroberto
158181834Sroberto    case 0:
159290000Sglebius        fputs(ONLY_OPTS_LOOP,   stdout);
160290000Sglebius        fputs(INIT_LOPT_STR,    stdout);
161290000Sglebius        emit_long(opts);
162290000Sglebius        printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
163181834Sroberto        break;
164181834Sroberto
165181834Sroberto    case OPTPROC_SHORTOPT:
166290000Sglebius        fputs(LOOP_STR,         stdout);
167181834Sroberto
168290000Sglebius        fputs(FLAG_OPT_MARK,    stdout);
169290000Sglebius        fputs(INIT_OPT_STR,     stdout);
170290000Sglebius        emit_flag(opts);
171290000Sglebius        printf(OPT_ARG_FMT,     opts->pzPROGNAME);
172290000Sglebius        fputs(END_OPT_SEL_STR,  stdout);
173181834Sroberto
174290000Sglebius        fputs(NOT_FOUND_STR,    stdout);
175181834Sroberto        break;
176181834Sroberto
177181834Sroberto    case OPTPROC_LONGOPT|OPTPROC_SHORTOPT:
178290000Sglebius        fputs(LOOP_STR,         stdout);
179181834Sroberto
180290000Sglebius        fputs(LONG_OPT_MARK,    stdout);
181290000Sglebius        fputs(INIT_LOPT_STR,    stdout);
182290000Sglebius        emit_long(opts);
183290000Sglebius        printf(LOPT_ARG_FMT,    opts->pzPROGNAME);
184290000Sglebius        fputs(END_OPT_SEL_STR,  stdout);
185181834Sroberto
186290000Sglebius        fputs(FLAG_OPT_MARK,    stdout);
187290000Sglebius        fputs(INIT_OPT_STR,     stdout);
188290000Sglebius        emit_flag(opts);
189290000Sglebius        printf(OPT_ARG_FMT,     opts->pzPROGNAME);
190290000Sglebius        fputs(END_OPT_SEL_STR,  stdout);
191181834Sroberto
192290000Sglebius        fputs(NOT_FOUND_STR,    stdout);
193181834Sroberto        break;
194181834Sroberto    }
195181834Sroberto
196290000Sglebius    emit_wrapup(opts);
197290000Sglebius    if ((script_trailer != NULL) && (*script_trailer != NUL))
198290000Sglebius        fputs(script_trailer, stdout);
199290000Sglebius    else if (ENABLED_GENSHELL_OPT(SHELL))
200290000Sglebius        printf(SHOW_PROG_ENV, opts->pzPROGNAME);
201181834Sroberto
202290000Sglebius#ifdef HAVE_FCHMOD
203290000Sglebius    fchmod(STDOUT_FILENO, 0755);
204290000Sglebius#endif
205290000Sglebius    fclose(stdout);
206290000Sglebius
207290000Sglebius    if (ferror(stdout))
208290000Sglebius        fserr_exit(opts->pzProgName, zwriting, zstdout_name);
209290000Sglebius
210290000Sglebius    AGFREE(script_text);
211290000Sglebius    script_leader    = NULL;
212290000Sglebius    script_trailer   = NULL;
213290000Sglebius    script_text      = NULL;
214181834Sroberto}
215181834Sroberto
216290000Sglebius#ifdef HAVE_WORKING_FORK
217290000Sglebius/**
218290000Sglebius * Print the value of "var" to a file descriptor.
219290000Sglebius * The "fdin" is the read end of a pipe to a forked process that
220290000Sglebius * is writing usage text to it.  We read that text in and re-emit
221290000Sglebius * to standard out, formatting it so that it is assigned to a
222290000Sglebius * shell variable.
223290000Sglebius *
224290000Sglebius * @param[in] prog  The capitalized, c-variable-formatted program name
225290000Sglebius * @param[in] var   a similarly formatted type name
226290000Sglebius *                  (LONGUSAGE, USAGE or VERSION)
227290000Sglebius * @param[in] fdin  the input end of a pipe
228290000Sglebius */
229290000Sglebiusstatic void
230290000Sglebiusemit_var_text(char const * prog, char const * var, int fdin)
231290000Sglebius{
232290000Sglebius    FILE * fp   = fdopen(fdin, "r" FOPEN_BINARY_FLAG);
233290000Sglebius    int    nlct = 0; /* defer newlines and skip trailing ones */
234181834Sroberto
235290000Sglebius    printf(SET_TEXT_FMT, prog, var);
236290000Sglebius    if (fp == NULL)
237290000Sglebius        goto skip_text;
238290000Sglebius
239290000Sglebius    for (;;) {
240290000Sglebius        int  ch = fgetc(fp);
241290000Sglebius        switch (ch) {
242290000Sglebius
243290000Sglebius        case NL:
244290000Sglebius            nlct++;
245290000Sglebius            break;
246290000Sglebius
247290000Sglebius        case '\'':
248290000Sglebius            while (nlct > 0) {
249290000Sglebius                fputc(NL, stdout);
250290000Sglebius                nlct--;
251290000Sglebius            }
252290000Sglebius            fputs(apostrophe, stdout);
253290000Sglebius            break;
254290000Sglebius
255290000Sglebius        case EOF:
256290000Sglebius            goto done;
257290000Sglebius
258290000Sglebius        default:
259290000Sglebius            while (nlct > 0) {
260290000Sglebius                fputc(NL, stdout);
261290000Sglebius                nlct--;
262290000Sglebius            }
263290000Sglebius            fputc(ch, stdout);
264290000Sglebius            break;
265290000Sglebius        }
266290000Sglebius    } done:;
267290000Sglebius
268290000Sglebius    fclose(fp);
269290000Sglebius
270290000Sglebius skip_text:
271290000Sglebius
272290000Sglebius    fputs(END_SET_TEXT, stdout);
273290000Sglebius}
274290000Sglebius#endif
275290000Sglebius
276290000Sglebius/**
277290000Sglebius *  The purpose of this function is to assign "long usage", short usage
278290000Sglebius *  and version information to a shell variable.  Rather than wind our
279290000Sglebius *  way through all the logic necessary to emit the text directly, we
280290000Sglebius *  fork(), have our child process emit the text the normal way and
281290000Sglebius *  capture the output in the parent process.
282290000Sglebius *
283290000Sglebius * @param[in] opts  the program options
284290000Sglebius * @param[in] which what to print: long usage, usage or version
285290000Sglebius * @param[in] od    for TT_VERSION, it is the version option
286290000Sglebius */
287181834Srobertostatic void
288290000Sglebiustext_to_var(tOptions * opts, teTextTo which, tOptDesc * od)
289181834Sroberto{
290290000Sglebius#   define _TT_(n) static char const z ## n [] = #n;
291181834Sroberto    TEXTTO_TABLE
292181834Sroberto#   undef _TT_
293181834Sroberto#   define _TT_(n) z ## n ,
294290000Sglebius      static char const * ttnames[] = { TEXTTO_TABLE };
295181834Sroberto#   undef _TT_
296181834Sroberto
297290000Sglebius#if ! defined(HAVE_WORKING_FORK)
298290000Sglebius    printf(SET_NO_TEXT_FMT, opts->pzPROGNAME, ttnames[which]);
299181834Sroberto#else
300290000Sglebius    int  fdpair[2];
301181834Sroberto
302290000Sglebius    fflush(stdout);
303290000Sglebius    fflush(stderr);
304181834Sroberto
305290000Sglebius    if (pipe(fdpair) != 0)
306290000Sglebius        fserr_exit(opts->pzProgName, "pipe", zinter_proc_pipe);
307181834Sroberto
308181834Sroberto    switch (fork()) {
309181834Sroberto    case -1:
310290000Sglebius        fserr_exit(opts->pzProgName, "fork", opts->pzProgName);
311290000Sglebius        /* NOTREACHED */
312181834Sroberto
313181834Sroberto    case 0:
314290000Sglebius        /*
315290000Sglebius         * Send both stderr and stdout to the pipe.  No matter which
316290000Sglebius         * descriptor is used, we capture the output on the read end.
317290000Sglebius         */
318290000Sglebius        dup2(fdpair[1], STDERR_FILENO);
319290000Sglebius        dup2(fdpair[1], STDOUT_FILENO);
320290000Sglebius        close(fdpair[0]);
321181834Sroberto
322290000Sglebius        switch (which) {
323181834Sroberto        case TT_LONGUSAGE:
324290000Sglebius            (*(opts->pUsageProc))(opts, EXIT_SUCCESS);
325181834Sroberto            /* NOTREACHED */
326181834Sroberto
327181834Sroberto        case TT_USAGE:
328290000Sglebius            (*(opts->pUsageProc))(opts, EXIT_FAILURE);
329181834Sroberto            /* NOTREACHED */
330181834Sroberto
331181834Sroberto        case TT_VERSION:
332290000Sglebius            if (od->fOptState & OPTST_ALLOC_ARG) {
333290000Sglebius                AGFREE(od->optArg.argString);
334290000Sglebius                od->fOptState &= ~OPTST_ALLOC_ARG;
335181834Sroberto            }
336290000Sglebius            od->optArg.argString = "c";
337290000Sglebius            optionPrintVersion(opts, od);
338181834Sroberto            /* NOTREACHED */
339181834Sroberto
340181834Sroberto        default:
341290000Sglebius            option_exits(EXIT_FAILURE);
342290000Sglebius            /* NOTREACHED */
343181834Sroberto        }
344290000Sglebius        /* NOTREACHED */
345181834Sroberto
346181834Sroberto    default:
347290000Sglebius        close(fdpair[1]);
348181834Sroberto    }
349181834Sroberto
350290000Sglebius    emit_var_text(opts->pzPROGNAME, ttnames[which], fdpair[0]);
351181834Sroberto#endif
352181834Sroberto}
353181834Sroberto
354290000Sglebius/**
355290000Sglebius * capture usage text in shell variables.
356290000Sglebius *
357290000Sglebius */
358181834Srobertostatic void
359290000Sglebiusemit_usage(tOptions * opts)
360181834Sroberto{
361290000Sglebius    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     */
369290000Sglebius    if (script_leader != NULL)
370290000Sglebius        fputs(script_leader, stdout);
371181834Sroberto
372181834Sroberto    {
373290000Sglebius        char const * out_nm;
374181834Sroberto
375181834Sroberto        {
376290000Sglebius            time_t    c_tim = time(NULL);
377290000Sglebius            struct tm * ptm = localtime(&c_tim);
378290000Sglebius            strftime(tm_nm_buf, AO_NAME_SIZE, TIME_FMT, ptm );
379181834Sroberto        }
380181834Sroberto
381290000Sglebius        if (HAVE_GENSHELL_OPT(SCRIPT))
382290000Sglebius             out_nm = GENSHELL_OPT_ARG(SCRIPT);
383290000Sglebius        else out_nm = STDOUT;
384181834Sroberto
385290000Sglebius        if ((script_leader == NULL) && (shell_prog != NULL))
386290000Sglebius            printf(SHELL_MAGIC, shell_prog);
387181834Sroberto
388290000Sglebius        printf(PREAMBLE_FMT, START_MARK, out_nm, tm_nm_buf);
389181834Sroberto    }
390181834Sroberto
391290000Sglebius    printf(END_PRE_FMT, opts->pzPROGNAME);
392290000Sglebius
393181834Sroberto    /*
394290000Sglebius     *  Get a copy of the original program name in lower case and
395290000Sglebius     *  fill in an approximation of the program name from it.
396181834Sroberto     */
397181834Sroberto    {
398290000Sglebius        char *       pzPN = tm_nm_buf;
399290000Sglebius        char const * pz   = opts->pzPROGNAME;
400290000Sglebius        char **      pp;
401290000Sglebius
402290000Sglebius        /* Copy the program name into the time/name buffer */
403181834Sroberto        for (;;) {
404294904Sdelphij            if ((*pzPN++ = (char)tolower((unsigned char)*pz++)) == NUL)
405181834Sroberto                break;
406181834Sroberto        }
407290000Sglebius
408290000Sglebius        pp  = VOIDP(&(opts->pzProgPath));
409290000Sglebius        *pp = tm_nm_buf;
410290000Sglebius        pp  = VOIDP(&(opts->pzProgName));
411290000Sglebius        *pp = tm_nm_buf;
412181834Sroberto    }
413181834Sroberto
414290000Sglebius    text_to_var(opts, TT_LONGUSAGE, NULL);
415290000Sglebius    text_to_var(opts, TT_USAGE,     NULL);
416181834Sroberto
417181834Sroberto    {
418290000Sglebius        tOptDesc * pOptDesc = opts->pOptDesc;
419290000Sglebius        int        optionCt = opts->optCt;
420181834Sroberto
421181834Sroberto        for (;;) {
422181834Sroberto            if (pOptDesc->pOptProc == optionPrintVersion) {
423290000Sglebius                text_to_var(opts, TT_VERSION, pOptDesc);
424181834Sroberto                break;
425181834Sroberto            }
426181834Sroberto
427181834Sroberto            if (--optionCt <= 0)
428181834Sroberto                break;
429181834Sroberto            pOptDesc++;
430181834Sroberto        }
431181834Sroberto    }
432181834Sroberto}
433181834Sroberto
434290000Sglebiusstatic void
435290000Sglebiusemit_wrapup(tOptions * opts)
436290000Sglebius{
437290000Sglebius    tOptDesc *   od     = opts->pOptDesc;
438290000Sglebius    int          opt_ct = opts->presetOptCt;
439290000Sglebius    char const * fmt;
440181834Sroberto
441290000Sglebius    printf(FINISH_LOOP, opts->pzPROGNAME);
442290000Sglebius    for (;opt_ct > 0; od++, --opt_ct) {
443290000Sglebius        /*
444290000Sglebius         *  Options that are either usage documentation or are compiled out
445290000Sglebius         *  are not to be processed.
446290000Sglebius         */
447290000Sglebius        if (SKIP_OPT(od) || (od->pz_NAME == NULL))
448290000Sglebius            continue;
449290000Sglebius
450290000Sglebius        /*
451290000Sglebius         *  do not presence check if there is no minimum/must-set
452290000Sglebius         */
453290000Sglebius        if ((od->optMinCt == 0) && ((od->fOptState & OPTST_MUST_SET) == 0))
454290000Sglebius            continue;
455290000Sglebius
456290000Sglebius        if (od->optMaxCt > 1)
457290000Sglebius             fmt = CHK_MIN_COUNT;
458290000Sglebius        else fmt = CHK_ONE_REQUIRED;
459290000Sglebius
460290000Sglebius        {
461290000Sglebius            int min = (od->optMinCt == 0) ? 1 : od->optMinCt;
462290000Sglebius            printf(fmt, opts->pzPROGNAME, od->pz_NAME, min);
463290000Sglebius        }
464290000Sglebius    }
465290000Sglebius    fputs(END_MARK, stdout);
466290000Sglebius}
467290000Sglebius
468181834Srobertostatic void
469290000Sglebiusemit_setup(tOptions * opts)
470181834Sroberto{
471290000Sglebius    tOptDesc *   od     = opts->pOptDesc;
472290000Sglebius    int          opt_ct = opts->presetOptCt;
473290000Sglebius    char const * fmt;
474290000Sglebius    char const * def_val;
475181834Sroberto
476290000Sglebius    for (;opt_ct > 0; od++, --opt_ct) {
477290000Sglebius        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         */
483290000Sglebius        if (SKIP_OPT(od) || (od->pz_NAME == NULL))
484181834Sroberto            continue;
485181834Sroberto
486290000Sglebius        if (od->optMaxCt > 1)
487290000Sglebius             fmt = MULTI_DEF_FMT;
488290000Sglebius        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         */
494290000Sglebius        switch (OPTST_GET_ARGTYPE(od->fOptState)) {
495181834Sroberto        case OPARG_TYPE_ENUMERATION:
496290000Sglebius            (*(od->pOptProc))(OPTPROC_EMIT_SHELL, od );
497290000Sglebius            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:
504290000Sglebius            snprintf(int_val_buf, sizeof(int_val_buf), "%d",
505290000Sglebius                     (int)od->optArg.argInt);
506290000Sglebius            def_val = int_val_buf;
507181834Sroberto            break;
508181834Sroberto
509181834Sroberto        case OPARG_TYPE_MEMBERSHIP:
510290000Sglebius            snprintf(int_val_buf, sizeof(int_val_buf), "%lu",
511290000Sglebius                     (unsigned long)od->optArg.argIntptr);
512290000Sglebius            def_val = int_val_buf;
513181834Sroberto            break;
514181834Sroberto
515181834Sroberto        case OPARG_TYPE_BOOLEAN:
516290000Sglebius            def_val = (od->optArg.argBool) ? TRUE_STR : FALSE_STR;
517181834Sroberto            break;
518181834Sroberto
519181834Sroberto        default:
520290000Sglebius            if (od->optArg.argString == NULL) {
521290000Sglebius                if (fmt == SGL_DEF_FMT)
522290000Sglebius                    fmt = SGL_NO_DEF_FMT;
523290000Sglebius                def_val = NULL;
524181834Sroberto            }
525181834Sroberto            else
526290000Sglebius                def_val = od->optArg.argString;
527181834Sroberto        }
528181834Sroberto
529290000Sglebius        printf(fmt, opts->pzPROGNAME, od->pz_NAME, def_val);
530181834Sroberto    }
531181834Sroberto}
532181834Sroberto
533181834Srobertostatic void
534290000Sglebiusemit_action(tOptions * opts, tOptDesc * od)
535181834Sroberto{
536290000Sglebius    if (od->pOptProc == optionPrintVersion)
537290000Sglebius        printf(ECHO_N_EXIT, opts->pzPROGNAME, VER_STR);
538181834Sroberto
539290000Sglebius    else if (od->pOptProc == optionPagedUsage)
540290000Sglebius        printf(PAGE_USAGE_TEXT, opts->pzPROGNAME);
541181834Sroberto
542290000Sglebius    else if (od->pOptProc == optionLoadOpt) {
543290000Sglebius        printf(LVL3_CMD, NO_LOAD_WARN);
544290000Sglebius        printf(LVL3_CMD, YES_NEED_OPT_ARG);
545181834Sroberto
546290000Sglebius    } else if (od->pz_NAME == NULL) {
547181834Sroberto
548290000Sglebius        if (od->pOptProc == NULL) {
549290000Sglebius            printf(LVL3_CMD, NO_SAVE_OPTS);
550290000Sglebius            printf(LVL3_CMD, OK_NEED_OPT_ARG);
551181834Sroberto        } else
552290000Sglebius            printf(ECHO_N_EXIT, opts->pzPROGNAME, LONG_USE_STR);
553181834Sroberto
554181834Sroberto    } else {
555290000Sglebius        if (od->optMaxCt == 1)
556290000Sglebius            printf(SGL_ARG_FMT, opts->pzPROGNAME, od->pz_NAME);
557181834Sroberto        else {
558290000Sglebius            if ((unsigned)od->optMaxCt < NOLIMIT)
559290000Sglebius                printf(CHK_MAX_COUNT, opts->pzPROGNAME,
560290000Sglebius                       od->pz_NAME, od->optMaxCt);
561181834Sroberto
562290000Sglebius            printf(MULTI_ARG_FMT, opts->pzPROGNAME, od->pz_NAME);
563181834Sroberto        }
564181834Sroberto
565181834Sroberto        /*
566181834Sroberto         *  Fix up the args.
567181834Sroberto         */
568290000Sglebius        if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NONE) {
569290000Sglebius            printf(SET_MULTI_ARG, opts->pzPROGNAME, od->pz_NAME);
570290000Sglebius            printf(LVL3_CMD, NO_ARG_NEEDED);
571181834Sroberto
572290000Sglebius        } else if (od->fOptState & OPTST_ARG_OPTIONAL) {
573290000Sglebius            printf(SET_MULTI_ARG,  opts->pzPROGNAME, od->pz_NAME);
574290000Sglebius            printf(LVL3_CMD, OK_NEED_OPT_ARG);
575181834Sroberto
576181834Sroberto        } else {
577290000Sglebius            printf(LVL3_CMD, YES_NEED_OPT_ARG);
578181834Sroberto        }
579181834Sroberto    }
580290000Sglebius    fputs(zOptionEndSelect, stdout);
581181834Sroberto}
582181834Sroberto
583181834Srobertostatic void
584290000Sglebiusemit_inaction(tOptions * opts, tOptDesc * od)
585181834Sroberto{
586290000Sglebius    if (od->pOptProc == optionLoadOpt) {
587290000Sglebius        printf(LVL3_CMD, NO_SUPPRESS_LOAD);
588181834Sroberto
589290000Sglebius    } else if (od->optMaxCt == 1)
590290000Sglebius        printf(NO_SGL_ARG_FMT, opts->pzPROGNAME,
591290000Sglebius               od->pz_NAME, od->pz_DisablePfx);
592181834Sroberto    else
593290000Sglebius        printf(NO_MULTI_ARG_FMT, opts->pzPROGNAME,
594290000Sglebius               od->pz_NAME, od->pz_DisablePfx);
595181834Sroberto
596290000Sglebius    printf(LVL3_CMD, NO_ARG_NEEDED);
597290000Sglebius    fputs(zOptionEndSelect, stdout);
598181834Sroberto}
599181834Sroberto
600290000Sglebius/**
601290000Sglebius * recognize flag options.  These go at the end.
602290000Sglebius * At the end, emit code to handle options we don't recognize.
603290000Sglebius *
604290000Sglebius * @param[in] opts  the program options
605290000Sglebius */
606181834Srobertostatic void
607290000Sglebiusemit_flag(tOptions * opts)
608181834Sroberto{
609290000Sglebius    tOptDesc * od = opts->pOptDesc;
610290000Sglebius    int        opt_ct = opts->optCt;
611181834Sroberto
612290000Sglebius    fputs(zOptionCase, stdout);
613181834Sroberto
614290000Sglebius    for (;opt_ct > 0; od++, --opt_ct) {
615181834Sroberto
616290000Sglebius        if (SKIP_OPT(od) || ! IS_GRAPHIC_CHAR(od->optValue))
617181834Sroberto            continue;
618181834Sroberto
619290000Sglebius        printf(zOptionFlag, od->optValue);
620290000Sglebius        emit_action(opts, od);
621181834Sroberto    }
622290000Sglebius    printf(UNK_OPT_FMT, FLAG_STR, opts->pzPROGNAME);
623181834Sroberto}
624181834Sroberto
625290000Sglebius/**
626290000Sglebius *  Emit the match text for a long option.  The passed in \a name may be
627290000Sglebius *  either the enablement name or the disablement name.
628290000Sglebius *
629290000Sglebius * @param[in] name  The current name to check.
630290000Sglebius * @param[in] cod   current option descriptor
631290000Sglebius * @param[in] opts  the program options
632181834Sroberto */
633181834Srobertostatic void
634290000Sglebiusemit_match_expr(char const * name, tOptDesc * cod, tOptions * opts)
635181834Sroberto{
636290000Sglebius    char name_bf[32];
637290000Sglebius    unsigned int    min_match_ct = 2;
638290000Sglebius    unsigned int    max_match_ct = strlen(name) - 1;
639181834Sroberto
640290000Sglebius    if (max_match_ct >= sizeof(name_bf) - 1)
641290000Sglebius        goto leave;
642290000Sglebius
643290000Sglebius    {
644290000Sglebius        tOptDesc *  od = opts->pOptDesc;
645290000Sglebius        int         ct = opts->optCt;
646181834Sroberto
647290000Sglebius        for (; ct-- > 0; od++) {
648290000Sglebius            unsigned int match_ct = 0;
649181834Sroberto
650290000Sglebius            /*
651290000Sglebius             *  Omit the current option, Doc opts and compiled out opts.
652290000Sglebius             */
653290000Sglebius            if ((od == cod) || SKIP_OPT(od))
654290000Sglebius                continue;
655181834Sroberto
656290000Sglebius            /*
657290000Sglebius             *  Check each character of the name case insensitively.
658290000Sglebius             *  They must not be the same.  They cannot be, because it would
659290000Sglebius             *  not compile correctly if they were.
660290000Sglebius             */
661290000Sglebius            while (UPPER(od->pz_Name[match_ct]) == UPPER(name[match_ct]))
662290000Sglebius                match_ct++;
663181834Sroberto
664290000Sglebius            if (match_ct > min_match_ct)
665290000Sglebius                min_match_ct = match_ct;
666290000Sglebius
667290000Sglebius            /*
668290000Sglebius             *  Check the disablement name, too.
669290000Sglebius             */
670290000Sglebius            if (od->pz_DisableName == NULL)
671290000Sglebius                continue;
672290000Sglebius
673290000Sglebius            match_ct = 0;
674294904Sdelphij            while (  toupper((unsigned char)od->pz_DisableName[match_ct])
675294904Sdelphij                  == toupper((unsigned char)name[match_ct]))
676290000Sglebius                match_ct++;
677290000Sglebius            if (match_ct > min_match_ct)
678290000Sglebius                min_match_ct = match_ct;
679181834Sroberto        }
680181834Sroberto    }
681181834Sroberto
682181834Sroberto    /*
683290000Sglebius     *  Don't bother emitting partial matches if there is only one possible
684290000Sglebius     *  partial match.
685181834Sroberto     */
686290000Sglebius    if (min_match_ct < max_match_ct) {
687290000Sglebius        char *  pz    = name_bf + min_match_ct;
688290000Sglebius        int     nm_ix = min_match_ct;
689181834Sroberto
690290000Sglebius        memcpy(name_bf, name, min_match_ct);
691181834Sroberto
692181834Sroberto        for (;;) {
693181834Sroberto            *pz = NUL;
694290000Sglebius            printf(zOptionPartName, name_bf);
695290000Sglebius            *pz++ = name[nm_ix++];
696290000Sglebius            if (name[nm_ix] == NUL) {
697181834Sroberto                *pz = NUL;
698181834Sroberto                break;
699181834Sroberto            }
700181834Sroberto        }
701181834Sroberto    }
702290000Sglebius
703290000Sglebiusleave:
704290000Sglebius    printf(zOptionFullName, name);
705181834Sroberto}
706181834Sroberto
707290000Sglebius/**
708290000Sglebius *  Emit GNU-standard long option handling code.
709290000Sglebius *
710290000Sglebius * @param[in] opts  the program options
711181834Sroberto */
712181834Srobertostatic void
713290000Sglebiusemit_long(tOptions * opts)
714181834Sroberto{
715290000Sglebius    tOptDesc * od = opts->pOptDesc;
716290000Sglebius    int        ct  = opts->optCt;
717181834Sroberto
718290000Sglebius    fputs(zOptionCase, stdout);
719181834Sroberto
720181834Sroberto    /*
721181834Sroberto     *  do each option, ...
722181834Sroberto     */
723181834Sroberto    do  {
724181834Sroberto        /*
725181834Sroberto         *  Documentation & compiled-out options
726181834Sroberto         */
727290000Sglebius        if (SKIP_OPT(od))
728181834Sroberto            continue;
729181834Sroberto
730290000Sglebius        emit_match_expr(od->pz_Name, od, opts);
731290000Sglebius        emit_action(opts, od);
732181834Sroberto
733181834Sroberto        /*
734181834Sroberto         *  Now, do the same thing for the disablement version of the option.
735181834Sroberto         */
736290000Sglebius        if (od->pz_DisableName != NULL) {
737290000Sglebius            emit_match_expr(od->pz_DisableName, od, opts);
738290000Sglebius            emit_inaction(opts, od);
739181834Sroberto        }
740290000Sglebius    } while (od++, --ct > 0);
741181834Sroberto
742290000Sglebius    printf(UNK_OPT_FMT, OPTION_STR, opts->pzPROGNAME);
743181834Sroberto}
744181834Sroberto
745290000Sglebius/**
746290000Sglebius * Load the previous shell script output file.  We need to preserve any
747290000Sglebius * hand-edited additions outside of the START_MARK and END_MARKs.
748290000Sglebius *
749290000Sglebius * @param[in] fname  the output file name
750290000Sglebius */
751290000Sglebiusstatic char *
752290000Sglebiusload_old_output(char const * fname, char const * pname)
753181834Sroberto{
754290000Sglebius    /*
755290000Sglebius     *  IF we cannot stat the file,
756290000Sglebius     *  THEN assume we are creating a new file.
757290000Sglebius     *       Skip the loading of the old data.
758290000Sglebius     */
759290000Sglebius    FILE * fp = fopen(fname, "r" FOPEN_BINARY_FLAG);
760181834Sroberto    struct stat stbf;
761290000Sglebius    char * text;
762290000Sglebius    char * scan;
763181834Sroberto
764290000Sglebius    if (fp == NULL)
765290000Sglebius        return NULL;
766181834Sroberto
767290000Sglebius    /*
768290000Sglebius     * If we opened it, we should be able to stat it and it needs
769290000Sglebius     * to be a regular file
770290000Sglebius     */
771290000Sglebius    if ((fstat(fileno(fp), &stbf) != 0) || (! S_ISREG(stbf.st_mode)))
772290000Sglebius        fserr_exit(pname, "fstat", fname);
773290000Sglebius
774290000Sglebius    scan = text = AGALOC(stbf.st_size + 1, "f data");
775290000Sglebius
776290000Sglebius    /*
777290000Sglebius     *  Read in all the data as fast as our OS will let us.
778290000Sglebius     */
779290000Sglebius    for (;;) {
780290000Sglebius        size_t inct = fread(VOIDP(scan), 1, (size_t)stbf.st_size, fp);
781290000Sglebius        if (inct == 0)
782181834Sroberto            break;
783181834Sroberto
784290000Sglebius        stbf.st_size -= (ssize_t)inct;
785181834Sroberto
786290000Sglebius        if (stbf.st_size == 0)
787290000Sglebius            break;
788181834Sroberto
789290000Sglebius        scan += inct;
790290000Sglebius    }
791181834Sroberto
792290000Sglebius    *scan = NUL;
793290000Sglebius    fclose(fp);
794181834Sroberto
795290000Sglebius    return text;
796290000Sglebius}
797181834Sroberto
798290000Sglebius/**
799290000Sglebius * Open the specified output file.  If it already exists, load its
800290000Sglebius * contents and save the non-generated (hand edited) portions.
801290000Sglebius * If a "start mark" is found, everything before it is preserved leader.
802290000Sglebius * If not, the entire thing is a trailer.  Assuming the start is found,
803290000Sglebius * then everything after the end marker is the trailer.  If the end
804290000Sglebius * mark is not found, the file is actually corrupt, but we take the
805290000Sglebius * remainder to be the trailer.
806290000Sglebius *
807290000Sglebius * @param[in] fname  the output file name
808290000Sglebius */
809290000Sglebiusstatic void
810290000Sglebiusopen_out(char const * fname, char const * pname)
811290000Sglebius{
812181834Sroberto
813290000Sglebius    do  {
814290000Sglebius        char * txt = script_text = load_old_output(fname, pname);
815290000Sglebius        char * scn;
816290000Sglebius
817290000Sglebius        if (txt == NULL)
818181834Sroberto            break;
819290000Sglebius
820290000Sglebius        scn = strstr(txt, START_MARK);
821290000Sglebius        if (scn == NULL) {
822290000Sglebius            script_trailer = txt;
823290000Sglebius            break;
824181834Sroberto        }
825181834Sroberto
826290000Sglebius        *(scn++) = NUL;
827290000Sglebius        scn = strstr(scn, END_MARK);
828290000Sglebius        if (scn == NULL) {
829290000Sglebius            /*
830290000Sglebius             * The file is corrupt.  Set the trailer to be everything
831290000Sglebius             * after the start mark. The user will need to fix it up.
832290000Sglebius             */
833290000Sglebius            script_trailer = txt + strlen(txt) + START_MARK_LEN + 1;
834181834Sroberto            break;
835181834Sroberto        }
836181834Sroberto
837181834Sroberto        /*
838290000Sglebius         *  Check to see if the data contains our marker.
839290000Sglebius         *  If it does, then we will skip over it
840181834Sroberto         */
841290000Sglebius        script_trailer = scn + END_MARK_LEN;
842290000Sglebius        script_leader  = txt;
843290000Sglebius    } while (false);
844181834Sroberto
845290000Sglebius    if (freopen(fname, "w" FOPEN_BINARY_FLAG, stdout) != stdout)
846290000Sglebius        fserr_exit(pname, "freopen", fname);
847181834Sroberto}
848181834Sroberto
849181834Sroberto/*=export_func genshelloptUsage
850181834Sroberto * private:
851181834Sroberto * what: The usage function for the genshellopt generated program
852181834Sroberto *
853290000Sglebius * arg:  + tOptions * + opts    + program options descriptor +
854290000Sglebius * 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
864290000SglebiusgenshelloptUsage(tOptions * opts, int exit_cd)
865181834Sroberto{
866290000Sglebius#if ! defined(HAVE_WORKING_FORK)
867290000Sglebius    optionUsage(opts, exit_cd);
868181834Sroberto#else
869181834Sroberto    /*
870181834Sroberto     *  IF not EXIT_SUCCESS,
871181834Sroberto     *  THEN emit the short form of usage.
872181834Sroberto     */
873290000Sglebius    if (exit_cd != EXIT_SUCCESS)
874290000Sglebius        optionUsage(opts, exit_cd);
875290000Sglebius    fflush(stderr);
876290000Sglebius    fflush(stdout);
877290000Sglebius    if (ferror(stdout) || ferror(stderr))
878290000Sglebius        option_exits(EXIT_FAILURE);
879181834Sroberto
880181834Sroberto    option_usage_fp = stdout;
881181834Sroberto
882181834Sroberto    /*
883181834Sroberto     *  First, print our usage
884181834Sroberto     */
885181834Sroberto    switch (fork()) {
886181834Sroberto    case -1:
887290000Sglebius        optionUsage(opts, EXIT_FAILURE);
888290000Sglebius        /* NOTREACHED */
889181834Sroberto
890181834Sroberto    case 0:
891181834Sroberto        pagerState = PAGER_STATE_CHILD;
892290000Sglebius        optionUsage(opts, EXIT_SUCCESS);
893290000Sglebius        /* NOTREACHED */
894290000Sglebius        _exit(EXIT_FAILURE);
895181834Sroberto
896181834Sroberto    default:
897181834Sroberto    {
898181834Sroberto        int  sts;
899290000Sglebius        wait(&sts);
900181834Sroberto    }
901181834Sroberto    }
902181834Sroberto
903181834Sroberto    /*
904181834Sroberto     *  Generate the pzProgName, since optionProcess() normally
905181834Sroberto     *  gets it from the command line
906181834Sroberto     */
907181834Sroberto    {
908290000Sglebius        char *  pz;
909290000Sglebius        char ** pp = VOIDP(&(optionParseShellOptions->pzProgName));
910290000Sglebius        AGDUPSTR(pz, optionParseShellOptions->pzPROGNAME, "prog name");
911290000Sglebius        *pp = pz;
912181834Sroberto        while (*pz != NUL) {
913290000Sglebius            *pz = (char)LOWER(*pz);
914181834Sroberto            pz++;
915181834Sroberto        }
916181834Sroberto    }
917181834Sroberto
918181834Sroberto    /*
919181834Sroberto     *  Separate the makeshell usage from the client usage
920181834Sroberto     */
921290000Sglebius    fprintf(option_usage_fp, zGenshell, optionParseShellOptions->pzProgName);
922290000Sglebius    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:
932290000Sglebius        optionUsage(optionParseShellOptions, EXIT_FAILURE);
933181834Sroberto
934181834Sroberto    default:
935181834Sroberto    {
936181834Sroberto        int  sts;
937290000Sglebius        wait(&sts);
938181834Sroberto    }
939181834Sroberto    }
940181834Sroberto
941290000Sglebius    fflush(stdout);
942290000Sglebius    if (ferror(stdout))
943290000Sglebius        fserr_exit(opts->pzProgName, zwriting, zstdout_name);
944290000Sglebius
945290000Sglebius    option_exits(EXIT_SUCCESS);
946181834Sroberto#endif
947181834Sroberto}
948181834Sroberto
949290000Sglebius/** @}
950290000Sglebius *
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