/** * \file load.c * * 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. * * @addtogroup autoopts * @{ */ /* * This file is part of AutoOpts, a companion to AutoGen. * AutoOpts is free software. * AutoOpts is Copyright (C) 1992-2016 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 */ /* = = = START-STATIC-FORWARD = = = */ static bool get_realpath(char * buf, size_t b_sz); static bool add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path); static bool add_env_val(char * buf, int buf_sz, char const * name); static char * assemble_arg_val(char * txt, tOptionLoadMode mode); static char * trim_quotes(char * arg); static bool direction_ok(opt_state_mask_t f, int dir); /* = = = END-STATIC-FORWARD = = = */ static bool get_realpath(char * buf, size_t b_sz) { #if defined(HAVE_CANONICALIZE_FILE_NAME) { size_t name_len; char * pz = canonicalize_file_name(buf); if (pz == NULL) return false; name_len = strlen(pz); if (name_len >= (size_t)b_sz) { free(pz); return false; } memcpy(buf, pz, name_len + 1); free(pz); } #elif defined(HAVE_REALPATH) { size_t name_len; char z[PATH_MAX+1]; if (realpath(buf, z) == NULL) return false; name_len = strlen(z); if (name_len >= b_sz) return false; memcpy(buf, z, name_len + 1); } #endif return true; } /*=export_func optionMakePath * private: * * what: translate and construct a path * arg: + char * + p_buf + The result buffer + * arg: + int + b_sz + The size of this buffer + * arg: + char const * + fname + The input name + * arg: + char const * + prg_path + 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 * p_buf, int b_sz, char const * fname, char const * prg_path) { { size_t len = strlen(fname); if (((size_t)b_sz <= len) || (len == 0)) return false; } /* * IF not an environment variable, just copy the data */ if (*fname != '$') { char const * src = fname; char * dst = p_buf; int ct = b_sz; for (;;) { if ( (*(dst++) = *(src++)) == 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 (fname[1]) { case NUL: return false; case '$': if (! add_prog_path(p_buf, b_sz, fname, prg_path)) return false; break; case '@': if (program_pkgdatadir[0] == NUL) return false; if (snprintf(p_buf, (size_t)b_sz, "%s%s", program_pkgdatadir, fname + 2) >= b_sz) return false; break; default: if (! add_env_val(p_buf, b_sz, fname)) return false; } return get_realpath(p_buf, b_sz); } /** * convert a leading "$$" into a path to the executable. */ static bool add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path) { char const * path; char const * pz; int skip = 2; size_t fname_len; size_t dir_len; //!< length of the directory portion of the path to the exe switch (fname[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(prg_path, DIRCH) != NULL) path = prg_path; else { path = pathfind(getenv("PATH"), (char *)prg_path, "rx"); if (path == NULL) return false; } pz = strrchr(path, 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; fname += skip; fname_len = strlen(fname) + 1; // + NUL byte dir_len = (pz - path) + 1; // + dir sep character /* * Concatenate the file name to the end of the executable path. * The result may be either a file or a directory. */ if (dir_len + fname_len > (unsigned)b_sz) return false; memcpy(buf, path, dir_len); memcpy(buf + dir_len, fname, fname_len); /* * If the "path" path was gotten from "pathfind()", then it was * allocated and we need to deallocate it. */ if (path != prg_path) AGFREE(path); return true; } /** * Add an environment variable value. */ 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; { size_t dir_len = strlen(dir_part); size_t nm_len = strlen(name) + 1; if (dir_len + nm_len >= (unsigned)buf_sz) return false; memcpy(buf, dir_part, dir_len); memcpy(buf + dir_len, name, nm_len); } return true; } /** * Trim leading and trailing white space. * If we are cooking the text and the text is quoted, then "cook" * the string. To cook, the string must be quoted. * * @param[in,out] txt the input and output string * @param[in] mode the handling mode (cooking method) */ LOCAL void munge_str(char * txt, tOptionLoadMode mode) { char * end; if (mode == OPTION_LOAD_KEEP) return; if (IS_WHITESPACE_CHAR(*txt)) { char * src = SPN_WHITESPACE_CHARS(txt+1); size_t l = strlen(src) + 1; memmove(txt, src, l); end = txt + l - 1; } else end = txt + strlen(txt); end = SPN_WHITESPACE_BACK(txt, end); *end = NUL; if (mode == OPTION_LOAD_UNCOOKED) return; switch (*txt) { default: return; case '"': case '\'': break; } switch (end[-1]) { default: return; case '"': case '\'': break; } (void)ao_string_cook(txt, NULL); } static char * assemble_arg_val(char * txt, tOptionLoadMode mode) { char * end = strpbrk(txt, ARG_BREAK_STR); int space_break; /* * Not having an argument to a configurable name is okay. */ if (end == 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) { *(end++) = NUL; return end; } /* * 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(*end); *(end++) = NUL; end = SPN_WHITESPACE_CHARS(end); if (space_break && ((*end == ':') || (*end == '='))) end = SPN_WHITESPACE_CHARS(end+1); return end; } static char * trim_quotes(char * arg) { switch (*arg) { case '"': case '\'': ao_string_cook(arg, NULL); } return arg; } /** * See if the option is to be processed in the current scan direction * (-1 or +1). */ static bool direction_ok(opt_state_mask_t f, int dir) { if (dir == 0) return true; switch (f & (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(dir)) return false; break; case OPTST_IMM: if (PRESETTING(dir)) { /* * We are in the presetting direction with an option we handle * immediately for enablement, but normally for disablement. * Therefore, skip if disabled. */ if ((f & OPTST_DISABLED) == 0) return false; } 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 ((f & OPTST_DISABLED) != 0) return false; } break; case OPTST_DISABLE_IMM: if (PRESETTING(dir)) { /* * We are in the presetting direction with an option we handle * immediately for disablement, but normally for disablement. * Therefore, skip if NOT disabled. */ if ((f & OPTST_DISABLED) != 0) return false; } else { /* * We are in the processing direction with an option we handle * immediately for disablement, but normally for disablement. * Therefore, skip if disabled. */ if ((f & OPTST_DISABLED) == 0) return false; } 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(dir)) return false; break; } return true; } /** * 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 load_opt_line(tOptions * opts, tOptState * opt_state, char * line, tDirection direction, tOptionLoadMode load_mode ) { /* * When parsing a stored line, we only look at the characters after * a hyphen. Long names must always be at least two characters and * short options are always exactly one character long. */ line = SPN_LOAD_LINE_SKIP_CHARS(line); { char * arg = assemble_arg_val(line, load_mode); if (IS_OPTION_NAME_CHAR(line[1])) { if (! SUCCESSFUL(opt_find_long(opts, line, opt_state))) return; } else if (! SUCCESSFUL(opt_find_short(opts, *line, opt_state))) return; if ((! CALLED(direction)) && (opt_state->flags & OPTST_NO_INIT)) return; opt_state->pzOptArg = trim_quotes(arg); } if (! direction_ok(opt_state->flags, direction)) return; /* * 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; proc_state_mask_t sv_flags = opts->fOptSet; opts->fOptSet &= ~OPTPROC_ERRSTOP; AGDUPSTR(pz, line, "opt line"); load_opt_line(opts, &st, pz, DIRECTION_CALLED, OPTION_LOAD_COOKED); AGFREE(pz); opts->fOptSet = sv_flags; } /** @} * * Local Variables: * mode: C * c-file-style: "stroustrup" * indent-tabs-mode: nil * End: * end of autoopts/load.c */