diff options
Diffstat (limited to 'autoopts/load.c')
-rw-r--r-- | autoopts/load.c | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/autoopts/load.c b/autoopts/load.c new file mode 100644 index 0000000..06b0079 --- /dev/null +++ b/autoopts/load.c @@ -0,0 +1,519 @@ + +/** + * \file load.c + * Time-stamp: "2012-08-11 08:20:09 bkorb" + * + * This file contains the routines that deal with processing text strings + * for options, either from a NUL-terminated string passed in or from an + * rc/ini file. + * + * This file is part of AutoOpts, a companion to AutoGen. + * AutoOpts is free software. + * AutoOpts is Copyright (c) 1992-2012 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 md5sums: + * + * 43b91e8ca915626ed3818ffb1b71248b pkg/libopts/COPYING.gplv3 + * 06a1a2e4760c90ea5e1dad8dfaac4d39 pkg/libopts/COPYING.lgplv3 + * 66a5cedaf62c4b2637025f049f9b826f pkg/libopts/COPYING.mbsd + */ + +/* = = = START-STATIC-FORWARD = = = */ +static bool +add_prog_path(char * pzBuf, int bufSize, char const * pzName, + char const * pzProgPath); + +static bool +add_env_val(char * buf, int buf_sz, char const * name); + +static char * +assemble_arg_val(char * txt, tOptionLoadMode mode); +/* = = = END-STATIC-FORWARD = = = */ + +/*=export_func optionMakePath + * private: + * + * what: translate and construct a path + * arg: + char* + pzBuf + The result buffer + + * arg: + int + bufSize + The size of this buffer + + * arg: + char const* + pzName + The input name + + * arg: + char const* + pzProgPath + The full path of the current program + + * + * ret-type: bool + * ret-desc: true if the name was handled, otherwise false. + * If the name does not start with ``$'', then it is handled + * simply by copying the input name to the output buffer and + * resolving the name with either + * @code{canonicalize_file_name(3GLIBC)} or @code{realpath(3C)}. + * + * doc: + * + * This routine will copy the @code{pzName} input name into the + * @code{pzBuf} output buffer, not exceeding @code{bufSize} bytes. If the + * first character of the input name is a @code{'$'} character, then there + * is special handling: + * @* + * @code{$$} is replaced with the directory name of the @code{pzProgPath}, + * searching @code{$PATH} if necessary. + * @* + * @code{$@} is replaced with the AutoGen package data installation directory + * (aka @code{pkgdatadir}). + * @* + * @code{$NAME} is replaced by the contents of the @code{NAME} environment + * variable. If not found, the search fails. + * + * Please note: both @code{$$} and @code{$NAME} must be at the start of the + * @code{pzName} string and must either be the entire string or be followed + * by the @code{'/'} (backslash on windows) character. + * + * err: @code{false} is returned if: + * @* + * @bullet{} The input name exceeds @code{bufSize} bytes. + * @* + * @bullet{} @code{$$}, @code{$@@} or @code{$NAME} is not the full string + * and the next character is not '/'. + * @* + * @bullet{} libopts was built without PKGDATADIR defined and @code{$@@} + * was specified. + * @* + * @bullet{} @code{NAME} is not a known environment variable + * @* + * @bullet{} @code{canonicalize_file_name} or @code{realpath} return + * errors (cannot resolve the resulting path). +=*/ +bool +optionMakePath(char * pzBuf, int bufSize, char const * pzName, + char const * pzProgPath) +{ + size_t name_len = strlen(pzName); + + if (((size_t)bufSize <= name_len) || (name_len == 0)) + return false; + + /* + * IF not an environment variable, just copy the data + */ + if (*pzName != '$') { + char const* pzS = pzName; + char* pzD = pzBuf; + int ct = bufSize; + + for (;;) { + if ( (*(pzD++) = *(pzS++)) == NUL) + break; + if (--ct <= 0) + return false; + } + } + + /* + * IF the name starts with "$$", then it must be "$$" or + * it must start with "$$/". In either event, replace the "$$" + * with the path to the executable and append a "/" character. + */ + else switch (pzName[1]) { + case NUL: + return false; + + case '$': + if (! add_prog_path(pzBuf, bufSize, pzName, pzProgPath)) + return false; + break; + + case '@': + if (program_pkgdatadir[0] == NUL) + return false; + + if (snprintf(pzBuf, bufSize, "%s%s", program_pkgdatadir, pzName + 2) + >= bufSize) + return false; + break; + + default: + if (! add_env_val(pzBuf, bufSize, pzName)) + return false; + } + +#if defined(HAVE_CANONICALIZE_FILE_NAME) + { + char * pz = canonicalize_file_name(pzBuf); + if (pz == NULL) + return false; + + name_len = strlen(pz); + if (name_len >= (size_t)bufSize) { + free(pz); + return false; + } + + memcpy(pzBuf, pz, name_len + 1); + free(pz); + } + +#elif defined(HAVE_REALPATH) + { + char z[PATH_MAX+1]; + + if (realpath(pzBuf, z) == NULL) + return false; + + name_len = strlen(z); + if (name_len >= bufSize) + return false; + + memcpy(pzBuf, z, name_len + 1); + } +#endif + + return true; +} + +static bool +add_prog_path(char * pzBuf, int bufSize, char const * pzName, + char const * pzProgPath) +{ + char const* pzPath; + char const* pz; + int skip = 2; + + switch (pzName[2]) { + case DIRCH: + skip = 3; + case NUL: + break; + default: + return false; + } + + /* + * See if the path is included in the program name. + * If it is, we're done. Otherwise, we have to hunt + * for the program using "pathfind". + */ + if (strchr(pzProgPath, DIRCH) != NULL) + pzPath = pzProgPath; + else { + pzPath = pathfind(getenv("PATH"), (char*)pzProgPath, "rx"); + + if (pzPath == NULL) + return false; + } + + pz = strrchr(pzPath, DIRCH); + + /* + * IF we cannot find a directory name separator, + * THEN we do not have a path name to our executable file. + */ + if (pz == NULL) + return false; + + pzName += skip; + + /* + * Concatenate the file name to the end of the executable path. + * The result may be either a file or a directory. + */ + if ((pz - pzPath)+1 + strlen(pzName) >= (unsigned)bufSize) + return false; + + memcpy(pzBuf, pzPath, (size_t)((pz - pzPath)+1)); + strcpy(pzBuf + (pz - pzPath) + 1, pzName); + + /* + * If the "pzPath" path was gotten from "pathfind()", then it was + * allocated and we need to deallocate it. + */ + if (pzPath != pzProgPath) + AGFREE(pzPath); + return true; +} + +static bool +add_env_val(char * buf, int buf_sz, char const * name) +{ + char * dir_part = buf; + + for (;;) { + int ch = (int)*++name; + if (! IS_VALUE_NAME_CHAR(ch)) + break; + *(dir_part++) = (char)ch; + } + + if (dir_part == buf) + return false; + + *dir_part = NUL; + + dir_part = getenv(buf); + + /* + * Environment value not found -- skip the home list entry + */ + if (dir_part == NULL) + return false; + + if (strlen(dir_part) + 1 + strlen(name) >= (unsigned)buf_sz) + return false; + + sprintf(buf, "%s%s", dir_part, name); + return true; +} + +LOCAL void +mungeString(char * txt, tOptionLoadMode mode) +{ + char * pzE; + + if (mode == OPTION_LOAD_KEEP) + return; + + if (IS_WHITESPACE_CHAR(*txt)) { + char * pzS = SPN_WHITESPACE_CHARS(txt+1); + size_t l = strlen(pzS) + 1; + memmove(txt, pzS, l); + pzE = txt + l - 1; + + } else + pzE = txt + strlen(txt); + + pzE = SPN_WHITESPACE_BACK(txt, pzE); + *pzE = NUL; + + if (mode == OPTION_LOAD_UNCOOKED) + return; + + switch (*txt) { + default: return; + case '"': + case '\'': break; + } + + switch (pzE[-1]) { + default: return; + case '"': + case '\'': break; + } + + (void)ao_string_cook(txt, NULL); +} + +static char * +assemble_arg_val(char * txt, tOptionLoadMode mode) +{ + char* pzEnd = strpbrk(txt, ARG_BREAK_STR); + int space_break; + + /* + * Not having an argument to a configurable name is okay. + */ + if (pzEnd == NULL) + return txt + strlen(txt); + + /* + * If we are keeping all whitespace, then the modevalue starts with the + * character that follows the end of the configurable name, regardless + * of which character caused it. + */ + if (mode == OPTION_LOAD_KEEP) { + *(pzEnd++) = NUL; + return pzEnd; + } + + /* + * If the name ended on a white space character, remember that + * because we'll have to skip over an immediately following ':' or '=' + * (and the white space following *that*). + */ + space_break = IS_WHITESPACE_CHAR(*pzEnd); + *(pzEnd++) = NUL; + + pzEnd = SPN_WHITESPACE_CHARS(pzEnd); + if (space_break && ((*pzEnd == ':') || (*pzEnd == '='))) + pzEnd = SPN_WHITESPACE_CHARS(pzEnd+1); + + return pzEnd; +} + +/** + * Load an option from a block of text. The text must start with the + * configurable/option name and be followed by its associated value. + * That value may be processed in any of several ways. See "tOptionLoadMode" + * in autoopts.h. + * + * @param[in,out] opts program options descriptor + * @param[in,out] opt_state option processing state + * @param[in,out] line source line with long option name in it + * @param[in] direction current processing direction (preset or not) + * @param[in] load_mode option loading mode (OPTION_LOAD_*) + */ +LOCAL void +loadOptionLine( + tOptions * opts, + tOptState * opt_state, + char * line, + tDirection direction, + tOptionLoadMode load_mode ) +{ + line = SPN_LOAD_LINE_SKIP_CHARS(line); + + { + char * arg = assemble_arg_val(line, load_mode); + + if (! SUCCESSFUL(opt_find_long(opts, line, opt_state))) + return; + + if (opt_state->flags & OPTST_NO_INIT) + return; + + opt_state->pzOptArg = arg; + } + + switch (opt_state->flags & (OPTST_IMM|OPTST_DISABLE_IMM)) { + case 0: + /* + * The selected option has no immediate action. + * THEREFORE, if the direction is PRESETTING + * THEN we skip this option. + */ + if (PRESETTING(direction)) + return; + break; + + case OPTST_IMM: + if (PRESETTING(direction)) { + /* + * We are in the presetting direction with an option we handle + * immediately for enablement, but normally for disablement. + * Therefore, skip if disabled. + */ + if ((opt_state->flags & OPTST_DISABLED) == 0) + return; + } else { + /* + * We are in the processing direction with an option we handle + * immediately for enablement, but normally for disablement. + * Therefore, skip if NOT disabled. + */ + if ((opt_state->flags & OPTST_DISABLED) != 0) + return; + } + break; + + case OPTST_DISABLE_IMM: + if (PRESETTING(direction)) { + /* + * We are in the presetting direction with an option we handle + * immediately for disablement, but normally for disablement. + * Therefore, skip if NOT disabled. + */ + if ((opt_state->flags & OPTST_DISABLED) != 0) + return; + } else { + /* + * We are in the processing direction with an option we handle + * immediately for disablement, but normally for disablement. + * Therefore, skip if disabled. + */ + if ((opt_state->flags & OPTST_DISABLED) == 0) + return; + } + break; + + case OPTST_IMM|OPTST_DISABLE_IMM: + /* + * The selected option is always for immediate action. + * THEREFORE, if the direction is PROCESSING + * THEN we skip this option. + */ + if (PROCESSING(direction)) + return; + break; + } + + /* + * Fix up the args. + */ + if (OPTST_GET_ARGTYPE(opt_state->pOD->fOptState) == OPARG_TYPE_NONE) { + if (*opt_state->pzOptArg != NUL) + return; + opt_state->pzOptArg = NULL; + + } else if (opt_state->pOD->fOptState & OPTST_ARG_OPTIONAL) { + if (*opt_state->pzOptArg == NUL) + opt_state->pzOptArg = NULL; + else { + AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg"); + opt_state->flags |= OPTST_ALLOC_ARG; + } + + } else { + if (*opt_state->pzOptArg == NUL) + opt_state->pzOptArg = zNil; + else { + AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg"); + opt_state->flags |= OPTST_ALLOC_ARG; + } + } + + { + tOptionLoadMode sv = option_load_mode; + option_load_mode = load_mode; + handle_opt(opts, opt_state); + option_load_mode = sv; + } +} + +/*=export_func optionLoadLine + * + * what: process a string for an option name and value + * + * arg: tOptions*, opts, program options descriptor + * arg: char const*, line, NUL-terminated text + * + * doc: + * + * This is a client program callable routine for setting options from, for + * example, the contents of a file that they read in. Only one option may + * appear in the text. It will be treated as a normal (non-preset) option. + * + * When passed a pointer to the option struct and a string, it will find + * the option named by the first token on the string and set the option + * argument to the remainder of the string. The caller must NUL terminate + * the string. The caller need not skip over any introductory hyphens. + * Any embedded new lines will be included in the option + * argument. If the input looks like one or more quoted strings, then the + * input will be "cooked". The "cooking" is identical to the string + * formation used in AutoGen definition files (@pxref{basic expression}), + * except that you may not use backquotes. + * + * err: Invalid options are silently ignored. Invalid option arguments + * will cause a warning to print, but the function should return. +=*/ +void +optionLoadLine(tOptions * opts, char const * line) +{ + tOptState st = OPTSTATE_INITIALIZER(SET); + char* pz; + AGDUPSTR(pz, line, "user option line"); + loadOptionLine(opts, &st, pz, DIRECTION_PROCESS, OPTION_LOAD_COOKED); + AGFREE(pz); +} +/* + * Local Variables: + * mode: C + * c-file-style: "stroustrup" + * indent-tabs-mode: nil + * End: + * end of autoopts/load.c */ |