static void
file_preset(tOptions * opts, char const * fname, int dir);
static char *
handle_comment(char * txt);
static char *
handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir);
static char *
handle_directive(tOptions * opts, char * txt);
static char *
aoflags_directive(tOptions * opts, char * txt);
static char *
program_directive(tOptions * opts, char * txt);
static char *
handle_section(tOptions * opts, char * txt);
static int
parse_xml_encoding(char ** ppz);
static char *
trim_xml_text(char * intxt, char const * pznm, tOptionLoadMode mode);
static void
cook_xml_text(char * pzData);
static char *
handle_struct(tOptions * opts, tOptState * ost, char * txt, int dir);
static char const *
parse_keyword(tOptions * opts, char const * txt, tOptionValue * typ);
static char const *
parse_set_mem(tOptions * opts, char const * txt, tOptionValue * typ);
static char const *
parse_value(char const * txt, tOptionValue * typ);
inline static char const *
skip_unkn(char const * txt)
{
txt = BRK_END_XML_TOKEN_CHARS(txt);
return (*txt == NUL) ? NULL : txt;
}
const tOptionValue *
configFileLoad(char const * fname)
{
tmap_info_t cfgfile;
tOptionValue * res = NULL;
tOptionLoadMode save_mode = option_load_mode;
char * txt = text_mmap(fname, PROT_READ, MAP_PRIVATE, &cfgfile);
if (TEXT_MMAP_FAILED_ADDR(txt))
return NULL;
option_load_mode = OPTION_LOAD_COOKED;
res = optionLoadNested(txt, fname, strlen(fname));
if (res == NULL) {
int err = errno;
text_munmap(&cfgfile);
errno = err;
} else
text_munmap(&cfgfile);
option_load_mode = save_mode;
return res;
}
const tOptionValue *
optionFindValue(const tOptDesc * odesc, char const * name, char const * val)
{
const tOptionValue * res = NULL;
if ( (odesc == NULL)
|| (OPTST_GET_ARGTYPE(odesc->fOptState) != OPARG_TYPE_HIERARCHY)) {
errno = EINVAL;
}
else if (odesc->optCookie == NULL) {
errno = ENOENT;
}
else do {
tArgList * argl = odesc->optCookie;
int argct = argl->useCt;
const void ** poptv = VOIDP(argl->apzArgs);
if (argct == 0) {
errno = ENOENT;
break;
}
if (name == NULL) {
res = (const tOptionValue *)*poptv;
break;
}
while (--argct >= 0) {
const tOptionValue * ov = *(poptv++);
const tOptionValue * rv = optionGetValue(ov, name);
if (rv == NULL)
continue;
if (val == NULL) {
res = ov;
break;
}
}
if (res == NULL)
errno = ENOENT;
} while (false);
return res;
}
tOptionValue const *
optionFindNextValue(const tOptDesc * odesc, const tOptionValue * pPrevVal,
char const * pzName, char const * pzVal)
{
bool old_found = false;
const tOptionValue * res = NULL;
(void)pzName;
(void)pzVal;
if ( (odesc == NULL)
|| (OPTST_GET_ARGTYPE(odesc->fOptState) != OPARG_TYPE_HIERARCHY)) {
errno = EINVAL;
}
else if (odesc->optCookie == NULL) {
errno = ENOENT;
}
else do {
tArgList * argl = odesc->optCookie;
int ct = argl->useCt;
const void ** poptv = VOIDP(argl->apzArgs);
while (--ct >= 0) {
const tOptionValue * pOV = *(poptv++);
if (old_found) {
res = pOV;
break;
}
if (pOV == pPrevVal)
old_found = true;
}
if (res == NULL)
errno = ENOENT;
} while (false);
return res;
}
tOptionValue const *
optionGetValue(tOptionValue const * oov, char const * vname)
{
tArgList * arg_list;
const tOptionValue * res = NULL;
if ((oov == NULL) || (oov->valType != OPARG_TYPE_HIERARCHY)) {
errno = EINVAL;
return res;
}
arg_list = oov->v.nestVal;
if (arg_list->useCt > 0) {
int ct = arg_list->useCt;
const void ** ovlist = VOIDP(arg_list->apzArgs);
if (vname == NULL) {
res = (const tOptionValue *)*ovlist;
} else do {
const tOptionValue * opt_val = *(ovlist++);
if (strcmp(opt_val->pzName, vname) == 0) {
res = opt_val;
break;
}
} while (--ct > 0);
}
if (res == NULL)
errno = ENOENT;
return res;
}
tOptionValue const *
optionNextValue(tOptionValue const * ov_list,tOptionValue const * oov )
{
tArgList * arg_list;
const tOptionValue * res = NULL;
int err = EINVAL;
if ((ov_list == NULL) || (ov_list->valType != OPARG_TYPE_HIERARCHY)) {
errno = EINVAL;
return NULL;
}
arg_list = ov_list->v.nestVal;
{
int ct = arg_list->useCt;
const void ** o_list = VOIDP(arg_list->apzArgs);
while (ct-- > 0) {
const tOptionValue * nov = *(o_list++);
if (nov == oov) {
if (ct == 0) {
err = ENOENT;
} else {
err = 0;
res = (const tOptionValue *)*o_list;
}
break;
}
}
}
if (err != 0)
errno = err;
return res;
}
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;
opt_state_mask_t fl_save = opts->fOptSet;
char * ftext =
text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &cfgfile);
if (TEXT_MMAP_FAILED_ADDR(ftext))
return;
opts->fOptSet &= ~OPTPROC_ERRSTOP;
if (dir == DIRECTION_CALLED) {
st_flags = OPTST_DEFINED;
dir = DIRECTION_PROCESS;
}
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;
}
} while (ftext != NULL);
all_done:
text_munmap(&cfgfile);
opts->fOptSet = fl_save;
}
static char *
handle_comment(char * txt)
{
char * pz = strstr(txt, "-->");
if (pz != NULL)
pz += 3;
return pz;
}
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;
load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
return pzEnd;
}
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 (pzEnd[-1] == '\\') {
char * pcD = pzEnd-1;
char * pcS = pzEnd;
for (;;) {
char ch = *(pcS++);
switch (ch) {
case NUL:
pcS = NULL;
case NL:
*pcD = NUL;
pzEnd = pcS;
goto copy_done;
case '\\':
if (*pcS == NL)
ch = *(pcS++);
default:
*(pcD++) = ch;
}
} copy_done:;
} else {
*(pzEnd++) = NUL;
}
load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
return pzEnd;
}
static char *
handle_directive(tOptions * opts, char * txt)
{
# define DIRECTIVE_TABLE \
_dt_(zCfgProg, program_directive) \
_dt_(zCfgAO_Flags, aoflags_directive)
typedef char * (directive_func_t)(tOptions *, char *);
# define _dt_(_s, _fn) _fn,
static directive_func_t * dir_disp[] = {
DIRECTIVE_TABLE
};
# undef _dt_
# define _dt_(_s, _fn) 1 +
static int const dir_ct = DIRECTIVE_TABLE 0;
static char const * dir_names[DIRECTIVE_TABLE 0];
# undef _dt_
int ix;
if (dir_names[0] == NULL) {
ix = 0;
# define _dt_(_s, _fn) dir_names[ix++] = _s;
DIRECTIVE_TABLE;
# undef _dt_
}
for (ix = 0; ix < dir_ct; ix++) {
size_t len = strlen(dir_names[ix]);
if ( (strncmp(txt + 2, dir_names[ix], len) == 0)
&& (! IS_VALUE_NAME_CHAR(txt[len+2])) )
return dir_disp[ix](opts, txt + len + 2);
}
txt = strchr(txt+2, '>');
if (txt != NULL)
txt++;
return txt;
# undef DIRECTIVE_TABLE
}
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;
}
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;
}
static char *
handle_section(tOptions * opts, char * txt)
{
size_t len = strlen(opts->pzPROGNAME);
if ( (strncmp(txt+1, opts->pzPROGNAME, len) == 0)
&& (txt[len+1] == ']'))
return strchr(txt + len + 2, NL);
if (len > 16)
return NULL;
{
char z[24];
sprintf(z, "[%s]", opts->pzPROGNAME);
txt = strstr(txt, z);
}
if (txt != NULL)
txt = strchr(txt, NL);
return txt;
}
static int
parse_xml_encoding(char ** ppz)
{
# define XMLTABLE \
_xmlNm_(amp, '&') \
_xmlNm_(lt, '<') \
_xmlNm_(gt, '>') \
_xmlNm_(ff, '\f') \
_xmlNm_(ht, '\t') \
_xmlNm_(cr, '\r') \
_xmlNm_(vt, '\v') \
_xmlNm_(bel, '\a') \
_xmlNm_(nl, NL) \
_xmlNm_(space, ' ') \
_xmlNm_(quot, '"') \
_xmlNm_(apos, '\'')
static struct {
char const * const nm_str;
unsigned short nm_len;
short nm_val;
} const xml_names[] = {
# define _xmlNm_(_n, _v) { #_n ";", sizeof(#_n), _v },
XMLTABLE
# undef _xmlNm_
# undef XMLTABLE
};
static int const nm_ct = sizeof(xml_names) / sizeof(xml_names[0]);
int base = 10;
char * pz = *ppz;
if (*pz == '#') {
pz++;
goto parse_number;
}
if (IS_DEC_DIGIT_CHAR(*pz)) {
unsigned long v;
parse_number:
switch (*pz) {
case 'x': case 'X':
base = 16;
pz++;
break;
case '0':
if (pz[1] == '0')
base = 16;
break;
}
v = strtoul(pz, &pz, base);
if ((*pz != ';') || (v > 0x7F))
return NUL;
*ppz = pz + 1;
return (int)v;
}
{
int ix = 0;
do {
if (strncmp(pz, xml_names[ix].nm_str, xml_names[ix].nm_len)
== 0) {
*ppz = pz + xml_names[ix].nm_len;
return xml_names[ix].nm_val;
}
} while (++ix < nm_ct);
}
return NUL;
}
static char *
trim_xml_text(char * intxt, char const * pznm, tOptionLoadMode mode)
{
static char const fmt[] = "</%s>";
size_t len = strlen(pznm) + sizeof(fmt) - 2 ;
char * etext;
{
char z[64], *pz = z;
if (len >= sizeof(z))
pz = AGALOC(len, "scan name");
len = (size_t)sprintf(pz, fmt, pznm);
*intxt = ' ';
etext = strstr(intxt, pz);
if (pz != z) AGFREE(pz);
}
if (etext == NULL)
return etext;
{
char * result = etext + len;
if (mode != OPTION_LOAD_UNCOOKED)
etext = SPN_WHITESPACE_BACK(intxt, etext);
*etext = NUL;
return result;
}
}
static void
cook_xml_text(char * pzData)
{
char * pzs = pzData;
char * pzd = pzData;
char bf[4];
bf[2] = NUL;
for (;;) {
int ch = ((int)*(pzs++)) & 0xFF;
switch (ch) {
case NUL:
*pzd = NUL;
return;
case '&':
ch = parse_xml_encoding(&pzs);
*(pzd++) = (char)ch;
if (ch == NUL)
return;
break;
case '%':
bf[0] = *(pzs++);
bf[1] = *(pzs++);
if ((bf[0] == NUL) || (bf[1] == NUL)) {
*pzd = NUL;
return;
}
ch = (int)strtoul(bf, NULL, 16);
default:
*(pzd++) = (char)ch;
}
}
}
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 = VOIDP(parse_attrs(
opts, SPN_WHITESPACE_CHARS(txt), &mode, &valu));
if (txt == NULL)
return txt;
if (*txt == '>')
break;
if (*txt != '/')
return NULL;
case '/':
if (txt[1] != '>')
return NULL;
*txt = NUL;
txt += 2;
load_opt_line(opts, ost, pzName, dir, mode);
return txt;
case '>':
break;
default:
txt = strchr(txt, '>');
if (txt != NULL)
txt++;
return txt;
}
*pcNulPoint = NUL;
pzData = ++txt;
txt = trim_xml_text(txt, pzName, mode);
if (txt == NULL)
return txt;
memset(pcNulPoint, ' ', (size_t)(pzData - pcNulPoint));
if ( (valu.valType == OPARG_TYPE_STRING)
&& (mode == OPTION_LOAD_COOKED))
cook_xml_text(pzData);
load_opt_line(opts, ost, pzName, dir, mode);
return txt;
}
LOCAL void
intern_file_load(tOptions * opts)
{
uint32_t svfl;
int idx;
int inc;
char f_name[ AG_PATH_MAX+1 ];
if (opts->papzHomeList == NULL)
return;
svfl = opts->fOptSet;
inc = DIRECTION_PRESET;
opts->fOptSet &= ~OPTPROC_ERRSTOP;
for (idx = 0; opts->papzHomeList[ idx+1 ] != NULL; ++idx) ;
for (;;) {
struct stat sb;
cch_t * path;
if (idx < 0) {
inc = DIRECTION_PROCESS;
idx = 0;
}
path = opts->papzHomeList[ idx ];
if (path == NULL)
break;
idx += inc;
if (! optionMakePath(f_name, (int)sizeof(f_name),
path, opts->pzProgPath))
continue;
if (stat(f_name, &sb) != 0)
continue;
if (S_ISDIR(sb.st_mode)) {
size_t len = strlen(f_name);
size_t nln = strlen(opts->pzRcName) + 1;
char * pz = f_name + len;
if (len + 1 + nln >= sizeof(f_name))
continue;
if (pz[-1] != DIRCH)
*(pz++) = DIRCH;
memcpy(pz, opts->pzRcName, nln);
}
file_preset(opts, f_name, inc);
{
tOptDesc * od = opts->pOptDesc + opts->specOptIdx.save_opts + 1;
if (DISABLED_OPT(od) && PRESETTING(inc)) {
idx -= inc;
inc = DIRECTION_PROCESS;
}
}
}
opts->fOptSet = svfl;
}
int
optionFileLoad(tOptions * opts, char const * prog)
{
if (! SUCCESSFUL(validate_struct(opts, prog)))
return -1;
{
char const ** pp = VOIDP(&(opts->pzProgName));
*pp = prog;
}
intern_file_load(opts);
return 0;
}
void
optionLoadOpt(tOptions * opts, tOptDesc * odesc)
{
struct stat sb;
if (opts <= OPTPROC_EMIT_LIMIT)
return;
if ( DISABLED_OPT(odesc)
|| ((odesc->fOptState & OPTST_RESET) != 0))
return;
if (stat(odesc->optArg.argString, &sb) != 0) {
if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
return;
fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
}
if (! S_ISREG(sb.st_mode)) {
if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
return;
errno = EINVAL;
fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
}
file_preset(opts, odesc->optArg.argString, DIRECTION_CALLED);
}
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);
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;
case '>': return txt;
}
if (! IS_LOWER_CASE_CHAR(*txt))
return NULL;
}
}
static char const *
parse_keyword(tOptions * opts, char const * txt, tOptionValue * typ)
{
(void)opts;
(void)typ;
return skip_unkn(txt);
}
static char const *
parse_set_mem(tOptions * opts, char const * txt, tOptionValue * typ)
{
(void)opts;
(void)typ;
return skip_unkn(txt);
}
static char const *
parse_value(char const * txt, tOptionValue * typ)
{
size_t len = 0;
if (*(txt++) != '=')
goto woops;
len = (size_t)(SPN_OPTION_NAME_CHARS(txt) - txt);
if ((len == 0) || (! IS_END_XML_TOKEN_CHAR(txt[len]))) {
woops:
typ->valType = OPARG_TYPE_NONE;
return skip_unkn(txt + len);
}
switch (find_option_value_type_cmd(txt, len)) {
default:
case VTP_INVALID_CMD: goto woops;
case VTP_CMD_STRING:
typ->valType = OPARG_TYPE_STRING;
break;
case VTP_CMD_INTEGER:
typ->valType = OPARG_TYPE_NUMERIC;
break;
case VTP_CMD_BOOL:
case VTP_CMD_BOOLEAN:
typ->valType = OPARG_TYPE_BOOLEAN;
break;
case VTP_CMD_KEYWORD:
typ->valType = OPARG_TYPE_ENUMERATION;
break;
case VTP_CMD_SET:
case VTP_CMD_SET_MEMBERSHIP:
typ->valType = OPARG_TYPE_MEMBERSHIP;
break;
case VTP_CMD_NESTED:
case VTP_CMD_HIERARCHY:
typ->valType = OPARG_TYPE_HIERARCHY;
}
return txt + len;
}