/** * Alters the current line number and/or file name. You may wish to * use this directive if you extract definition source from other files. * @command{getdefs} uses this mechanism so AutoGen will report the correct * file and approximate line number of any errors found in extracted * definitions. */ char * doDir_line(directive_enum_t id, char const * dir, char * scan_next) { (void)id; /* * The sequence must be: #line <number> "file-name-string" * * Start by scanning up to and extracting the line number. */ dir = SPN_WHITESPACE_CHARS(dir); if (! IS_DEC_DIGIT_CHAR(*dir)) return scan_next; cctx->scx_line = (int)strtol(dir, (char **)&dir, 0); /* * Now extract the quoted file name string. * We dup the string so it won't disappear on us. */ dir = SPN_WHITESPACE_CHARS(dir); if (*(dir++) != '"') return scan_next; { char * pz = strchr(dir, '"'); if (pz == NULL) return scan_next; *pz = NUL; } AGDUPSTR(cctx->scx_fname, dir, "#line"); return scan_next; }
/** * Configure a dependency option. * Handles any of these letters: MFQTPGD as the first part of the option * argument. * * @param opts the autogen options data structure * @param pOptDesc the option descriptor for this option. */ LOCAL void config_dep(tOptions * opts, tOptDesc * od) { char const * opt_arg = od->optArg.argString; (void)opts; /* * The option argument is optional. Make sure we have one. */ if (opt_arg == NULL) return; while (*opt_arg == 'M') opt_arg++; opt_arg = SPN_WHITESPACE_CHARS(opt_arg); switch (*opt_arg) { case 'F': if (dep_file != NULL) dep_usage(CFGDEP_DUP_TARGET_MSG); opt_arg = SPN_WHITESPACE_CHARS(opt_arg + 1); AGDUPSTR(dep_file, opt_arg, "f name"); break; case 'Q': case 'T': { bool quote_it = (*opt_arg == 'Q'); if (dep_target != NULL) dep_usage(CFGDEP_DUP_TARGET_MSG); opt_arg = SPN_WHITESPACE_CHARS(opt_arg + 1); if (quote_it) dep_target = make_quote_str(opt_arg); else AGDUPSTR(dep_target, opt_arg, "t name"); break; } case 'P': dep_phonies = true; break; case 'D': case 'G': case NUL: /* * 'D' and 'G' make sense to GCC, not us. Ignore 'em. If we * found a NUL byte, then act like we found -MM on the command line. */ break; default: dep_usage(CFGDEP_UNKNOWN_DEP_FMT, opt_arg); } }
/** * Parse the option usage flags string. Any parsing problems yield * a zero (no flags set) result. This function is internal to * set_usage_flags(). * * @param[in] fnt Flag Name Table - maps a name to a mask * @param[in] txt the text to process. If NULL, then * getenv("AUTOOPTS_USAGE") is used. * @returns a bit mask indicating which \a fnt entries were found. */ static unsigned int parse_usage_flags(ao_flag_names_t const * fnt, char const * txt) { unsigned int res = 0; /* * The text may be passed in. If not, use the environment variable. */ if (txt == NULL) { txt = getenv("AUTOOPTS_USAGE"); if (txt == NULL) return 0; } txt = SPN_WHITESPACE_CHARS(txt); if (*txt == NUL) return 0; /* * search the string for table entries. We must understand everything * we see in the string, or we give up on it. */ for (;;) { int ix = 0; for (;;) { if (strneqvcmp(txt, fnt[ix].fnm_name, (int)fnt[ix].fnm_len) == 0) break; if (++ix >= AOUF_COUNT) return 0; } /* * Make sure we have a full match. Look for whitespace, * a comma, or a NUL byte. */ if (! IS_END_LIST_ENTRY_CHAR(txt[fnt[ix].fnm_len])) return 0; res |= 1U << ix; txt = SPN_WHITESPACE_CHARS(txt + fnt[ix].fnm_len); switch (*txt) { case NUL: return res; case ',': txt = SPN_WHITESPACE_CHARS(txt + 1); /* Something must follow the comma */ default: continue; } } }
/** * We've found a closing '>' without a preceding '/', thus we must search * the text for '<name/>' where "name" is the name of the XML element. * * @param[in] name the start of the name in the element header * @param[in] nm_len the length of that name * @param[out] len the length of the value (string between header and * the trailer/tail. * @returns the character after the trailer, or NULL if not found. */ static char const * find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len) { char z[72] = "</"; char * dst = z + 2; do { *(dst++) = *(src++); } while (--nm_len > 0); /* nm_len is known to be 64 or less */ *(dst++) = '>'; *dst = NUL; { char const * res = strstr(val, z); if (res != NULL) { char const * end = (option_load_mode != OPTION_LOAD_KEEP) ? SPN_WHITESPACE_BACK(val, res) : res; *len = (size_t)(end - val); /* includes trailing white space */ res = SPN_WHITESPACE_CHARS(res + (dst - z)); } return res; } }
/** * Scan off the xml element name, and the rest of the header, too. * Set the value type to NONE if it ends with "/>". * * @param[in] name the first name character (alphabetic) * @param[out] nm_len the length of the name * @param[out] val set valType field to STRING or NONE. * * @returns the scan resumption point, or NULL on error */ static char const * scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val) { char const * scan = SPN_VALUE_NAME_CHARS(name + 1); *nm_len = (size_t)(scan - name); if (*nm_len > 64) return NULL; val->valType = OPARG_TYPE_STRING; if (IS_WHITESPACE_CHAR(*scan)) { /* * There are attributes following the name. Parse 'em. */ scan = SPN_WHITESPACE_CHARS(scan); scan = parse_attrs(NULL, scan, &option_load_mode, val); if (scan == NULL) return NULL; /* oops */ } if (! IS_END_XML_TOKEN_CHAR(*scan)) return NULL; /* oops */ if (*scan == '/') { /* * Single element XML entries get inserted as an empty string. */ if (*++scan != '>') return NULL; val->valType = OPARG_TYPE_NONE; } return scan+1; }
static void check_assert_str(char const * pz, char const * arg) { pz = SPN_WHITESPACE_CHARS(pz); if (IS_FALSE_TYPE_CHAR(*pz)) AG_ABEND(aprf(DIRECT_ASSERT_FMT, pz, arg)); }
/** * Check membership start conditions. An equal character (@samp{=}) says to * clear the result and not carry over any residual value. A carat * (@samp{^}), which may follow the equal character, says to invert the * result. The scanning pointer is advanced past these characters and any * leading white space. Invalid sequences are indicated by setting the * scanning pointer to NULL. * * @param od the set membership option description * @param argp a pointer to the string scanning pointer * @param invert a pointer to the boolean inversion indicator * * @returns either zero or the original value for the optCookie. */ static uintptr_t check_membership_start(tOptDesc * od, char const ** argp, bool * invert) { uintptr_t res = (uintptr_t)od->optCookie; char const * arg = SPN_WHITESPACE_CHARS(od->optArg.argString); if ((arg == NULL) || (*arg == NUL)) goto member_start_fail; *invert = false; switch (*arg) { case '=': res = 0UL; arg = SPN_WHITESPACE_CHARS(arg + 1); switch (*arg) { case '=': case ',': goto member_start_fail; case '^': goto inversion; default: break; } break; case '^': inversion: *invert = true; arg = SPN_WHITESPACE_CHARS(arg + 1); if (*arg != ',') break; /* FALLTHROUGH */ case ',': goto member_start_fail; default: break; } *argp = arg; return res; member_start_fail: *argp = NULL; return 0UL; }
/** * This directive will pass the option name and associated text to the * AutoOpts optionLoadLine routine (@pxref{libopts-optionLoadLine}). The * option text may span multiple lines by continuing them with a backslash. * The backslash/newline pair will be replaced with two space characters. * This directive may be used to set a search path for locating template files * For example, this: * * @example * #option templ-dirs $ENVVAR/dirname * @end example * @noindent * will direct autogen to use the @code{ENVVAR} environment variable to find * a directory named @code{dirname} that (may) contain templates. Since these * directories are searched in most recently supplied first order, search * directories supplied in this way will be searched before any supplied on * the command line. */ char * doDir_option(directive_enum_t id, char const * dir, char * scan_next) { dir = SPN_WHITESPACE_CHARS(dir); optionLoadLine(&autogenOptions, dir); (void)id; return scan_next; }
/** * Will remove any entries from the define list * that match the undef name pattern. */ char * doDir_undef(directive_enum_t id, char const * dir, char * scan_next) { dir = SPN_WHITESPACE_CHARS(dir); SET_OPT_UNDEFINE(dir); (void)id; return scan_next; }
static token_list_t * alloc_token_list(char const * str) { token_list_t * res; int max_token_ct = 2; /* allow for trailing NULL pointer & NUL on string */ if (str == NULL) goto enoent_res; /* * Trim leading white space. Use "ENOENT" and a NULL return to indicate * an empty string was passed. */ str = SPN_WHITESPACE_CHARS(str); if (*str == NUL) goto enoent_res; /* * Take an approximate count of tokens. If no quoted strings are used, * it will be accurate. If quoted strings are used, it will be a little * high and we'll squander the space for a few extra pointers. */ { char const * pz = str; do { max_token_ct++; pz = BRK_WHITESPACE_CHARS(pz+1); pz = SPN_WHITESPACE_CHARS(pz); } while (*pz != NUL); res = malloc(sizeof(*res) + (size_t)(pz - str) + ((size_t)max_token_ct * sizeof(ch_t*))); } if (res == NULL) errno = ENOMEM; else res->tkn_list[0] = (ch_t*)(res->tkn_list + (max_token_ct - 1)); return res; enoent_res: errno = ENOENT; return NULL; }
/** * This directive will cause AutoGen to stop processing * and exit with a status of EXIT_FAILURE. */ char * doDir_error(directive_enum_t id, char const * arg, char * scan_next) { arg = SPN_WHITESPACE_CHARS(arg); AG_ABEND(aprf(DIRECT_ERROR_FMT, cctx->scx_fname, cctx->scx_line, arg)); /* NOTREACHED */ (void)scan_next; (void)id; return NULL; }
/** * The definitions that follow, up to the matching @code{#endif} will be * processed only if there is a corresponding @code{-Dname} command line * option or if a @code{#define} of that name has been previously encountered. */ char * doDir_ifdef(directive_enum_t id, char const * dir, char * scan_next) { char const * defstr = get_define_str(SPN_WHITESPACE_CHARS(dir), false); bool ok = (id == DIR_IFDEF) ? (defstr != NULL) : (defstr == NULL); if (! ok) return skip_to_else_end(scan_next); ifdef_lvl++; return scan_next; }
/** * Print the usage information for a single vendor option. * * @param[in] opts the program option descriptor * @param[in] od the option descriptor * @param[in] argtp names of the option argument types * @param[in] usefmt format for primary usage line */ static void prt_one_vendor(tOptions * opts, tOptDesc * od, arg_types_t * argtp, char const * usefmt) { prt_preamble(opts, od, argtp); { char z[ 80 ]; char const * pzArgType; /* * 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) { pzArgType = argtp->pzOpt; } else switch (OPTST_GET_ARGTYPE(od->fOptState)) { case OPARG_TYPE_NONE: pzArgType = argtp->pzNo; break; case OPARG_TYPE_ENUMERATION: pzArgType = argtp->pzKey; break; case OPARG_TYPE_FILE: pzArgType = argtp->pzFile; break; case OPARG_TYPE_MEMBERSHIP: pzArgType = argtp->pzKeyL; break; case OPARG_TYPE_BOOLEAN: pzArgType = argtp->pzBool; break; case OPARG_TYPE_NUMERIC: pzArgType = argtp->pzNum; break; case OPARG_TYPE_HIERARCHY: pzArgType = argtp->pzNest; break; case OPARG_TYPE_STRING: pzArgType = argtp->pzStr; break; case OPARG_TYPE_TIME: pzArgType = argtp->pzTime; break; default: goto bogus_desc; } pzArgType = SPN_WHITESPACE_CHARS(pzArgType); if (*pzArgType == NUL) snprintf(z, sizeof(z), "%s", od->pz_Name); else snprintf(z, sizeof(z), "%s=%s", od->pz_Name, pzArgType); fprintf(option_usage_fp, usefmt, 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); ao_bug(zbad_arg_type_msg); }
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; }
static char * next_directive(char * scan) { if (*scan == '#') scan++; else { char * pz = strstr(scan, DIRECT_CK_LIST_MARK); if (pz == NULL) AG_ABEND(aprf(DIRECT_NOENDIF_FMT, cctx->scx_fname, cctx->scx_line)); scan = pz + 2; } return SPN_WHITESPACE_CHARS(scan); }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * processDirective * * THIS IS THE ONLY EXTERNAL ENTRY POINT * * A directive character has been found. * Decide what to do and return a pointer to the character * where scanning is to resume. */ LOCAL char * processDirective(char * scan) { char * eodir = end_of_directive(scan); /* * Ignore '#!' as a comment, enabling a definition file to behave * as a script that gets interpreted by autogen. :-) */ if (*scan == '!') return eodir; scan = SPN_WHITESPACE_CHARS(scan); if (! IS_ALPHABETIC_CHAR(*scan)) return doDir_invalid(DIR_INVALID, scan, eodir); return doDir_directive_disp(scan, eodir); }
/** * handle AutoOpts mode flags. * * @param[in,out] opts program option descriptor * @param[in] txt scanning pointer * @returns the next character to look at */ static char * aoflags_directive(tOptions * opts, char * txt) { char * pz; pz = SPN_WHITESPACE_CHARS(txt+1); txt = strchr(pz, '>'); if (txt != NULL) { size_t len = (unsigned)(txt - pz); char * ftxt = AGALOC(len + 1, "aoflags"); memcpy(ftxt, pz, len); ftxt[len] = NUL; set_usage_flags(opts, ftxt); AGFREE(ftxt); txt++; } return txt; }
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); }
/** * handle program segmentation of config file. * * @param[in,out] opts program option descriptor * @param[in] txt scanning pointer * @returns the next character to look at */ static char * program_directive(tOptions * opts, char * txt) { static char const ttlfmt[] = "<?"; size_t ttl_len = sizeof(ttlfmt) + strlen(zCfgProg); char * ttl = AGALOC(ttl_len, "prog title"); size_t name_len = strlen(opts->pzProgName); memcpy(ttl, ttlfmt, sizeof(ttlfmt) - 1); memcpy(ttl + sizeof(ttlfmt) - 1, zCfgProg, ttl_len - (sizeof(ttlfmt) - 1)); do { txt = SPN_WHITESPACE_CHARS(txt+1); if ( (strneqvcmp(txt, opts->pzProgName, (int)name_len) == 0) && (IS_END_XML_TOKEN_CHAR(txt[name_len])) ) { txt += name_len; break; } txt = strstr(txt, ttl); } while (txt != NULL); AGFREE(ttl); if (txt != NULL) for (;;) { if (*txt == NUL) { txt = NULL; break; } if (*(txt++) == '>') break; } return txt; }
/** * This directive @i{is} processed, but only if the expression begins with * either a back quote (@code{`}) or an open parenthesis (@code{(}). * Text within the back quotes are handed off to the shell for processing * and parenthesized text is handed off to Guile. Multiple line expressions * must be joined with backslashes. * * If the @code{shell-script} or @code{scheme-expr} do not yield @code{true} * valued results, autogen will be aborted. If @code{<anything else>} or * nothing at all is provided, then this directive is ignored. * * The result is @code{false} (and fails) if the result is empty, the * number zero, or a string that starts with the letters 'n' or 'f' ("no" * or "false"). */ char * doDir_assert(directive_enum_t id, char const * dir, char * scan_next) { (void)id; dir = SPN_WHITESPACE_CHARS(dir); switch (*dir) { case '`': { char * pzS = (char *)dir+1; char * pzR = SPN_WHITESPACE_BACK(pzS, NULL); if (*(--pzR) != '`') break; /* not a valid script */ *pzR = NUL; pzS = shell_cmd((char const *)pzS); check_assert_str(pzS, dir); AGFREE(pzS); break; } case '(': { SCM res = ag_scm_c_eval_string_from_file_line( dir, cctx->scx_fname, cctx->scx_line); check_assert_str(scm2display(res), dir); break; } default: break; } return scan_next; }
/** * We've found a '<' character. We ignore this if it is a comment or a * directive. If it is something else, then whatever it is we are looking * at is bogus. Returning NULL stops processing. * * @param[in] xml_name the name of an xml bracket (usually) * @param[in,out] res_val the option data derived from the XML element * * @returns the place to resume scanning input */ static char const * scan_xml(char const * xml_name, tOptionValue * res_val) { size_t nm_len, v_len; char const * scan; char const * val_str; tOptionValue valu; tOptionLoadMode save_mode = option_load_mode; if (! IS_VAR_FIRST_CHAR(*++xml_name)) return unnamed_xml(xml_name); /* * "scan_xml_name()" may change "option_load_mode". */ val_str = scan_xml_name(xml_name, &nm_len, &valu); if (val_str == NULL) goto bail_scan_xml; if (valu.valType == OPARG_TYPE_NONE) scan = val_str; else { if (option_load_mode != OPTION_LOAD_KEEP) val_str = SPN_WHITESPACE_CHARS(val_str); scan = find_end_xml(xml_name, nm_len, val_str, &v_len); if (scan == NULL) goto bail_scan_xml; } /* * "scan" now points to where the scan is to resume after returning. * It either points after "/>" at the end of the XML element header, * or it points after the "</name>" tail based on the name in the header. */ switch (valu.valType) { case OPARG_TYPE_NONE: add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0); break; case OPARG_TYPE_STRING: { tOptionValue * new_val = add_string( &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); if (option_load_mode != OPTION_LOAD_KEEP) munge_str(new_val->v.strVal, option_load_mode); break; } case OPARG_TYPE_BOOLEAN: add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); break; case OPARG_TYPE_NUMERIC: add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); break; case OPARG_TYPE_HIERARCHY: { char * pz = AGALOC(v_len+1, "h scan"); memcpy(pz, val_str, v_len); pz[v_len] = NUL; add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len); AGFREE(pz); break; } case OPARG_TYPE_ENUMERATION: case OPARG_TYPE_MEMBERSHIP: default: break; } option_load_mode = save_mode; return scan; bail_scan_xml: option_load_mode = save_mode; return NULL; }
/** * "txt" points to a '<' character, followed by an alpha. * The end of the entry is either the "/>" following the name, or else a * "</name>" string. */ static char * handle_struct(tOptions * opts, tOptState * ost, char * txt, int dir) { tOptionLoadMode mode = option_load_mode; tOptionValue valu; char* pzName = ++txt; char* pzData; char* pcNulPoint; txt = SPN_VALUE_NAME_CHARS(txt); pcNulPoint = txt; valu.valType = OPARG_TYPE_STRING; switch (*txt) { case ' ': case '\t': txt = (void *)parse_attrs( opts, SPN_WHITESPACE_CHARS(txt), &mode, &valu); if (txt == NULL) return txt; if (*txt == '>') break; if (*txt != '/') return NULL; /* FALLTHROUGH */ case '/': if (txt[1] != '>') return NULL; *txt = NUL; txt += 2; loadOptionLine(opts, ost, pzName, dir, mode); return txt; case '>': break; default: txt = strchr(txt, '>'); if (txt != NULL) txt++; return txt; } /* * If we are here, we have a value. "txt" points to a closing angle * bracket. Separate the name from the value for a moment. */ *pcNulPoint = NUL; pzData = ++txt; txt = trim_xml_text(txt, pzName, mode); if (txt == NULL) return txt; /* * Rejoin the name and value for parsing by "loadOptionLine()". * Erase any attributes parsed by "parse_attrs()". */ memset(pcNulPoint, ' ', (size_t)(pzData - pcNulPoint)); /* * If we are getting a "string" value that is to be cooked, * then process the XML-ish &xx; XML-ish and %XX hex characters. */ if ( (valu.valType == OPARG_TYPE_STRING) && (mode == OPTION_LOAD_COOKED)) cook_xml_text(pzData); /* * "pzName" points to what looks like text for one option/configurable. * It is NUL terminated. Process it. */ loadOptionLine(opts, ost, pzName, dir, mode); return txt; }
/** * "txt" points to the start of some value name. * The end of the entry is the end of the line that is not preceded by * a backslash escape character. The string value is always processed * in "cooked" mode. */ static char * handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir) { char* pzName = txt++; char* pzEnd = strchr(txt, NL); if (pzEnd == NULL) return txt + strlen(txt); txt = SPN_VALUE_NAME_CHARS(txt); txt = SPN_WHITESPACE_CHARS(txt); if (txt > pzEnd) { name_only: *pzEnd++ = NUL; loadOptionLine(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED); return pzEnd; } /* * Either the first character after the name is a ':' or '=', * or else we must have skipped over white space. Anything else * is an invalid format and we give up parsing the text. */ if ((*txt == '=') || (*txt == ':')) { txt = SPN_WHITESPACE_CHARS(txt+1); if (txt > pzEnd) goto name_only; } else if (! IS_WHITESPACE_CHAR(txt[-1])) return NULL; /* * IF the value is continued, remove the backslash escape and push "pzEnd" * on to a newline *not* preceded by a backslash. */ if (pzEnd[-1] == '\\') { char* pcD = pzEnd-1; char* pcS = pzEnd; for (;;) { char ch = *(pcS++); switch (ch) { case NUL: pcS = NULL; /* FALLTHROUGH */ case NL: *pcD = NUL; pzEnd = pcS; goto copy_done; case '\\': if (*pcS == NL) ch = *(pcS++); /* FALLTHROUGH */ default: *(pcD++) = ch; } } copy_done:; } else { /* * The newline was not preceded by a backslash. NUL it out */ *(pzEnd++) = NUL; } /* * "pzName" points to what looks like text for one option/configurable. * It is NUL terminated. Process it. */ loadOptionLine(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED); return pzEnd; }
/** * Load a file containing presetting information (a configuration file). */ static void file_preset(tOptions * opts, char const * fname, int dir) { tmap_info_t cfgfile; tOptState optst = OPTSTATE_INITIALIZER(PRESET); opt_state_mask_t st_flags = optst.flags; char * ftext = text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &cfgfile); if (TEXT_MMAP_FAILED_ADDR(ftext)) return; if (dir == DIRECTION_CALLED) { st_flags = OPTST_DEFINED; dir = DIRECTION_PROCESS; } /* * IF this is called via "optionProcess", then we are presetting. * This is the default and the PRESETTING bit will be set. * If this is called via "optionFileLoad", then the bit is not set * and we consider stuff set herein to be "set" by the client program. */ if ((opts->fOptSet & OPTPROC_PRESETTING) == 0) st_flags = OPTST_SET; do { optst.flags = st_flags; ftext = SPN_WHITESPACE_CHARS(ftext); if (IS_VAR_FIRST_CHAR(*ftext)) { ftext = handle_cfg(opts, &optst, ftext, dir); } else switch (*ftext) { case '<': if (IS_VAR_FIRST_CHAR(ftext[1])) ftext = handle_struct(opts, &optst, ftext, dir); else switch (ftext[1]) { case '?': ftext = handle_directive(opts, ftext); break; case '!': ftext = handle_comment(ftext); break; case '/': ftext = strchr(ftext + 2, '>'); if (ftext++ != NULL) break; default: ftext = NULL; } if (ftext == NULL) goto all_done; break; case '[': ftext = handle_section(opts, ftext); break; case '#': ftext = strchr(ftext + 1, NL); break; default: goto all_done; /* invalid format */ } } while (ftext != NULL); all_done: text_munmap(&cfgfile); }
/** * Parse the various attributes of an XML-styled config file entry * * @returns NULL on failure, otherwise the scan point */ LOCAL char const * parse_attrs(tOptions * opts, char const * txt, tOptionLoadMode * pMode, tOptionValue * pType) { size_t len = 0; for (;;) { len = (size_t)(SPN_LOWER_CASE_CHARS(txt) - txt); /* * The enumeration used in this switch is derived from this switch * statement itself. The "find_option_xat_attribute_cmd" function * will return XAT_CMD_MEMBERS for the "txt" string value * "members", etc. */ switch (find_option_xat_attribute_cmd(txt, len)) { case XAT_CMD_TYPE: txt = parse_value(txt+len, pType); break; case XAT_CMD_WORDS: txt = parse_keyword(opts, txt+len, pType); break; case XAT_CMD_MEMBERS: txt = parse_set_mem(opts, txt+len, pType); break; case XAT_CMD_COOKED: txt += len; if (! IS_END_XML_TOKEN_CHAR(*txt)) goto invalid_kwd; *pMode = OPTION_LOAD_COOKED; break; case XAT_CMD_UNCOOKED: txt += len; if (! IS_END_XML_TOKEN_CHAR(*txt)) goto invalid_kwd; *pMode = OPTION_LOAD_UNCOOKED; break; case XAT_CMD_KEEP: txt += len; if (! IS_END_XML_TOKEN_CHAR(*txt)) goto invalid_kwd; *pMode = OPTION_LOAD_KEEP; break; default: case XAT_INVALID_CMD: invalid_kwd: pType->valType = OPARG_TYPE_NONE; return skip_unkn(txt); } if (txt == NULL) return NULL; txt = SPN_WHITESPACE_CHARS(txt); switch (*txt) { case '/': pType->valType = OPARG_TYPE_NONE; /* FALLTHROUGH */ case '>': return txt; } if (! IS_LOWER_CASE_CHAR(*txt)) return NULL; } }
/*= * private: * * what: parse a hierarchical option argument * arg: + char const * + pzTxt + the text to scan + * arg: + char const * + pzName + the name for the text + * arg: + size_t + nm_len + the length of "name" + * * ret_type: tOptionValue * * ret_desc: An allocated, compound value structure * * doc: * A block of text represents a series of values. It may be an * entire configuration file, or it may be an argument to an * option that takes a hierarchical value. * * If NULL is returned, errno will be set: * @itemize @bullet * @item * @code{EINVAL} the input text was NULL. * @item * @code{ENOMEM} the storage structures could not be allocated * @item * @code{ENOMSG} no configuration values were found * @end itemize =*/ LOCAL tOptionValue * optionLoadNested(char const * text, char const * name, size_t nm_len) { tOptionValue * res_val; /* * Make sure we have some data and we have space to put what we find. */ if (text == NULL) { errno = EINVAL; return NULL; } text = SPN_WHITESPACE_CHARS(text); if (*text == NUL) { errno = ENOMSG; return NULL; } res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args"); res_val->valType = OPARG_TYPE_HIERARCHY; res_val->pzName = (char *)(res_val + 1); memcpy(res_val->pzName, name, nm_len); res_val->pzName[nm_len] = NUL; { tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l"); res_val->v.nestVal = arg_list; arg_list->useCt = 0; arg_list->allocCt = MIN_ARG_ALLOC_CT; } /* * Scan until we hit a NUL. */ do { text = SPN_WHITESPACE_CHARS(text); if (IS_VAR_FIRST_CHAR(*text)) text = scan_name(text, res_val); else switch (*text) { case NUL: goto scan_done; case '<': text = scan_xml(text, res_val); if (text == NULL) goto woops; if (*text == ',') text++; break; case '#': text = strchr(text, NL); break; default: goto woops; } } while (text != NULL); scan_done:; { tArgList * al = res_val->v.nestVal; if (al->useCt == 0) { errno = ENOMSG; goto woops; } if (al->useCt > 1) sort_list(al); } return res_val; woops: AGFREE(res_val->v.nestVal); AGFREE(res_val); return NULL; }
/*=export_func optionSetMembers * what: Convert between bit flag values and strings * private: * * arg: tOptions *, opts, the program options descriptor * arg: tOptDesc *, od, the set membership option description * arg: char const * const *, * nm_list, list of enumeration names * arg: unsigned int, nm_ct, number of names in list * * doc: This converts the optArg.argString string from the option description * into the index corresponding to an entry in the name list. * This will match the generated enumeration value. * Full matches are always accepted. Partial matches are accepted * if there is only one partial match. =*/ void optionSetMembers(tOptions * opts, tOptDesc * od, char const * const * nm_list, unsigned int nm_ct) { /* * IF the program option descriptor pointer is invalid, * then it is some sort of special request. */ switch ((uintptr_t)opts) { case (uintptr_t)OPTPROC_EMIT_USAGE: enum_err(OPTPROC_EMIT_USAGE, od, nm_list, nm_ct); return; case (uintptr_t)OPTPROC_EMIT_SHELL: set_memb_shell(opts, od, nm_list, nm_ct); return; case (uintptr_t)OPTPROC_RETURN_VALNAME: set_memb_names(opts, od, nm_list, nm_ct); return; default: break; } if ((od->fOptState & OPTST_RESET) != 0) return; { char const * arg; bool invert; uintptr_t res = check_membership_start(od, &arg, &invert); if (arg == NULL) goto fail_return; while (*arg != NUL) { bool inv_val = false; int len; switch (*arg) { case ',': arg = SPN_WHITESPACE_CHARS(arg+1); if ((*arg == ',') || (*arg == '|')) goto fail_return; continue; case '-': case '!': inv_val = true; /* FALLTHROUGH */ case '+': case '|': arg = SPN_WHITESPACE_CHARS(arg+1); } len = (int)(BRK_SET_SEPARATOR_CHARS(arg) - arg); if (len == 0) break; if ((len == 3) && (strncmp(arg, zAll, 3) == 0)) { if (inv_val) res = 0; else res = ~0UL; } else if ((len == 4) && (strncmp(arg, zNone, 4) == 0)) { if (! inv_val) res = 0; } else do { char * pz; uintptr_t bit = strtoul(arg, &pz, 0); if (pz != arg + len) { bit = find_member_bit(opts, od, pz, len, nm_list, nm_ct); if (bit == 0UL) goto fail_return; } if (inv_val) res &= ~bit; else res |= bit; } while (false); arg = SPN_WHITESPACE_CHARS(arg + len); } if (invert) res ^= ~0UL; if (nm_ct < (8 * sizeof(uintptr_t))) res &= (1UL << nm_ct) - 1UL; od->optCookie = VOIDP(res); } return; fail_return: od->optCookie = VOIDP(0); }
/** * Will add the name to the define list as if it were a DEFINE program * argument. Its value will be the first non-whitespace token following * the name. Quotes are @strong{not} processed. * * After the definitions file has been processed, any remaining entries * in the define list will be added to the environment. */ char * doDir_define(directive_enum_t id, char const * dir, char * scan_next) { char * def_name = SPN_WHITESPACE_CHARS(dir); (void)id; /* * Skip any #defines that do not look reasonable */ if (! IS_VAR_FIRST_CHAR(*def_name)) return scan_next; dir = SPN_VARIABLE_NAME_CHARS(def_name); /* * IF this is a macro definition (rather than a value def), * THEN we will ignore it. */ if (*dir == '(') return scan_next; /* * We have found the end of the name. * IF there is no more data on the line, * THEN we do not have space for the '=' required by PUTENV. * Therefore, move the name back over the "#define" * directive itself, giving us the space needed. */ if (! IS_WHITESPACE_CHAR(*dir)) { char * pzS = (char *)dir; char * pzD = (def_name - 6); *pzS = NUL; // whatever it used to be, it is a NUL now. pzS = def_name; while ((*(pzD++) = *(pzS++)) != NUL) ; pzD[-1] = '='; pzD[ 0] = NUL; def_name -= 6; } else { /* * Otherwise, insert the '=' and move any data up against it. * We only accept one name-type, space separated token. * We are not ANSI-C. ;-) */ char * pz = (char *)(dir++); *pz++ = '='; dir = SPN_WHITESPACE_CHARS(dir); /* * Copy chars for so long as it is not NUL and does not require quoting */ for (;;) { if ((*pz++ = *dir++) == NUL) break; if (! IS_UNQUOTABLE_CHAR(*dir)) { *pz = NUL; break; } } } SET_OPT_DEFINE(def_name); return scan_next; }
/*=export_func ao_string_tokenize * * what: tokenize an input string * * arg: + char const* + string + string to be tokenized + * * ret_type: token_list_t* * ret_desc: pointer to a structure that lists each token * * doc: * * This function will convert one input string into a list of strings. * The list of strings is derived by separating the input based on * white space separation. However, if the input contains either single * or double quote characters, then the text after that character up to * a matching quote will become the string in the list. * * The returned pointer should be deallocated with @code{free(3C)} when * are done using the data. The data are placed in a single block of * allocated memory. Do not deallocate individual token/strings. * * The structure pointed to will contain at least these two fields: * @table @samp * @item tkn_ct * The number of tokens found in the input string. * @item tok_list * An array of @code{tkn_ct + 1} pointers to substring tokens, with * the last pointer set to NULL. * @end table * * There are two types of quoted strings: single quoted (@code{'}) and * double quoted (@code{"}). Singly quoted strings are fairly raw in that * escape characters (@code{\\}) are simply another character, except when * preceding the following characters: * @example * @code{\\} double backslashes reduce to one * @code{'} incorporates the single quote into the string * @code{\n} suppresses both the backslash and newline character * @end example * * Double quote strings are formed according to the rules of string * constants in ANSI-C programs. * * example: * @example * #include <stdlib.h> * int ix; * token_list_t* ptl = ao_string_tokenize(some_string) * for (ix = 0; ix < ptl->tkn_ct; ix++) * do_something_with_tkn(ptl->tkn_list[ix]); * free(ptl); * @end example * Note that everything is freed with the one call to @code{free(3C)}. * * err: * NULL is returned and @code{errno} will be set to indicate the problem: * @itemize @bullet * @item * @code{EINVAL} - There was an unterminated quoted string. * @item * @code{ENOENT} - The input string was empty. * @item * @code{ENOMEM} - There is not enough memory. * @end itemize =*/ token_list_t* ao_string_tokenize(char const* str) { token_list_t* res = alloc_token_list(str); ch_t* pzDest; /* * Now copy each token into the output buffer. */ if (res == NULL) return res; pzDest = (ch_t*)(res->tkn_list[0]); res->tkn_ct = 0; do { res->tkn_list[ res->tkn_ct++ ] = pzDest; for (;;) { int ch = (ch_t)*str; if (IS_WHITESPACE_CHAR(ch)) { found_white_space: str = SPN_WHITESPACE_CHARS(str+1); break; } switch (ch) { case '"': copy_cooked(&pzDest, &str); if (str == NULL) { free(res); errno = EINVAL; return NULL; } if (IS_WHITESPACE_CHAR(*str)) goto found_white_space; break; case '\'': copy_raw(&pzDest, &str); if (str == NULL) { free(res); errno = EINVAL; return NULL; } if (IS_WHITESPACE_CHAR(*str)) goto found_white_space; break; case NUL: goto copy_done; default: str++; *(pzDest++) = (unsigned char)ch; } } copy_done:; /* * NUL terminate the last token and see if we have any more tokens. */ *(pzDest++) = NUL; } while (*str != NUL); res->tkn_list[ res->tkn_ct ] = NULL; return res; }
/** * This directive will insert definitions from another file into * the current collection. If the file name is adorned with * double quotes or angle brackets (as in a C program), then the * include is ignored. */ char * doDir_include(directive_enum_t id, char const * dir, char * scan_next) { static char const * const apzSfx[] = { DIRECT_INC_DEF_SFX, NULL }; scan_ctx_t * new_ctx; size_t inc_sz; char full_name[ AG_PATH_MAX + 1 ]; (void)id; dir = SPN_WHITESPACE_CHARS(dir); /* * Ignore C-style includes. This allows "C" files to be processed * for their "#define"s. */ if ((*dir == '"') || (*dir == '<')) return scan_next; if (! SUCCESSFUL( find_file(dir, full_name, apzSfx, cctx->scx_fname))) { errno = ENOENT; fswarn("search for", cctx->scx_fname); return scan_next; } /* * Make sure the specified file is a regular file and we can get * the correct size for it. */ inc_sz = file_size(full_name); if (inc_sz == 0) return scan_next; /* * Get the space for the output data and for context overhead. * This is an extra allocation and copy, but easier than rewriting * 'loadData()' for this special context. */ { size_t sz = sizeof(scan_ctx_t) + 4 + inc_sz; new_ctx = (scan_ctx_t *)AGALOC(sz, "inc def head"); memset(VOIDP(new_ctx), 0, sz); new_ctx->scx_line = 1; } /* * Link it into the context stack */ cctx->scx_scan = scan_next; new_ctx->scx_next = cctx; cctx = new_ctx; AGDUPSTR(new_ctx->scx_fname, full_name, "def file"); new_ctx->scx_scan = new_ctx->scx_data = scan_next = (char *)(new_ctx + 1); /* * Read all the data. Usually in a single read, but loop * in case multiple passes are required. */ { FILE * fp = fopen(full_name, "r" FOPEN_TEXT_FLAG); char * pz = scan_next; if (fp == NULL) AG_CANT(DIRECT_INC_CANNOT_OPEN, full_name); if (dep_fp != NULL) add_source_file(full_name); do { size_t rdct = fread(VOIDP(pz), (size_t)1, inc_sz, fp); if (rdct == 0) AG_CANT(DIRECT_INC_CANNOT_READ, full_name); pz += rdct; inc_sz -= rdct; } while (inc_sz > 0); fclose(fp); *pz = NUL; } return scan_next; }