/**
* \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
*/
/*=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);
#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;
/**
* 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);
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]);
/**
* 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);
/*
* 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((unsigned char)*pz++)) == NUL)
break;
}
{
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;
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 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;
/**
* 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;
/**
* 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((unsigned char)od->pz_DisableName[match_ct])
== toupper((unsigned char)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;
/*
* 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);
/**
* 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)
{
*(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);
/*=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);
/*
* 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 */