/*=export_func optionRestore * * what: restore option state from memory copy * arg: tOptions*, pOpts, program options descriptor * * doc: Copy back the option state from saved memory. * The allocated memory is left intact, so this routine can be * called repeatedly without having to call optionSaveState again. * If you are restoring a state that was saved before the first call * to optionProcess(3AO), then you may change the contents of the * argc/argv parameters to optionProcess. * * err: If you have not called @code{optionSaveState} before, a diagnostic is * printed to @code{stderr} and exit is called. =*/ void optionRestore(tOptions* pOpts) { tOptions* p = (tOptions*)pOpts->pSavedState; if (p == NULL) { char const * pzName = pOpts->pzProgName; if (pzName == NULL) { pzName = pOpts->pzPROGNAME; if (pzName == NULL) pzName = zNil; } fprintf(stderr, zNoState, pzName); option_exits(EXIT_FAILURE); } pOpts->pSavedState = NULL; optionFree(pOpts); memcpy(pOpts, p, sizeof(*p)); memcpy(pOpts->pOptDesc, p+1, (size_t)p->optCt * sizeof(tOptDesc)); pOpts->pSavedState = p; fixupSavedOptionArgs(pOpts); }
/*=export_func optionProcess * * what: this is the main option processing routine * * arg: + tOptions* + opts + program options descriptor + * arg: + int + a_ct + program arg count + * arg: + char** + a_v + program arg vector + * * ret_type: int * ret_desc: the count of the arguments processed * * doc: * * This is the main entry point for processing options. It is intended * that this procedure be called once at the beginning of the execution of * a program. Depending on options selected earlier, it is sometimes * necessary to stop and restart option processing, or to select completely * different sets of options. This can be done easily, but you generally * do not want to do this. * * The number of arguments processed always includes the program name. * If one of the arguments is "--", then it is counted and the processing * stops. If an error was encountered and errors are to be tolerated, then * the returned value is the index of the argument causing the error. * A hyphen by itself ("-") will also cause processing to stop and will * @emph{not} be counted among the processed arguments. A hyphen by itself * is treated as an operand. Encountering an operand stops option * processing. * * err: Errors will cause diagnostics to be printed. @code{exit(3)} may * or may not be called. It depends upon whether or not the options * were generated with the "allow-errors" attribute, or if the * ERRSKIP_OPTERR or ERRSTOP_OPTERR macros were invoked. =*/ int optionProcess(tOptions * opts, int a_ct, char ** a_v) { if (! SUCCESSFUL(validate_struct(opts, a_v[0]))) ao_bug(zbad_data_msg); /* * Establish the real program name, the program full path, * and do all the presetting the first time thru only. */ if (! ao_initialize(opts, a_ct, a_v)) return 0; /* * IF we are (re)starting, * THEN reset option location */ if (opts->curOptIdx <= 0) { opts->curOptIdx = 1; opts->pzCurOpt = NULL; } if (! SUCCESSFUL(regular_opts(opts))) return (int)opts->origArgCt; /* * IF there were no errors * AND we have RC/INI files * AND there is a request to save the files * THEN do that now before testing for conflicts. * (conflicts are ignored in preset options) */ switch (opts->specOptIdx.save_opts) { case 0: case NO_EQUIVALENT: break; default: { tOptDesc * od = opts->pOptDesc + opts->specOptIdx.save_opts; if (SELECTED_OPT(od)) { optionSaveFile(opts); option_exits(EXIT_SUCCESS); } } } /* * IF we are checking for errors, * THEN look for too few occurrences of required options */ if (((opts->fOptSet & OPTPROC_ERRSTOP) != 0) && (! is_consistent(opts))) (*opts->pUsageProc)(opts, EXIT_FAILURE); return (int)opts->curOptIdx; }
LOCAL char * ao_strdup(char const *str) { char * res = strdup(str); if (res == NULL) { fprintf(stderr, zalloc_fail, (int)strlen(str)); option_exits(EXIT_FAILURE); } return res; }
LOCAL void * ao_realloc(void *p, size_t sz) { void * res = (p == NULL) ? malloc(sz) : realloc(p, sz); if (res == NULL) { fprintf(stderr, zrealloc_fail, (int)sz, p); option_exits(EXIT_FAILURE); } return res; }
LOCAL void * ao_malloc(size_t sz) { void * res = malloc(sz); if (res == NULL) { fprintf(stderr, zalloc_fail, (int)sz); option_exits(EXIT_FAILURE); } return res; }
/** * Print the usage information for a single option. * * @param opts the program option descriptor * @param od the option descriptor * @param at names of the option argument types */ static void prt_one_usage(tOptions * opts, tOptDesc * od, arg_types_t * at) { prt_preamble(opts, od, at); { char z[80]; char const * atyp; /* * Determine the argument type string first on its usage, then, * when the option argument is required, base the type string on the * argument type. */ if (od->fOptState & OPTST_ARG_OPTIONAL) { atyp = at->pzOpt; } else switch (OPTST_GET_ARGTYPE(od->fOptState)) { case OPARG_TYPE_NONE: atyp = at->pzNo; break; case OPARG_TYPE_ENUMERATION: atyp = at->pzKey; break; case OPARG_TYPE_FILE: atyp = at->pzFile; break; case OPARG_TYPE_MEMBERSHIP: atyp = at->pzKeyL; break; case OPARG_TYPE_BOOLEAN: atyp = at->pzBool; break; case OPARG_TYPE_NUMERIC: atyp = at->pzNum; break; case OPARG_TYPE_HIERARCHY: atyp = at->pzNest; break; case OPARG_TYPE_STRING: atyp = at->pzStr; break; case OPARG_TYPE_TIME: atyp = at->pzTime; break; default: goto bogus_desc; } #ifdef _WIN32 if (at->pzOptFmt == zGnuOptFmt) snprintf(z, sizeof(z), "--%s%s", od->pz_Name, atyp); else if (at->pzOptFmt == zGnuOptFmt + 2) snprintf(z, sizeof(z), "%s%s", od->pz_Name, atyp); else #endif snprintf(z, sizeof(z), at->pzOptFmt, atyp, od->pz_Name, (od->optMinCt != 0) ? at->pzReq : at->pzOpt); fprintf(option_usage_fp, line_fmt_buf, z, od->pzText); switch (OPTST_GET_ARGTYPE(od->fOptState)) { case OPARG_TYPE_ENUMERATION: case OPARG_TYPE_MEMBERSHIP: displayEnum = (od->pOptProc != NULL) ? true : displayEnum; } } return; bogus_desc: fprintf(stderr, zbad_od, opts->pzProgName, od->pz_Name); option_exits(EX_SOFTWARE); }
/** * Process option with a required argument. Long options can either have a * separate command line argument, or an argument attached by the '=' * character. Figure out which. * * @param[in,out] opts the program option descriptor * @param[in,out] o_st the option processing state * @returns SUCCESS or FAILURE */ static tSuccess get_opt_arg_must(tOptions * opts, tOptState * o_st) { switch (o_st->optType) { case TOPT_SHORT: /* * See if an arg string follows the flag character */ if (*++(opts->pzCurOpt) == NUL) opts->pzCurOpt = opts->origArgVect[ opts->curOptIdx++ ]; o_st->pzOptArg = opts->pzCurOpt; break; case TOPT_LONG: /* * See if an arg string has already been assigned (glued on * with an `=' character) */ if (o_st->pzOptArg == NULL) o_st->pzOptArg = opts->origArgVect[ opts->curOptIdx++ ]; break; default: #ifdef DEBUG fputs("AutoOpts lib error: option type not selected\n", stderr); option_exits(EXIT_FAILURE); #endif case TOPT_DEFAULT: /* * The option was selected by default. The current token is * the option argument. */ break; } /* * Make sure we did not overflow the argument list. */ if (opts->curOptIdx > opts->origArgCt) { fprintf(stderr, zMisArg, opts->pzProgPath, o_st->pOD->pz_Name); return FAILURE; } opts->pzCurOpt = NULL; /* next time advance to next arg */ return SUCCESS; }
/*=export_func optionAlias * private: * * what: relay an option to its alias * arg: + tOptions * + opts + program options descriptor + * arg: + tOptDesc * + old_od + the descriptor for this arg + * arg: + unsigned int + alias + the aliased-to option index + * ret-type: int * * doc: * Handle one option as if it had been specified as another. Exactly. * Returns "-1" if the aliased-to option has appeared too many times. =*/ int optionAlias(tOptions * opts, tOptDesc * old_od, unsigned int alias) { tOptDesc * new_od; if (opts <= OPTPROC_EMIT_LIMIT) return 0; new_od = opts->pOptDesc + alias; if ((unsigned)opts->optCt <= alias) { fputs(zbad_alias_id, stderr); option_exits(EXIT_FAILURE); } /* * Copy over the option instance flags */ new_od->fOptState &= OPTST_PERSISTENT_MASK; new_od->fOptState |= (old_od->fOptState & ~OPTST_PERSISTENT_MASK); new_od->optArg.argString = old_od->optArg.argString; /* * Keep track of count only for DEFINED (command line) options. * IF we have too many, build up an error message and bail. */ if ( (new_od->fOptState & OPTST_DEFINED) && (++new_od->optOccCt > new_od->optMaxCt) ) return too_many_occurrences(opts, new_od); /* * Clear the state bits and counters */ old_od->fOptState &= OPTST_PERSISTENT_MASK; old_od->optOccCt = 0; /* * If there is a procedure to call, call it */ if (new_od->pOptProc != NULL) (*new_od->pOptProc)(opts, new_od); return 0; }
/*=export_func optionQuoteString * private: * * what: Print a string as quoted text suitable for a C compiler. * arg: + char const * + text + a block of text to quote + * arg: + char const * + nl + line splice text + * * ret_type: char const * * ret_desc: the allocated input string as a quoted string * * doc: * This is for internal use by autogen and autoopts. * It takes an input string and produces text the C compiler can process * to produce an exact copy of the original string. * The caller must deallocate the result. Standard C strings and * K&R strings are distinguished by the "nl" string. =*/ char const * optionQuoteString(char const * text, char const * nl) { size_t nl_len = strlen(nl); size_t out_sz = string_size(text, nl_len); char * out; char * res = out = AGALOC(out_sz, "quot str"); *(out++) = '"'; for (;;) { unsigned char ch = (unsigned char)*text; if ((ch >= ' ') && (ch <= '~')) { if ((ch == '"') || (ch == '\\')) /* * We must escape these characters in the output string */ *(out++) = '\\'; *(out++) = (char)ch; } else switch (ch) { # define add_esc_ch(_ch) { *(out++) = '\\'; *(out++) = (_ch); } case BEL: add_esc_ch('a'); break; case BS: add_esc_ch('b'); break; case HT: add_esc_ch('t'); break; case VT: add_esc_ch('v'); break; case FF: add_esc_ch('f'); break; case CR: add_esc_ch('r'); break; case LF: /* * Place contiguous new-lines on a single line. * The current character is a NL, check the next one. */ while (*++text == NL) add_esc_ch('n'); /* * Insert a splice before starting next line */ if (*text != NUL) { memcpy(out, nl, nl_len); out += nl_len; continue; /* text is already at the next character */ } add_esc_ch('n'); /* FALLTHROUGH */ case NUL: /* * End of string. Terminate the quoted output. If necessary, * deallocate the text string. Return the scan resumption point. */ *(out++) = '"'; *(out++) = NUL; #ifndef NDEBUG if ((out - res) > out_sz) { fputs(misguess_len, stderr); option_exits(EXIT_FAILURE); } #endif return res; default: /* * sprintf is safe here, because we already computed * the amount of space we will be using. Assertion is above. */ out += sprintf(out, MK_STR_OCT_FMT, ch); } text++; # undef add_esc_ch } }
LOCAL noreturn void fserr_exit(char const * prog, char const * op, char const * fname) { fserr_warn(prog, op, fname); option_exits(EXIT_FAILURE); }
/*=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); /* NOTREACHED */ case 0: pagerState = PAGER_STATE_CHILD; optionUsage(opts, EXIT_SUCCESS); /* NOTREACHED */ _exit(EXIT_FAILURE); 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); default: { int sts; wait(&sts); } } fflush(stdout); if (ferror(stdout)) fserr_exit(opts->pzProgName, zwriting, zstdout_name); option_exits(EXIT_SUCCESS); #endif }
LOCAL noreturn void ao_bug(char const * msg) { fprintf(stderr, zao_bug_msg, msg); option_exits(EX_SOFTWARE); }
/** * 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); /* NOTREACHED */ case TT_USAGE: (*(opts->pUsageProc))(opts, EXIT_FAILURE); /* 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); /* NOTREACHED */ default: option_exits(EXIT_FAILURE); /* NOTREACHED */ } /* NOTREACHED */ default: close(fdpair[1]); } emit_var_text(opts->pzPROGNAME, ttnames[which], fdpair[0]); #endif }
/*=export_func optionUsage * private: * * what: Print usage text * arg: + tOptions * + opts + program options descriptor + * arg: + int + exitCode + exit code for calling exit(3) + * * doc: * This routine will print usage in both GNU-standard and AutoOpts-expanded * formats. The descriptor specifies the default, but AUTOOPTS_USAGE will * over-ride this, providing the value of it is set to either "gnu" or * "autoopts". This routine will @strong{not} return. * * If "exitCode" is "AO_EXIT_REQ_USAGE" (normally 64), then output will to * to stdout and the actual exit code will be "EXIT_SUCCESS". =*/ void optionUsage(tOptions * opts, int usage_exit_code) { int exit_code = (usage_exit_code == AO_EXIT_REQ_USAGE) ? EXIT_SUCCESS : usage_exit_code; displayEnum = false; set_usage_flags(opts, NULL); /* * Paged usage will preset option_usage_fp to an output file. * If it hasn't already been set, then set it to standard output * on successful exit (help was requested), otherwise error out. * * Test the version before obtaining pzFullUsage or pzShortUsage. * These fields do not exist before revision 30. */ { char const * pz; if (exit_code == EXIT_SUCCESS) { pz = (opts->structVersion >= 30 * 4096) ? opts->pzFullUsage : NULL; if (option_usage_fp == NULL) option_usage_fp = print_exit ? stderr : stdout; } else { pz = (opts->structVersion >= 30 * 4096) ? opts->pzShortUsage : NULL; if (option_usage_fp == NULL) option_usage_fp = stderr; } if (((opts->fOptSet & OPTPROC_COMPUTE) == 0) && (pz != NULL)) { if ((opts->fOptSet & OPTPROC_TRANSLATE) != 0) optionPrintParagraphs(pz, true, option_usage_fp); else fputs(pz, option_usage_fp); goto flush_and_exit; } } fprintf(option_usage_fp, opts->pzUsageTitle, opts->pzProgName); if ((exit_code == EXIT_SUCCESS) || (! skip_misuse_usage(opts))) print_usage_details(opts, usage_exit_code); else print_offer_usage(opts); flush_and_exit: fflush(option_usage_fp); if (ferror(option_usage_fp) != 0) fserr_exit(opts->pzProgName, zwriting, (option_usage_fp == stdout) ? zstdout_name : zstderr_name); option_exits(exit_code); }
/*=export_func optionTimeDate * private: * * what: process an option with a time and date. * arg: + tOptions * + opts + program options descriptor + * arg: + tOptDesc * + od + the descriptor for this arg + * * doc: * Decipher a time and date value. =*/ void optionTimeDate(tOptions * opts, tOptDesc * od) { #if defined(HAVE_GETDATE_R) && defined(HAVE_PUTENV) if (INQUERY_CALL(opts, od)) return; if ((! HAS_pzPkgDataDir(opts)) || (opts->pzPkgDataDir == NULL)) goto default_action; /* * Export the DATEMSK environment variable. getdate_r() uses it to * find the file with the strptime formats. If we cannot find the file * we need ($PKGDATADIR/datemsk), then fall back to just a time duration. */ { static char * envptr = NULL; if (envptr == NULL) { static char const fmt[] = "DATEMSK=%s/datemsk"; size_t sz = sizeof(fmt) + strlen(opts->pzPkgDataDir); envptr = AGALOC(sz, fmt); if (snprintf(envptr, sz, fmt, opts->pzPkgDataDir) >= (int)sz) option_exits(EXIT_FAILURE); putenv(envptr); } if (access(envptr+8, R_OK) != 0) goto default_action; } /* * Convert the date to a time since the epoch and stash it in a long int. */ { struct tm stm; time_t tm; if (getdate_r(od->optArg.argString, &stm) != 0) { fprintf(stderr, zNotDate, opts->pzProgName, od->optArg.argString); if ((opts->fOptSet & OPTPROC_ERRSTOP) != 0) (*(opts->pUsageProc))(opts, EXIT_FAILURE); return; } tm = mktime(&stm); if (od->fOptState & OPTST_ALLOC_ARG) { AGFREE(od->optArg.argString); od->fOptState &= ~OPTST_ALLOC_ARG; } od->optArg.argInt = tm; } return; default_action: #endif optionTimeVal(opts, od); if (od->optArg.argInt != BAD_TIME) od->optArg.argInt += (long)time(NULL); }
static void enum_err(tOptions * pOpts, tOptDesc * pOD, char const * const * paz_names, int name_ct) { size_t max_len = 0; size_t ttl_len = 0; int ct_down = name_ct; int hidden = 0; /* * A real "pOpts" pointer means someone messed up. Give a real error. */ if (pOpts > OPTPROC_EMIT_LIMIT) fprintf(option_usage_fp, pz_enum_err_fmt, pOpts->pzProgName, pOD->optArg.argString, pOD->pz_Name); fprintf(option_usage_fp, zValidKeys, pOD->pz_Name); /* * If the first name starts with this funny character, then we have * a first value with an unspellable name. You cannot specify it. * So, we don't list it either. */ if (**paz_names == 0x7F) { paz_names++; hidden = 1; ct_down = --name_ct; } /* * Figure out the maximum length of any name, plus the total length * of all the names. */ { char const * const * paz = paz_names; do { size_t len = strlen(*(paz++)) + 1; if (len > max_len) max_len = len; ttl_len += len; } while (--ct_down > 0); ct_down = name_ct; } /* * IF any one entry is about 1/2 line or longer, print one per line */ if (max_len > 35) { do { fprintf(option_usage_fp, ENUM_ERR_LINE, *(paz_names++)); } while (--ct_down > 0); } /* * ELSE IF they all fit on one line, then do so. */ else if (ttl_len < 76) { fputc(' ', option_usage_fp); do { fputc(' ', option_usage_fp); fputs(*(paz_names++), option_usage_fp); } while (--ct_down > 0); fputc(NL, option_usage_fp); } /* * Otherwise, columnize the output */ else { unsigned int ent_no = 0; char fmt[16]; /* format for all-but-last entries on a line */ if (snprintf(fmt, 16, ENUM_ERR_WIDTH, (int)max_len) >= 16) option_exits(EXIT_FAILURE); max_len = 78 / max_len; /* max_len is now max entries on a line */ fputs(TWO_SPACES_STR, option_usage_fp); /* * Loop through all but the last entry */ ct_down = name_ct; while (--ct_down > 0) { if (++ent_no == max_len) { /* * Last entry on a line. Start next line, too. */ fprintf(option_usage_fp, NLSTR_SPACE_FMT, *(paz_names++)); ent_no = 0; } else fprintf(option_usage_fp, fmt, *(paz_names++) ); } fprintf(option_usage_fp, NLSTR_FMT, *paz_names); } if (pOpts > OPTPROC_EMIT_LIMIT) { fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden); (*(pOpts->pUsageProc))(pOpts, EXIT_FAILURE); /* NOTREACHED */ } if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_MEMBERSHIP) { fprintf(option_usage_fp, zLowerBits, name_ct); fputs(zSetMemberSettings, option_usage_fp); } else { fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden); } }