/** * \file makeshell.c * * This module will interpret the options set in the tOptions * structure and create a Bourne shell script capable of parsing them. * * @addtogroup autoopts * @{ */ /* * This file is part of AutoOpts, a companion to AutoGen. * AutoOpts is free software. * AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved * * AutoOpts is available under any one of two licenses. The license * in use must be one of these two and the choice is under the control * of the user of the license. * * The GNU Lesser General Public License, version 3 or later * See the files "COPYING.lgplv3" and "COPYING.gplv3" * * The Modified Berkeley Software Distribution License * See the file "COPYING.mbsd" * * These files have the following sha256 sums: * * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd */ static inline unsigned char to_uchar (char ch) { return ch; } #define UPPER(_c) (toupper(to_uchar(_c))) #define LOWER(_c) (tolower(to_uchar(_c))) lo_noreturn static void option_exits(int exit_code) { if (print_exit) printf("\nexit %d\n", exit_code); exit(exit_code); } lo_noreturn static void ao_bug(char const * msg) { fprintf(stderr, zao_bug_msg, msg); option_exits(EX_SOFTWARE); } static void fserr_warn(char const * prog, char const * op, char const * fname) { fprintf(stderr, zfserr_fmt, prog, errno, strerror(errno), op, fname); } lo_noreturn static void fserr_exit(char const * prog, char const * op, char const * fname) { fserr_warn(prog, op, fname); option_exits(EXIT_FAILURE); } /*=export_func optionParseShell * private: * * what: Decipher a boolean value * arg: + tOptions * + pOpts + program options descriptor + * * doc: * Emit a shell script that will parse the command line options. =*/ void optionParseShell(tOptions * opts) { /* * Check for our SHELL option now. * IF the output file contains the "#!" magic marker, * it will override anything we do here. */ if (HAVE_GENSHELL_OPT(SHELL)) shell_prog = GENSHELL_OPT_ARG(SHELL); else if (! ENABLED_GENSHELL_OPT(SHELL)) shell_prog = NULL; else if ((shell_prog = getenv("SHELL")), shell_prog == NULL) shell_prog = POSIX_SHELL; /* * Check for a specified output file */ if (HAVE_GENSHELL_OPT(SCRIPT)) open_out(GENSHELL_OPT_ARG(SCRIPT), opts->pzProgName); emit_usage(opts); emit_setup(opts); /* * There are four modes of option processing. */ switch (opts->fOptSet & (OPTPROC_LONGOPT|OPTPROC_SHORTOPT)) { case OPTPROC_LONGOPT: fputs(LOOP_STR, stdout); fputs(LONG_OPT_MARK, stdout); fputs(INIT_LOPT_STR, stdout); emit_long(opts); printf(LOPT_ARG_FMT, opts->pzPROGNAME); fputs(END_OPT_SEL_STR, stdout); fputs(NOT_FOUND_STR, stdout); break; case 0: fputs(ONLY_OPTS_LOOP, stdout); fputs(INIT_LOPT_STR, stdout); emit_long(opts); printf(LOPT_ARG_FMT, opts->pzPROGNAME); break; case OPTPROC_SHORTOPT: fputs(LOOP_STR, stdout); fputs(FLAG_OPT_MARK, stdout); fputs(INIT_OPT_STR, stdout); emit_flag(opts); printf(OPT_ARG_FMT, opts->pzPROGNAME); fputs(END_OPT_SEL_STR, stdout); fputs(NOT_FOUND_STR, stdout); break; case OPTPROC_LONGOPT|OPTPROC_SHORTOPT: fputs(LOOP_STR, stdout); fputs(LONG_OPT_MARK, stdout); fputs(INIT_LOPT_STR, stdout); emit_long(opts); printf(LOPT_ARG_FMT, opts->pzPROGNAME); fputs(END_OPT_SEL_STR, stdout); fputs(FLAG_OPT_MARK, stdout); fputs(INIT_OPT_STR, stdout); emit_flag(opts); printf(OPT_ARG_FMT, opts->pzPROGNAME); fputs(END_OPT_SEL_STR, stdout); fputs(NOT_FOUND_STR, stdout); break; } emit_wrapup(opts); if ((script_trailer != NULL) && (*script_trailer != NUL)) fputs(script_trailer, stdout); else if (ENABLED_GENSHELL_OPT(SHELL)) printf(SHOW_PROG_ENV, opts->pzPROGNAME); #ifdef HAVE_FCHMOD fchmod(STDOUT_FILENO, 0755); #endif fclose(stdout); if (ferror(stdout)) fserr_exit(opts->pzProgName, zwriting, zstdout_name); AGFREE(script_text); script_leader = NULL; script_trailer = NULL; script_text = NULL; } #ifdef HAVE_WORKING_FORK /** * Print the value of "var" to a file descriptor. * The "fdin" is the read end of a pipe to a forked process that * is writing usage text to it. We read that text in and re-emit * to standard out, formatting it so that it is assigned to a * shell variable. * * @param[in] prog The capitalized, c-variable-formatted program name * @param[in] var a similarly formatted type name * (LONGUSAGE, USAGE or VERSION) * @param[in] fdin the input end of a pipe */ static void emit_var_text(char const * prog, char const * var, int fdin) { FILE * fp = fdopen(fdin, "r" FOPEN_BINARY_FLAG); int nlct = 0; /* defer newlines and skip trailing ones */ printf(SET_TEXT_FMT, prog, var); if (fp == NULL) goto skip_text; for (;;) { int ch = fgetc(fp); switch (ch) { case NL: nlct++; break; case '\'': while (nlct > 0) { fputc(NL, stdout); nlct--; } fputs(apostrophe, stdout); break; case EOF: goto done; default: while (nlct > 0) { fputc(NL, stdout); nlct--; } fputc(ch, stdout); break; } } done:; fclose(fp); skip_text: fputs(END_SET_TEXT, stdout); } #endif /** * The purpose of this function is to assign "long usage", short usage * and version information to a shell variable. Rather than wind our * way through all the logic necessary to emit the text directly, we * fork(), have our child process emit the text the normal way and * capture the output in the parent process. * * @param[in] opts the program options * @param[in] which what to print: long usage, usage or version * @param[in] od for TT_VERSION, it is the version option */ static void text_to_var(tOptions * opts, teTextTo which, tOptDesc * od) { # define _TT_(n) static char const z ## n [] = #n; TEXTTO_TABLE # undef _TT_ # define _TT_(n) z ## n , static char const * ttnames[] = { TEXTTO_TABLE }; # undef _TT_ #if ! defined(HAVE_WORKING_FORK) printf(SET_NO_TEXT_FMT, opts->pzPROGNAME, ttnames[which]); #else int fdpair[2]; fflush(stdout); fflush(stderr); if (pipe(fdpair) != 0) fserr_exit(opts->pzProgName, "pipe", zinter_proc_pipe); switch (fork()) { case -1: fserr_exit(opts->pzProgName, "fork", opts->pzProgName); /* NOTREACHED */ case 0: /* * Send both stderr and stdout to the pipe. No matter which * descriptor is used, we capture the output on the read end. */ dup2(fdpair[1], STDERR_FILENO); dup2(fdpair[1], STDOUT_FILENO); close(fdpair[0]); switch (which) { case TT_LONGUSAGE: (*(opts->pUsageProc))(opts, EXIT_SUCCESS); /* FALLTHROUGH */ /* NOTREACHED */ case TT_USAGE: (*(opts->pUsageProc))(opts, EXIT_FAILURE); /* FALLTHROUGH */ /* NOTREACHED */ case TT_VERSION: if (od->fOptState & OPTST_ALLOC_ARG) { AGFREE(od->optArg.argString); od->fOptState &= ~OPTST_ALLOC_ARG; } od->optArg.argString = "c"; optionPrintVersion(opts, od); /* FALLTHROUGH */ /* NOTREACHED */ default: option_exits(EXIT_FAILURE); /* FALLTHROUGH */ /* NOTREACHED */ } /* FALLTHROUGH */ /* NOTREACHED */ default: close(fdpair[1]); } emit_var_text(opts->pzPROGNAME, ttnames[which], fdpair[0]); #endif } /** * capture usage text in shell variables. * */ static void emit_usage(tOptions * opts) { char tm_nm_buf[AO_NAME_SIZE]; /* * First, switch stdout to the output file name. * Then, change the program name to the one defined * by the definitions (rather than the current * executable name). Down case the upper cased name. */ if (script_leader != NULL) fputs(script_leader, stdout); { char const * out_nm; { time_t c_tim = time(NULL); struct tm * ptm = localtime(&c_tim); strftime(tm_nm_buf, AO_NAME_SIZE, TIME_FMT, ptm ); } if (HAVE_GENSHELL_OPT(SCRIPT)) out_nm = GENSHELL_OPT_ARG(SCRIPT); else out_nm = STDOUT; if ((script_leader == NULL) && (shell_prog != NULL)) printf(SHELL_MAGIC, shell_prog); printf(PREAMBLE_FMT, START_MARK, out_nm, tm_nm_buf); } printf(END_PRE_FMT, opts->pzPROGNAME); /* * Get a copy of the original program name in lower case and * fill in an approximation of the program name from it. */ { char * pzPN = tm_nm_buf; char const * pz = opts->pzPROGNAME; char ** pp; /* Copy the program name into the time/name buffer */ for (;;) { if ((*pzPN++ = (char)tolower(*pz++)) == NUL) break; } pp = VOIDP(&(opts->pzProgPath)); *pp = tm_nm_buf; pp = VOIDP(&(opts->pzProgName)); *pp = tm_nm_buf; } text_to_var(opts, TT_LONGUSAGE, NULL); text_to_var(opts, TT_USAGE, NULL); { tOptDesc * pOptDesc = opts->pOptDesc; int optionCt = opts->optCt; for (;;) { if (pOptDesc->pOptProc == optionPrintVersion) { text_to_var(opts, TT_VERSION, pOptDesc); break; } if (--optionCt <= 0) break; pOptDesc++; } } } static void emit_wrapup(tOptions * opts) { tOptDesc * od = opts->pOptDesc; int opt_ct = opts->presetOptCt; char const * fmt; printf(FINISH_LOOP, opts->pzPROGNAME); for (;opt_ct > 0; od++, --opt_ct) { /* * Options that are either usage documentation or are compiled out * are not to be processed. */ if (SKIP_OPT(od) || (od->pz_NAME == NULL)) continue; /* * do not presence check if there is no minimum/must-set */ if ((od->optMinCt == 0) && ((od->fOptState & OPTST_MUST_SET) == 0)) continue; if (od->optMaxCt > 1) fmt = CHK_MIN_COUNT; else fmt = CHK_ONE_REQUIRED; { int min = (od->optMinCt == 0) ? 1 : od->optMinCt; printf(fmt, opts->pzPROGNAME, od->pz_NAME, min); } } fputs(END_MARK, stdout); } static void emit_setup(tOptions * opts) { tOptDesc * od = opts->pOptDesc; int opt_ct = opts->presetOptCt; char const * fmt; char const * def_val; for (;opt_ct > 0; od++, --opt_ct) { char int_val_buf[32]; /* * Options that are either usage documentation or are compiled out * are not to be processed. */ if (SKIP_OPT(od) || (od->pz_NAME == NULL)) continue; if (od->optMaxCt > 1) fmt = MULTI_DEF_FMT; else fmt = SGL_DEF_FMT; /* * IF this is an enumeration/bitmask option, then convert the value * to a string before printing the default value. */ switch (OPTST_GET_ARGTYPE(od->fOptState)) { case OPARG_TYPE_ENUMERATION: (*(od->pOptProc))(OPTPROC_EMIT_SHELL, od ); def_val = od->optArg.argString; break; /* * Numeric and membership bit options are just printed as a number. */ case OPARG_TYPE_NUMERIC: snprintf(int_val_buf, sizeof(int_val_buf), "%d", (int)od->optArg.argInt); def_val = int_val_buf; break; case OPARG_TYPE_MEMBERSHIP: snprintf(int_val_buf, sizeof(int_val_buf), "%lu", (unsigned long)od->optArg.argIntptr); def_val = int_val_buf; break; case OPARG_TYPE_BOOLEAN: def_val = (od->optArg.argBool) ? TRUE_STR : FALSE_STR; break; default: if (od->optArg.argString == NULL) { if (fmt == SGL_DEF_FMT) fmt = SGL_NO_DEF_FMT; def_val = NULL; } else def_val = od->optArg.argString; } printf(fmt, opts->pzPROGNAME, od->pz_NAME, def_val); } } static void emit_action(tOptions * opts, tOptDesc * od) { if (od->pOptProc == optionPrintVersion) printf(ECHO_N_EXIT, opts->pzPROGNAME, VER_STR); else if (od->pOptProc == optionPagedUsage) printf(PAGE_USAGE_TEXT, opts->pzPROGNAME); else if (od->pOptProc == optionLoadOpt) { printf(LVL3_CMD, NO_LOAD_WARN); printf(LVL3_CMD, YES_NEED_OPT_ARG); } else if (od->pz_NAME == NULL) { if (od->pOptProc == NULL) { printf(LVL3_CMD, NO_SAVE_OPTS); printf(LVL3_CMD, OK_NEED_OPT_ARG); } else printf(ECHO_N_EXIT, opts->pzPROGNAME, LONG_USE_STR); } else { if (od->optMaxCt == 1) printf(SGL_ARG_FMT, opts->pzPROGNAME, od->pz_NAME); else { if ((unsigned)od->optMaxCt < NOLIMIT) printf(CHK_MAX_COUNT, opts->pzPROGNAME, od->pz_NAME, od->optMaxCt); printf(MULTI_ARG_FMT, opts->pzPROGNAME, od->pz_NAME); } /* * Fix up the args. */ if (OPTST_GET_ARGTYPE(od->fOptState) == OPARG_TYPE_NONE) { printf(SET_MULTI_ARG, opts->pzPROGNAME, od->pz_NAME); printf(LVL3_CMD, NO_ARG_NEEDED); } else if (od->fOptState & OPTST_ARG_OPTIONAL) { printf(SET_MULTI_ARG, opts->pzPROGNAME, od->pz_NAME); printf(LVL3_CMD, OK_NEED_OPT_ARG); } else { printf(LVL3_CMD, YES_NEED_OPT_ARG); } } fputs(zOptionEndSelect, stdout); } static void emit_inaction(tOptions * opts, tOptDesc * od) { if (od->pOptProc == optionLoadOpt) { printf(LVL3_CMD, NO_SUPPRESS_LOAD); } else if (od->optMaxCt == 1) printf(NO_SGL_ARG_FMT, opts->pzPROGNAME, od->pz_NAME, od->pz_DisablePfx); else printf(NO_MULTI_ARG_FMT, opts->pzPROGNAME, od->pz_NAME, od->pz_DisablePfx); printf(LVL3_CMD, NO_ARG_NEEDED); fputs(zOptionEndSelect, stdout); } /** * recognize flag options. These go at the end. * At the end, emit code to handle options we don't recognize. * * @param[in] opts the program options */ static void emit_flag(tOptions * opts) { tOptDesc * od = opts->pOptDesc; int opt_ct = opts->optCt; fputs(zOptionCase, stdout); for (;opt_ct > 0; od++, --opt_ct) { if (SKIP_OPT(od) || ! IS_GRAPHIC_CHAR(od->optValue)) continue; printf(zOptionFlag, od->optValue); emit_action(opts, od); } printf(UNK_OPT_FMT, FLAG_STR, opts->pzPROGNAME); } /** * Emit the match text for a long option. The passed in \a name may be * either the enablement name or the disablement name. * * @param[in] name The current name to check. * @param[in] cod current option descriptor * @param[in] opts the program options */ static void emit_match_expr(char const * name, tOptDesc * cod, tOptions * opts) { char name_bf[32]; unsigned int min_match_ct = 2; unsigned int max_match_ct = strlen(name) - 1; if (max_match_ct >= sizeof(name_bf) - 1) goto leave; { tOptDesc * od = opts->pOptDesc; int ct = opts->optCt; for (; ct-- > 0; od++) { unsigned int match_ct = 0; /* * Omit the current option, Doc opts and compiled out opts. */ if ((od == cod) || SKIP_OPT(od)) continue; /* * Check each character of the name case insensitively. * They must not be the same. They cannot be, because it would * not compile correctly if they were. */ while (UPPER(od->pz_Name[match_ct]) == UPPER(name[match_ct])) match_ct++; if (match_ct > min_match_ct) min_match_ct = match_ct; /* * Check the disablement name, too. */ if (od->pz_DisableName == NULL) continue; match_ct = 0; while ( toupper(od->pz_DisableName[match_ct]) == toupper(name[match_ct])) match_ct++; if (match_ct > min_match_ct) min_match_ct = match_ct; } } /* * Don't bother emitting partial matches if there is only one possible * partial match. */ if (min_match_ct < max_match_ct) { char * pz = name_bf + min_match_ct; int nm_ix = min_match_ct; memcpy(name_bf, name, min_match_ct); for (;;) { *pz = NUL; printf(zOptionPartName, name_bf); *pz++ = name[nm_ix++]; if (name[nm_ix] == NUL) { *pz = NUL; break; } } } leave: printf(zOptionFullName, name); } /** * Emit GNU-standard long option handling code. * * @param[in] opts the program options */ static void emit_long(tOptions * opts) { tOptDesc * od = opts->pOptDesc; int ct = opts->optCt; fputs(zOptionCase, stdout); /* * do each option, ... */ do { /* * Documentation & compiled-out options */ if (SKIP_OPT(od)) continue; emit_match_expr(od->pz_Name, od, opts); emit_action(opts, od); /* * Now, do the same thing for the disablement version of the option. */ if (od->pz_DisableName != NULL) { emit_match_expr(od->pz_DisableName, od, opts); emit_inaction(opts, od); } } while (od++, --ct > 0); printf(UNK_OPT_FMT, OPTION_STR, opts->pzPROGNAME); } /** * Load the previous shell script output file. We need to preserve any * hand-edited additions outside of the START_MARK and END_MARKs. * * @param[in] fname the output file name */ static char * load_old_output(char const * fname, char const * pname) { /* * IF we cannot stat the file, * THEN assume we are creating a new file. * Skip the loading of the old data. */ FILE * fp = fopen(fname, "r" FOPEN_BINARY_FLAG); struct stat stbf; char * text; char * scan; if (fp == NULL) return NULL; /* * If we opened it, we should be able to stat it and it needs * to be a regular file */ if ((fstat(fileno(fp), &stbf) != 0) || (! S_ISREG(stbf.st_mode))) fserr_exit(pname, "fstat", fname); scan = text = AGALOC(stbf.st_size + 1, "f data"); /* * Read in all the data as fast as our OS will let us. */ for (;;) { size_t inct = fread(VOIDP(scan), 1, (size_t)stbf.st_size, fp); if (inct == 0) break; stbf.st_size -= (ssize_t)inct; if (stbf.st_size == 0) break; scan += inct; } *scan = NUL; fclose(fp); return text; } /** * Open the specified output file. If it already exists, load its * contents and save the non-generated (hand edited) portions. * If a "start mark" is found, everything before it is preserved leader. * If not, the entire thing is a trailer. Assuming the start is found, * then everything after the end marker is the trailer. If the end * mark is not found, the file is actually corrupt, but we take the * remainder to be the trailer. * * @param[in] fname the output file name */ static void open_out(char const * fname, char const * pname) { do { char * txt = script_text = load_old_output(fname, pname); char * scn; if (txt == NULL) break; scn = strstr(txt, START_MARK); if (scn == NULL) { script_trailer = txt; break; } *(scn++) = NUL; scn = strstr(scn, END_MARK); if (scn == NULL) { /* * The file is corrupt. Set the trailer to be everything * after the start mark. The user will need to fix it up. */ script_trailer = txt + strlen(txt) + START_MARK_LEN + 1; break; } /* * Check to see if the data contains our marker. * If it does, then we will skip over it */ script_trailer = scn + END_MARK_LEN; script_leader = txt; } while (false); if (freopen(fname, "w" FOPEN_BINARY_FLAG, stdout) != stdout) fserr_exit(pname, "freopen", fname); } /*=export_func genshelloptUsage * private: * what: The usage function for the genshellopt generated program * * arg: + tOptions * + opts + program options descriptor + * arg: + int + exit_cd + usage text type to produce + * * doc: * This function is used to create the usage strings for the option * processing shell script code. Two child processes are spawned * each emitting the usage text in either the short (error exit) * style or the long style. The generated program will capture this * and create shell script variables containing the two types of text. =*/ void genshelloptUsage(tOptions * opts, int exit_cd) { #if ! defined(HAVE_WORKING_FORK) optionUsage(opts, exit_cd); #else /* * IF not EXIT_SUCCESS, * THEN emit the short form of usage. */ if (exit_cd != EXIT_SUCCESS) optionUsage(opts, exit_cd); fflush(stderr); fflush(stdout); if (ferror(stdout) || ferror(stderr)) option_exits(EXIT_FAILURE); option_usage_fp = stdout; /* * First, print our usage */ switch (fork()) { case -1: optionUsage(opts, EXIT_FAILURE); /* FALLTHROUGH */ /* NOTREACHED */ case 0: pagerState = PAGER_STATE_CHILD; optionUsage(opts, EXIT_SUCCESS); /* FALLTHROUGH */ /* NOTREACHED */ default: { int sts; wait(&sts); } } /* * Generate the pzProgName, since optionProcess() normally * gets it from the command line */ { char * pz; char ** pp = VOIDP(&(optionParseShellOptions->pzProgName)); AGDUPSTR(pz, optionParseShellOptions->pzPROGNAME, "prog name"); *pp = pz; while (*pz != NUL) { *pz = (char)LOWER(*pz); pz++; } } /* * Separate the makeshell usage from the client usage */ fprintf(option_usage_fp, zGenshell, optionParseShellOptions->pzProgName); fflush(option_usage_fp); /* * Now, print the client usage. */ switch (fork()) { case 0: pagerState = PAGER_STATE_CHILD; /*FALLTHROUGH*/ case -1: optionUsage(optionParseShellOptions, EXIT_FAILURE); /* FALLTHROUGH */ /* NOTREACHED */ default: { int sts; wait(&sts); } } fflush(stdout); if (ferror(stdout)) fserr_exit(opts->pzProgName, zwriting, zstdout_name); option_exits(EXIT_SUCCESS); #endif } /** @} * * Local Variables: * mode: C * c-file-style: "stroustrup" * indent-tabs-mode: nil * End: * end of autoopts/makeshell.c */